pull/9482/merge
yxxhero 3 years ago committed by GitHub
commit 8d3815020c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -71,6 +71,36 @@ the dependency charts stored locally. The path should start with a prefix of
If the dependency chart is retrieved locally, it is not required to have the If the dependency chart is retrieved locally, it is not required to have the
repository added to helm by "helm add repo". Version matching is also supported repository added to helm by "helm add repo". Version matching is also supported
for this case. for this case.
A repository can be defined as a git URL. The path must start with a prefix of
"git://" followed by a valid git repository URL.
# Chart.yaml
dependencies:
- name: helm-chart
version: "main"
repository: "git://https://github.com/helm/helm-chart.git"
The 'repository' can be the https or ssh URL that you would use to clone a git
repo or add as a git remote, prefixed with 'git:'.
For example 'git://git@github.com:helm/helm-chart.git' or
'git://https://github.com/helm/helm-chart.git'
When using a 'git://' repository, the 'version' must be a valid semantic tag or branch
name for the git repo. For example 'master'.
Limitations when working with git repositories:
* Helm will use the 'git' executable on your system to retrieve information
about the repo. The 'git' command must be properly configured and available
on the PATH.
* When specifying a private repo, if git tries to query the user for
username/passowrd for an HTTPS url, or for a certificate password for an SSH
url, it may cause Helm to hang. Input is not forwarded to the child git
process, so it will not be able to receive user input. For private repos
it is recommended to use an SSH git url, and have your git client configured
with an SSH cert that does not require a password.
* The helm chart and 'Chart.yaml' must be in the root of the git repo.
The chart cannot be loaded from a subdirectory.
` `
const dependencyListDesc = ` const dependencyListDesc = `

@ -30,6 +30,7 @@ require (
github.com/spf13/cobra v1.4.0 github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.7.1
github.com/whilp/git-urls v1.0.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
@ -124,7 +125,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.15.0 // indirect github.com/onsi/gomega v1.17.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect

@ -870,7 +870,6 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
@ -1071,6 +1070,8 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmF
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=

@ -21,6 +21,12 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time"
"archive/tar"
"bytes"
"compress/gzip"
"helm.sh/helm/v3/internal/third_party/dep/fs" "helm.sh/helm/v3/internal/third_party/dep/fs"
) )
@ -49,3 +55,81 @@ func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error
return fs.RenameWithFallback(tempName, filename) return fs.RenameWithFallback(tempName, filename)
} }
func CompressDirToTgz(chartTmpDir, tmpdir string) (*bytes.Buffer, error) {
_, err := os.Stat(chartTmpDir)
if err != nil {
return nil, err
}
_, err = os.Stat(tmpdir)
if err != nil {
return nil, err
}
// tar => gzip => buf
buf := bytes.NewBuffer(nil)
zr := gzip.NewWriter(buf)
zr.ModTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
zr.Header.ModTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
zr.Header.OS = 3 // Unix
zr.OS = 3 //Unix
zr.Extra = nil
tw := tar.NewWriter(zr)
// walk through every file in the folder
walkErr := filepath.Walk(chartTmpDir, func(file string, fi os.FileInfo, err error) error {
// generate tar header
if err != nil {
return err
}
header, err := tar.FileInfoHeader(fi, strings.TrimPrefix(file, tmpdir+"/"))
if err != nil {
return err
}
// must provide real name
// (see https://golang.org/src/archive/tar/common.go?#L626)
header.Name = strings.TrimPrefix(filepath.ToSlash(file), tmpdir+"/")
header.ModTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
header.AccessTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
header.ChangeTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
header.Format = tar.FormatPAX
header.PAXRecords = nil
// write header
if err := tw.WriteHeader(header); err != nil {
return err
}
// if not a dir, write file content
if !fi.IsDir() {
data, err := os.Open(file)
if err != nil {
return err
}
if _, err := io.Copy(tw, data); err != nil {
return err
}
}
return nil
})
if walkErr != nil {
return nil, walkErr
}
// produce tar
if err := tw.Close(); err != nil {
return nil, err
}
// produce gzip
if err := zr.Close(); err != nil {
return nil, err
}
return buf, nil
}

@ -17,7 +17,10 @@ limitations under the License.
package fileutil package fileutil
import ( import (
"archive/tar"
"bytes" "bytes"
"compress/gzip"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -56,3 +59,47 @@ func TestAtomicWriteFile(t *testing.T) {
mode, gotinfo.Mode()) mode, gotinfo.Mode())
} }
} }
func TestCompressDirToTgz(t *testing.T) {
testDataDir := "testdata"
chartTestDir := "testdata/testdir"
chartBytes, err := CompressDirToTgz(chartTestDir, testDataDir)
if err != nil {
t.Fatal(err)
}
// gzip read
gr, err := gzip.NewReader(chartBytes)
if err != nil {
t.Fatal(err)
}
defer gr.Close()
// tar read
tr := tar.NewReader(gr)
defer gr.Close()
found := false
fileBytes := bytes.NewBuffer(nil)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if hdr.Name == "testdir/testfile" {
found = true
_, err := io.Copy(fileBytes, tr)
if err != nil {
t.Fatal(err)
}
}
}
if !found {
t.Fatal("testdir/testfile not found")
}
if !bytes.Equal(fileBytes.Bytes(), []byte("helm")) {
t.Fatalf("testdir/testfile's content not match, excpcted %s, got %s", "helm", fileBytes.String())
}
}

@ -29,12 +29,15 @@ import (
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/gitutils"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
var hasGitReference = gitutils.HasGitReference
// Resolver resolves dependencies from semantic version ranges to a particular version. // Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct { type Resolver struct {
chartpath string chartpath string
@ -107,6 +110,27 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
continue continue
} }
if strings.HasPrefix(d.Repository, "git://") {
found, err := hasGitReference(strings.TrimPrefix(d.Repository, "git://"), d.Version, d.Name)
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf(`dependency %q is missing git branch or tag: %s.
When using a "git://" type repository, the "version" should be a valid branch or tag name`, d.Name, d.Version)
}
locked[i] = &chart.Dependency{
Name: d.Name,
Repository: d.Repository,
Version: d.Version,
}
continue
}
repoName := repoNames[d.Name] repoName := repoNames[d.Name]
// if the repository was not defined, but the dependency defines a repository url, bypass the cache // if the repository was not defined, but the dependency defines a repository url, bypass the cache
if repoName == "" && d.Repository != "" { if repoName == "" && d.Repository != "" {

@ -23,7 +23,16 @@ import (
"helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/registry"
) )
func fakeGitReference(gitRepo, ref, repoName string) (bool, error) {
gitRefs := map[string]string{
"1.0.0": "",
}
_, found := gitRefs[ref]
return found, nil
}
func TestResolve(t *testing.T) { func TestResolve(t *testing.T) {
hasGitReference = fakeGitReference
tests := []struct { tests := []struct {
name string name string
req []*chart.Dependency req []*chart.Dependency
@ -137,6 +146,54 @@ func TestResolve(t *testing.T) {
}, },
err: true, err: true,
}, },
{
name: "repo from git https url",
req: []*chart.Dependency{
{Name: "gitdependencyok", Repository: "git://https://github.com/helm/helmchart.git", Version: "1.0.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "gitdependencyok", Repository: "git://https://github.com/helm/helmchart.git", Version: "1.0.0"},
},
},
err: false,
},
{
name: "repo from git https url",
req: []*chart.Dependency{
{Name: "gitdependencyerror", Repository: "git://https://github.com/helm/helmchart.git", Version: "2.0.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "gitdependencyerror", Repository: "git://https://github.com/helm/helmchart.git", Version: "2.0.0"},
},
},
err: true,
},
{
name: "repo from git ssh url",
req: []*chart.Dependency{
{Name: "gitdependency", Repository: "git://git@github.com:helm/helmchart.git", Version: "1.0.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "gitdependency", Repository: "git://git@github.com:helm/helmchart.git", Version: "1.0.0"},
},
},
err: false,
},
{
name: "repo from git ssh url",
req: []*chart.Dependency{
{Name: "gitdependencyerror", Repository: "git://git@github.com:helm/helmchart.git", Version: "2.0.0"},
},
expect: &chart.Lock{
Dependencies: []*chart.Dependency{
{Name: "gitdependencyerror", Repository: "git://git@github.com:helm/helmchart.git", Version: "2.0.0"},
},
},
err: true,
},
} }
repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"}

@ -26,6 +26,8 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
giturls "github.com/whilp/git-urls"
"helm.sh/helm/v3/internal/fileutil" "helm.sh/helm/v3/internal/fileutil"
"helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
@ -92,7 +94,15 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return "", nil, err return "", nil, err
} }
g, err := c.Getters.ByScheme(u.Scheme) scheme := ""
if strings.HasPrefix(ref, "git://") {
scheme = "git"
} else {
scheme = u.Scheme
}
g, err := c.Getters.ByScheme(scheme)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -103,10 +113,17 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
} }
name := filepath.Base(u.Path) name := filepath.Base(u.Path)
if u.Scheme == registry.OCIScheme { if u.Scheme == registry.OCIScheme || u.Scheme == "git" {
idx := strings.LastIndexByte(name, ':') idx := strings.LastIndexByte(name, ':')
name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:]) name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:])
} }
if scheme == "git" {
gitGetter, ok := g.(*getter.GitGetter)
if !ok {
return "", nil, fmt.Errorf("can't convert to GITGetter")
}
name = fmt.Sprintf("%s-%s.tgz", gitGetter.ChartName(), version)
}
destfile := filepath.Join(dest, name) destfile := filepath.Join(dest, name)
if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil { if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil {
@ -180,7 +197,7 @@ func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL,
// the URL using the appropriate Getter. // the URL using the appropriate Getter.
// //
// A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname' // A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname'
// reference, or a local path. // reference, git URL, or a local path.
// //
// A version is a SemVer string (1.2.3-beta.1+f334a6789). // A version is a SemVer string (1.2.3-beta.1+f334a6789).
// //
@ -190,6 +207,14 @@ func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL,
// * If version is empty, this will return the URL for the latest version // * If version is empty, this will return the URL for the latest version
// * If no version can be found, an error is returned // * If no version can be found, an error is returned
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) { func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) {
if strings.HasPrefix(ref, "git://") {
gitURL := strings.TrimPrefix(ref, "git://")
u, err := giturls.Parse(gitURL)
if err != nil {
return nil, errors.Errorf("invalid git URL format: %s", gitURL)
}
return u, nil
}
u, err := url.Parse(ref) u, err := url.Parse(ref)
if err != nil { if err != nil {
return nil, errors.Errorf("invalid chart URL format: %s", ref) return nil, errors.Errorf("invalid chart URL format: %s", ref)

@ -40,6 +40,7 @@ func TestResolveChartRef(t *testing.T) {
{name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"}, {name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"},
{name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"}, {name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"},
{name: "full URL, with authentication", ref: "http://username:password@example.com/foo-1.2.3.tgz", expect: "http://username:password@example.com/foo-1.2.3.tgz"}, {name: "full URL, with authentication", ref: "http://username:password@example.com/foo-1.2.3.tgz", expect: "http://username:password@example.com/foo-1.2.3.tgz"},
{name: "helmchart", ref: "git://https://github.com/helmchart/helmchart.git", expect: "https://github.com/helmchart/helmchart.git"},
{name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"}, {name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"},
{name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"}, {name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"},
{name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"}, {name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"},

@ -343,6 +343,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
} }
version := "" version := ""
if registry.IsOCI(churl) { if registry.IsOCI(churl) {
churl, version, err = parseOCIRef(churl) churl, version, err = parseOCIRef(churl)
if err != nil { if err != nil {
@ -353,6 +354,17 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
getter.WithTagName(version)) getter.WithTagName(version))
} }
if strings.HasPrefix(churl, "git://") {
version = dep.Version
dl.Options = append(dl.Options, getter.WithTagName(version))
dl.Options = append(dl.Options, getter.WithChartName(dep.Name))
if m.Debug {
fmt.Fprintf(m.Out, "Downloading %s from git repo %s\n", dep.Name, churl)
}
}
if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil { if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil {
saveError = errors.Wrapf(err, "could not download %s", churl) saveError = errors.Wrapf(err, "could not download %s", churl)
break break
@ -477,6 +489,11 @@ Loop:
continue continue
} }
// If repo is from git url, continue
if strings.HasPrefix(dd.Repository, "git://") {
continue
}
if dd.Repository == "" { if dd.Repository == "" {
continue continue
} }
@ -594,6 +611,16 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
reposMap[dd.Name] = dd.Repository reposMap[dd.Name] = dd.Repository
continue continue
} }
// if dep chart is from a git url, assume it is valid for now.
// if the repo does not exist then it will later error when we try to fetch branches and tags.
// we could check for the repo existence here, but trying to avoid anotehr git request.
if strings.HasPrefix(dd.Repository, "git://") {
if m.Debug {
fmt.Fprintf(m.Out, "Repository from git url: %s\n", strings.TrimPrefix(dd.Repository, "git:"))
}
reposMap[dd.Name] = dd.Repository
continue
}
found := false found := false
@ -704,6 +731,10 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
// //
// If it finds a URL that is "relative", it will prepend the repoURL. // If it finds a URL that is "relative", it will prepend the repoURL.
func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, caFile, certFile, keyFile string, err error) { func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, caFile, certFile, keyFile string, err error) {
if strings.HasPrefix(repoURL, "git://") {
return repoURL, "", "", false, false, "", "", "", nil
}
if registry.IsOCI(repoURL) { if registry.IsOCI(repoURL) {
return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil
} }

@ -80,55 +80,36 @@ func TestFindChartURL(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
name := "alpine" tests := []struct {
version := "0.1.0" name, version, repoURL, expectChurl, expectUserName, expectPassword string
repoURL := "http://example.com/charts" expectInsecureSkipTLSVerify, expectPasscredentialsall bool
}{
churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos) {name: "alpine", version: "0.1.0", repoURL: "http://example.com/charts", expectChurl: "https://charts.helm.sh/stable/alpine-0.1.0.tgz", expectUserName: "", expectPassword: "", expectInsecureSkipTLSVerify: false, expectPasscredentialsall: false},
{name: "tlsfoo", version: "1.2.3", repoURL: "https://example-https-insecureskiptlsverify.com", expectChurl: "https://example.com/tlsfoo-1.2.3.tgz", expectUserName: "", expectPassword: "", expectInsecureSkipTLSVerify: true, expectPasscredentialsall: false},
{name: "helm-test", version: "master", repoURL: "git://https://github.com/rally25rs/helm-test-chart.git", expectChurl: "git://https://github.com/rally25rs/helm-test-chart.git", expectUserName: "", expectPassword: "", expectInsecureSkipTLSVerify: false, expectPasscredentialsall: false},
}
for _, tt := range tests {
churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(tt.name, tt.version, tt.repoURL, repos)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if churl != tt.expectChurl {
if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" {
t.Errorf("Unexpected URL %q", churl) t.Errorf("Unexpected URL %q", churl)
} }
if username != "" { if username != tt.expectUserName {
t.Errorf("Unexpected username %q", username) t.Errorf("Unexpected username %q", username)
} }
if password != "" { if password != tt.expectPassword {
t.Errorf("Unexpected password %q", password) t.Errorf("Unexpected password %q", password)
} }
if passcredentialsall != false { if insecureSkipTLSVerify != tt.expectInsecureSkipTLSVerify {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
}
if insecureSkipTLSVerify {
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
}
name = "tlsfoo"
version = "1.2.3"
repoURL = "https://example-https-insecureskiptlsverify.com"
churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos)
if err != nil {
t.Fatal(err)
}
if !insecureSkipTLSVerify {
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
} }
if churl != "https://example.com/tlsfoo-1.2.3.tgz" { if passcredentialsall != tt.expectPasscredentialsall {
t.Errorf("Unexpected URL %q", churl)
}
if username != "" {
t.Errorf("Unexpected username %q", username)
}
if password != "" {
t.Errorf("Unexpected password %q", password)
}
if passcredentialsall != false {
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
} }
}
} }
func TestGetRepoNames(t *testing.T) { func TestGetRepoNames(t *testing.T) {
@ -193,6 +174,13 @@ func TestGetRepoNames(t *testing.T) {
}, },
expect: map[string]string{}, expect: map[string]string{},
}, },
{
name: "repo from git url",
req: []*chart.Dependency{
{Name: "local-dep", Repository: "git://https://github.com/git/git"},
},
expect: map[string]string{"local-dep": "git://https://github.com/git/git"},
},
} }
for _, tt := range tests { for _, tt := range tests {

@ -42,6 +42,7 @@ type options struct {
passCredentialsAll bool passCredentialsAll bool
userAgent string userAgent string
version string version string
chartName string
registryClient *registry.Client registryClient *registry.Client
timeout time.Duration timeout time.Duration
transport *http.Transport transport *http.Transport
@ -66,6 +67,11 @@ func WithBasicAuth(username, password string) Option {
opts.password = password opts.password = password
} }
} }
func WithChartName(chartName string) Option {
return func(opts *options) {
opts.chartName = chartName
}
}
func WithPassCredentialsAll(pass bool) Option { func WithPassCredentialsAll(pass bool) Option {
return func(opts *options) { return func(opts *options) {
@ -182,11 +188,16 @@ var ociProvider = Provider{
New: NewOCIGetter, New: NewOCIGetter,
} }
var gitProvider = Provider{
Schemes: []string{"git"},
New: NewGitGetter,
}
// All finds all of the registered getters as a list of Provider instances. // All finds all of the registered getters as a list of Provider instances.
// Currently, the built-in getters and the discovered plugins with downloader // Currently, the built-in getters and the discovered plugins with downloader
// notations are collected. // notations are collected.
func All(settings *cli.EnvSettings) Providers { func All(settings *cli.EnvSettings) Providers {
result := Providers{httpProvider, ociProvider} result := Providers{httpProvider, ociProvider, gitProvider}
pluginDownloaders, _ := collectPlugins(settings) pluginDownloaders, _ := collectPlugins(settings)
result = append(result, pluginDownloaders...) result = append(result, pluginDownloaders...)
return result return result

@ -57,8 +57,8 @@ func TestAll(t *testing.T) {
env.PluginsDirectory = pluginDir env.PluginsDirectory = pluginDir
all := All(env) all := All(env)
if len(all) != 4 { if len(all) != 5 {
t.Errorf("expected 4 providers (default plus three plugins), got %d", len(all)) t.Errorf("expected 5 providers (default plus three plugins), got %d", len(all))
} }
if _, err := all.ByScheme("test2"); err != nil { if _, err := all.ByScheme("test2"); err != nil {

@ -0,0 +1,114 @@
/*
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 getter
import (
"bytes"
"strings"
"fmt"
"os"
"path/filepath"
"github.com/Masterminds/vcs"
"helm.sh/helm/v3/internal/fileutil"
)
// GitGetter is the default HTTP(/S) backend handler
type GitGetter struct {
opts options
}
func (g *GitGetter) ChartName() string {
return g.opts.chartName
}
// ensureGitDirIgnored will append ".git/" to the .helmignore file in a directory.
// Create the .helmignore file if it does not exist.
func (g *GitGetter) 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
}
//Get performs a Get from repo.Getter and returns the body.
func (g *GitGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&g.opts)
}
return g.get(href)
}
func (g *GitGetter) get(href string) (*bytes.Buffer, error) {
gitURL := strings.TrimPrefix(href, "git://")
version := g.opts.version
chartName := g.opts.chartName
if version == "" {
return nil, fmt.Errorf("the version must be a valid tag or branch name for the git repo, not nil")
}
tmpDir, err := os.MkdirTemp("", "helm")
if err != nil {
return nil, err
}
chartTmpDir := filepath.Join(tmpDir, chartName)
if err := os.MkdirAll(chartTmpDir, 0755); err != nil {
return nil, err
}
defer os.RemoveAll(tmpDir)
repo, err := vcs.NewRepo(gitURL, chartTmpDir)
if err != nil {
return nil, err
}
if err := repo.Get(); err != nil {
return nil, err
}
if err := repo.UpdateVersion(version); err != nil {
return nil, 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(chartTmpDir)
buf, err := fileutil.CompressDirToTgz(chartTmpDir, tmpDir)
if err != nil {
return nil, fmt.Errorf("unable to tar and compress dir %s to tgz file. %s", tmpDir, err)
}
return buf, nil
}
// NewGitGetter constructs a valid git client as a Getter
func NewGitGetter(ops ...Option) (Getter, error) {
client := GitGetter{}
for _, opt := range ops {
opt(&client.opts)
}
return &client, nil
}

@ -0,0 +1,31 @@
/*
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 getter
import (
"testing"
)
func TestNewGITGetter(t *testing.T) {
g, err := NewGitGetter()
if err != nil {
t.Fatal(err)
}
if _, ok := g.(*GitGetter); !ok {
t.Fatal("Expected NewGITGetter to produce an *GITGetter")
}
}

@ -0,0 +1,47 @@
/*
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 engine implements the Go text template engine as needed for Helm.
When Helm renders templates it does so with additional functions and different
modes (e.g., strict, lint mode). This package handles the helm specific
implementation.
*/
package gitutils
import (
"os"
"github.com/Masterminds/vcs"
)
func HasGitReference(gitRepo, ref, repoName string) (bool, error) {
local, err := os.MkdirTemp("", repoName)
if err != nil {
return false, err
}
repo, err := vcs.NewRepo(gitRepo, local)
if err != nil {
return false, err
}
if err := repo.Get(); err != nil {
return false, err
}
defer os.RemoveAll(local)
return repo.IsReference(ref), nil
}
Loading…
Cancel
Save