mirror of https://github.com/helm/helm
parent
72e2bff0af
commit
9bdc07b0ba
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package downloader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
|
"helm.sh/helm/v3/pkg/chartutil"
|
||||||
|
"helm.sh/helm/v3/pkg/gitutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assigned here so it can be overridden for testing.
|
||||||
|
var gitCloneTo = gitutil.CloneTo
|
||||||
|
|
||||||
|
// GitDownloader handles downloading a chart from a git url.
|
||||||
|
type GitDownloader struct{}
|
||||||
|
|
||||||
|
// ensureGitDirIgnored will append ".git/" to the .helmignore file in a directory.
|
||||||
|
// Create the .helmignore file if it does not exist.
|
||||||
|
func (g *GitDownloader) ensureGitDirIgnored(repoPath string) error {
|
||||||
|
helmignorePath := filepath.Join(repoPath, ".helmignore")
|
||||||
|
f, err := os.OpenFile(helmignorePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.WriteString("\n.git/\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadTo will create a temp directory, then fetch a git repo into it.
|
||||||
|
// The git repo will be archived into a chart and copied to the destPath.
|
||||||
|
func (g *GitDownloader) DownloadTo(gitURL string, ref string, destPath string) error {
|
||||||
|
// the git archive command returns a tgz archive. we need to extract it to get the actual chart files.
|
||||||
|
tmpDir, err := ioutil.TempDir("", "helm")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
if err = gitCloneTo(gitURL, ref, tmpDir); err != nil {
|
||||||
|
return fmt.Errorf("Unable to retrieve git repo. %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A .helmignore that includes an ignore for .git/ should be included in the git repo itself,
|
||||||
|
// but a lot of people will probably not think about that.
|
||||||
|
// To prevent the git history from bleeding into the charts archive, append/create .helmignore.
|
||||||
|
g.ensureGitDirIgnored(tmpDir)
|
||||||
|
|
||||||
|
// Turn the extracted git archive into a chart and move it into the charts directory.
|
||||||
|
// This is using chartutil.Save() so that .helmignore logic is applied.
|
||||||
|
loadedChart, loadErr := loader.LoadDir(tmpDir)
|
||||||
|
if loadErr != nil {
|
||||||
|
return fmt.Errorf("Unable to process the git repo %s as a chart. %s", gitURL, err)
|
||||||
|
}
|
||||||
|
if _, saveErr := chartutil.Save(loadedChart, destPath); saveErr != nil {
|
||||||
|
return fmt.Errorf("Unable to save the git repo %s as a chart. %s", gitURL, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package gitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var execCommand = exec.Command
|
||||||
|
|
||||||
|
// This regex is designed to match output from git of the style:
|
||||||
|
// ebeb6eafceb61dd08441ffe086c77eb472842494 refs/tags/v0.21.0
|
||||||
|
// and extract the hash and ref name as capture groups
|
||||||
|
var gitRefLineRegexp = regexp.MustCompile(`^([a-fA-F0-9]+)\s+(refs\/(?:tags|heads|pull|remotes)\/)(.*)$`)
|
||||||
|
|
||||||
|
// Run a git command as a child process.
|
||||||
|
// If git is not on the path, an error will be returned.
|
||||||
|
// Returns the command output or an error.
|
||||||
|
func gitExec(args ...string) ([]byte, error) {
|
||||||
|
cmd := execCommand("git", args...)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error executing git command:\ngit %s\n\n%s\n%s", strings.Join(args, " "), string(output), err)
|
||||||
|
}
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRefs loads the tags, refs, branches (commit-ish) from a git repo.
|
||||||
|
// Returns a map of tags and branch names to commit shas
|
||||||
|
func GetRefs(gitRepoURL string) (map[string]string, error) {
|
||||||
|
output, err := gitExec("ls-remote", "--tags", "--heads", gitRepoURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsToCommitShas := map[string]string{}
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(output)))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
match := gitRefLineRegexp.FindSubmatch(line)
|
||||||
|
if len(match) == 4 {
|
||||||
|
// As documented in gitrevisions: https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html#_specifying_revisions
|
||||||
|
// "A suffix ^ followed by an empty brace pair means the object could be a tag, and dereference the tag recursively until a non-tag object is found."
|
||||||
|
// In other words, the hash without ^{} is the hash of the tag, and the hash with ^{} is the hash of the commit at which the tag was made.
|
||||||
|
// For our purposes, either will work.
|
||||||
|
var name = strings.TrimSuffix(string(match[3]), "^{}")
|
||||||
|
tagsToCommitShas[name] = string(match[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagsToCommitShas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneTo fetches a git repo at a specific ref from a git url
|
||||||
|
func CloneTo(gitRepoURL string, ref string, destinationPath string) error {
|
||||||
|
_, err := gitExec("clone", "--depth", "1", gitRepoURL, "--branch", ref, "--single-branch", destinationPath)
|
||||||
|
return err
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package gitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
||||||
|
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||||
|
cs = append(cs, args...)
|
||||||
|
cmd := exec.Command(os.Args[0], cs...)
|
||||||
|
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHelperProcess is not a test. It is used to create a mock process to spawn as a child process for testing exec.Command().
|
||||||
|
// Borrowed from the way Go tests exec.Command() internally: https://github.com/golang/go/blob/master/src/os/exec/exec_test.go#L727
|
||||||
|
func TestHelperProcess(t *testing.T) {
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args := os.Args
|
||||||
|
cmd := args[len(args)-1] // the last arg is the github url. using this to determine what to return.
|
||||||
|
result := "Unknown Command"
|
||||||
|
exitCode := 1
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "success":
|
||||||
|
exitCode = 0
|
||||||
|
result = `From git@github.com:helm/helm.git
|
||||||
|
9b42702a4bced339ff424a78ad68dd6be6e1a80a refs/heads/dev
|
||||||
|
9668ad4d90c5e95bd520e58e7387607be6b63bb6 refs/heads/master
|
||||||
|
44fb06eb69fecd4b6a5b2443a4768ba12bd70c09 refs/tags/v2.10.0
|
||||||
|
9ad53aac42165a5fadc6c87be0dea6b115f93090 refs/tags/v2.10.0^{}
|
||||||
|
4fdd07f21418abb43925998cf690857adc16451b refs/tags/v2.10.0-rc.1
|
||||||
|
aa98e7e3dd2356bce72e8e367e8c87e8085c692b refs/tags/v2.10.0-rc.1^{}`
|
||||||
|
|
||||||
|
case "error":
|
||||||
|
exitCode = 1
|
||||||
|
result = `ssh: Could not resolve hostname git: nodename nor servname provided, or not known
|
||||||
|
fatal: Could not read from remote repository.
|
||||||
|
|
||||||
|
Please make sure you have the correct access rights
|
||||||
|
and the repository exists.`
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, result)
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRefsWhenGitReturnsRefs(t *testing.T) {
|
||||||
|
expected := map[string]string{
|
||||||
|
"dev": "9b42702a4bced339ff424a78ad68dd6be6e1a80a",
|
||||||
|
"master": "9668ad4d90c5e95bd520e58e7387607be6b63bb6",
|
||||||
|
"v2.10.0": "9ad53aac42165a5fadc6c87be0dea6b115f93090",
|
||||||
|
"v2.10.0-rc.1": "aa98e7e3dd2356bce72e8e367e8c87e8085c692b",
|
||||||
|
}
|
||||||
|
execCommand = fakeExecCommand
|
||||||
|
defer func() { execCommand = exec.Command }()
|
||||||
|
result, err := GetRefs("success")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("Expected %q, got %q", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestGetRefsWhenGitCommandReturnsError(t *testing.T) {
|
||||||
|
execCommand = fakeExecCommand
|
||||||
|
defer func() { execCommand = exec.Command }()
|
||||||
|
_, err := GetRefs("error")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Error should have been returned, but was not")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue