openpitrix crd

Signed-off-by: LiHui <andrewli@yunify.com>

delete helm repo, release and app

Signed-off-by: LiHui <andrewli@yunify.com>

Fix Dockerfile

Signed-off-by: LiHui <andrewli@yunify.com>

add unit test for category controller

Signed-off-by: LiHui <andrewli@yunify.com>

resource api

Signed-off-by: LiHui <andrewli@yunify.com>

miscellaneous

Signed-off-by: LiHui <andrewli@yunify.com>

resource api

Signed-off-by: LiHui <andrewli@yunify.com>

add s3 repo indx

Signed-off-by: LiHui <andrewli@yunify.com>

attachment api

Signed-off-by: LiHui <andrewli@yunify.com>

repo controller test

Signed-off-by: LiHui <andrewli@yunify.com>

application controller test

Signed-off-by: LiHui <andrewli@yunify.com>

release metric

Signed-off-by: LiHui <andrewli@yunify.com>

helm release controller test

Signed-off-by: LiHui <andrewli@yunify.com>

move constants to /pkg/apis/application

Signed-off-by: LiHui <andrewli@yunify.com>

remove unused code

Signed-off-by: LiHui <andrewli@yunify.com>

add license header

Signed-off-by: LiHui <andrewli@yunify.com>

Fix bugs

Signed-off-by: LiHui <andrewli@yunify.com>

cluster cluent

Signed-off-by: LiHui <andrewli@yunify.com>

format code

Signed-off-by: LiHui <andrewli@yunify.com>

move workspace,cluster from spec to labels

Signed-off-by: LiHui <andrewli@yunify.com>

add license header

Signed-off-by: LiHui <andrewli@yunify.com>

openpitrix test

Signed-off-by: LiHui <andrewli@yunify.com>

add worksapce labels for app in appstore

Signed-off-by: LiHui <andrewli@yunify.com>
This commit is contained in:
LiHui
2020-12-23 15:24:30 +08:00
parent 737639020b
commit 83e6221f3a
193 changed files with 19634 additions and 6039 deletions

View File

@@ -0,0 +1,169 @@
/*
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 chart
import (
"path/filepath"
"strings"
)
// APIVersionV1 is the API version number for version 1.
const APIVersionV1 = "v1"
// APIVersionV2 is the API version number for version 2.
const APIVersionV2 = "v2"
// Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies).
type Chart struct {
// Raw contains the raw contents of the files originally contained in the chart archive.
//
// This should not be used except in special cases like `helm show values`,
// where we want to display the raw values, comments and all.
Raw []*File `json:"-"`
// Metadata is the contents of the Chartfile.
Metadata *Metadata `json:"metadata"`
// Lock is the contents of Chart.lock.
Lock *Lock `json:"lock"`
// Templates for this chart.
Templates []*File `json:"templates"`
// Values are default config for this chart.
Values map[string]interface{} `json:"values"`
// Schema is an optional JSON schema for imposing structure on Values
Schema []byte `json:"schema"`
// Files are miscellaneous files in a chart archive,
// e.g. README, LICENSE, etc.
Files []*File `json:"files"`
parent *Chart
dependencies []*Chart
}
type CRD struct {
// Name is the File.Name for the crd file
Name string
// Filename is the File obj Name including (sub-)chart.ChartFullPath
Filename string
// File is the File obj for the crd
File *File
}
// SetDependencies replaces the chart dependencies.
func (ch *Chart) SetDependencies(charts ...*Chart) {
ch.dependencies = nil
ch.AddDependency(charts...)
}
// Name returns the name of the chart.
func (ch *Chart) Name() string {
if ch.Metadata == nil {
return ""
}
return ch.Metadata.Name
}
// AddDependency determines if the chart is a subchart.
func (ch *Chart) AddDependency(charts ...*Chart) {
for i, x := range charts {
charts[i].parent = ch
ch.dependencies = append(ch.dependencies, x)
}
}
// Root finds the root chart.
func (ch *Chart) Root() *Chart {
if ch.IsRoot() {
return ch
}
return ch.Parent().Root()
}
// Dependencies are the charts that this chart depends on.
func (ch *Chart) Dependencies() []*Chart { return ch.dependencies }
// IsRoot determines if the chart is the root chart.
func (ch *Chart) IsRoot() bool { return ch.parent == nil }
// Parent returns a subchart's parent chart.
func (ch *Chart) Parent() *Chart { return ch.parent }
// ChartPath returns the full path to this chart in dot notation.
func (ch *Chart) ChartPath() string {
if !ch.IsRoot() {
return ch.Parent().ChartPath() + "." + ch.Name()
}
return ch.Name()
}
// ChartFullPath returns the full path to this chart.
func (ch *Chart) ChartFullPath() string {
if !ch.IsRoot() {
return ch.Parent().ChartFullPath() + "/charts/" + ch.Name()
}
return ch.Name()
}
// Validate validates the metadata.
func (ch *Chart) Validate() error {
return ch.Metadata.Validate()
}
// AppVersion returns the appversion of the chart.
func (ch *Chart) AppVersion() string {
if ch.Metadata == nil {
return ""
}
return ch.Metadata.AppVersion
}
// CRDs returns a list of File objects in the 'crds/' directory of a Helm chart.
// Deprecated: use CRDObjects()
func (ch *Chart) CRDs() []*File {
files := []*File{}
// Find all resources in the crds/ directory
for _, f := range ch.Files {
if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) {
files = append(files, f)
}
}
// Get CRDs from dependencies, too.
for _, dep := range ch.Dependencies() {
files = append(files, dep.CRDs()...)
}
return files
}
// CRDObjects returns a list of CRD objects in the 'crds/' directory of a Helm chart & subcharts
func (ch *Chart) CRDObjects() []CRD {
crds := []CRD{}
// Find all resources in the crds/ directory
for _, f := range ch.Files {
if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) {
mycrd := CRD{Name: f.Name, Filename: filepath.Join(ch.ChartFullPath(), f.Name), File: f}
crds = append(crds, mycrd)
}
}
// Get CRDs from dependencies, too.
for _, dep := range ch.Dependencies() {
crds = append(crds, dep.CRDObjects()...)
}
return crds
}
func hasManifestExtension(fname string) bool {
ext := filepath.Ext(fname)
return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json")
}

View File

@@ -0,0 +1,62 @@
/*
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 chart
import "time"
// Dependency describes a chart upon which another chart depends.
//
// Dependencies can be used to express developer intent, or to capture the state
// of a chart.
type Dependency struct {
// Name is the name of the dependency.
//
// This must mach the name in the dependency's Chart.yaml.
Name string `json:"name"`
// Version is the version (range) of this chart.
//
// A lock file will always produce a single version, while a dependency
// may contain a semantic version range.
Version string `json:"version,omitempty"`
// The URL to the repository.
//
// Appending `index.yaml` to this string should result in a URL that can be
// used to fetch the repository index.
Repository string `json:"repository"`
// A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
Condition string `json:"condition,omitempty"`
// Tags can be used to group charts for enabling/disabling together
Tags []string `json:"tags,omitempty"`
// Enabled bool determines if chart should be loaded
Enabled bool `json:"enabled,omitempty"`
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
// string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values,omitempty"`
// Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty"`
}
// Lock is a lock file for dependencies.
//
// It represents the state that the dependencies should be in.
type Lock struct {
// Generated is the date the lock file was last generated.
Generated time.Time `json:"generated"`
// Digest is a hash of the dependencies in Chart.yaml.
Digest string `json:"digest"`
// Dependencies is the list of dependencies that this lock file has locked.
Dependencies []*Dependency `json:"dependencies"`
}

View File

@@ -0,0 +1,23 @@
/*
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 chart
// ValidationError represents a data validation error.
type ValidationError string
func (v ValidationError) Error() string {
return "validation: " + string(v)
}

View File

@@ -0,0 +1,27 @@
/*
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 chart
// File represents a file as a name/value pair.
//
// By convention, name is a relative path within the scope of the chart's
// base directory.
type File struct {
// Name is the path-like name of the template.
Name string `json:"name"`
// Data is the template as byte data.
Data []byte `json:"data"`
}

View File

@@ -0,0 +1,196 @@
/*
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 loader
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"path"
"regexp"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart"
)
var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`)
// FileLoader loads a chart from a file
type FileLoader string
// Load loads a chart
func (l FileLoader) Load() (*chart.Chart, error) {
return LoadFile(string(l))
}
// LoadFile loads from an archive file.
func LoadFile(name string) (*chart.Chart, error) {
if fi, err := os.Stat(name); err != nil {
return nil, err
} else if fi.IsDir() {
return nil, errors.New("cannot load a directory")
}
raw, err := os.Open(name)
if err != nil {
return nil, err
}
defer raw.Close()
err = ensureArchive(name, raw)
if err != nil {
return nil, err
}
c, err := LoadArchive(raw)
if err != nil {
if err == gzip.ErrHeader {
return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err)
}
}
return c, err
}
// ensureArchive's job is to return an informative error if the file does not appear to be a gzipped archive.
//
// Sometimes users will provide a values.yaml for an argument where a chart is expected. One common occurrence
// of this is invoking `helm template values.yaml mychart` which would otherwise produce a confusing error
// if we didn't check for this.
func ensureArchive(name string, raw *os.File) error {
defer raw.Seek(0, 0) // reset read offset to allow archive loading to proceed.
// Check the file format to give us a chance to provide the user with more actionable feedback.
buffer := make([]byte, 512)
_, err := raw.Read(buffer)
if err != nil && err != io.EOF {
return fmt.Errorf("file '%s' cannot be read: %s", name, err)
}
if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" {
// TODO: Is there a way to reliably test if a file content is YAML? ghodss/yaml accepts a wide
// variety of content (Makefile, .zshrc) as valid YAML without errors.
// Wrong content type. Let's check if it's yaml and give an extra hint?
if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") {
return fmt.Errorf("file '%s' seems to be a YAML file, but expected a gzipped archive", name)
}
return fmt.Errorf("file '%s' does not appear to be a gzipped archive; got '%s'", name, contentType)
}
return nil
}
// LoadArchiveFiles reads in files out of an archive into memory. This function
// performs important path security checks and should always be used before
// expanding a tarball
func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
unzipped, err := gzip.NewReader(in)
if err != nil {
return nil, err
}
defer unzipped.Close()
files := []*BufferedFile{}
tr := tar.NewReader(unzipped)
for {
b := bytes.NewBuffer(nil)
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if hd.FileInfo().IsDir() {
// Use this instead of hd.Typeflag because we don't have to do any
// inference chasing.
continue
}
switch hd.Typeflag {
// We don't want to process these extension header files.
case tar.TypeXGlobalHeader, tar.TypeXHeader:
continue
}
// Archive could contain \ if generated on Windows
delimiter := "/"
if strings.ContainsRune(hd.Name, '\\') {
delimiter = "\\"
}
parts := strings.Split(hd.Name, delimiter)
n := strings.Join(parts[1:], delimiter)
// Normalize the path to the / delimiter
n = strings.ReplaceAll(n, delimiter, "/")
if path.IsAbs(n) {
return nil, errors.New("chart illegally contains absolute paths")
}
n = path.Clean(n)
if n == "." {
// In this case, the original path was relative when it should have been absolute.
return nil, errors.Errorf("chart illegally contains content outside the base directory: %q", hd.Name)
}
if strings.HasPrefix(n, "..") {
return nil, errors.New("chart illegally references parent directory")
}
// In some particularly arcane acts of path creativity, it is possible to intermix
// UNIX and Windows style paths in such a way that you produce a result of the form
// c:/foo even after all the built-in absolute path checks. So we explicitly check
// for this condition.
if drivePathPattern.MatchString(n) {
return nil, errors.New("chart contains illegally named files")
}
if parts[0] == "Chart.yaml" {
return nil, errors.New("chart yaml not in base directory")
}
if _, err := io.Copy(b, tr); err != nil {
return nil, err
}
data := bytes.TrimPrefix(b.Bytes(), utf8bom)
files = append(files, &BufferedFile{Name: n, Data: data})
b.Reset()
}
if len(files) == 0 {
return nil, errors.New("no files in chart archive")
}
return files, nil
}
// LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (*chart.Chart, error) {
files, err := LoadArchiveFiles(in)
if err != nil {
return nil, err
}
return LoadFiles(files)
}

View File

@@ -0,0 +1,120 @@
/*
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 loader
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/internal/ignore"
"helm.sh/helm/v3/internal/sympath"
"helm.sh/helm/v3/pkg/chart"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// DirLoader loads a chart from a directory
type DirLoader string
// Load loads the chart
func (l DirLoader) Load() (*chart.Chart, error) {
return LoadDir(string(l))
}
// LoadDir loads from a directory.
//
// This loads charts only from directories.
func LoadDir(dir string) (*chart.Chart, error) {
topdir, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
// Just used for errors.
c := &chart.Chart{}
rules := ignore.Empty()
ifile := filepath.Join(topdir, ignore.HelmIgnore)
if _, err := os.Stat(ifile); err == nil {
r, err := ignore.ParseFile(ifile)
if err != nil {
return c, err
}
rules = r
}
rules.AddDefaults()
files := []*BufferedFile{}
topdir += string(filepath.Separator)
walk := func(name string, fi os.FileInfo, err error) error {
n := strings.TrimPrefix(name, topdir)
if n == "" {
// No need to process top level. Avoid bug with helmignore .* matching
// empty names. See issue 1779.
return nil
}
// Normalize to / since it will also work on Windows
n = filepath.ToSlash(n)
if err != nil {
return err
}
if fi.IsDir() {
// Directory-based ignore rules should involve skipping the entire
// contents of that directory.
if rules.Ignore(n, fi) {
return filepath.SkipDir
}
return nil
}
// If a .helmignore file matches, skip this file.
if rules.Ignore(n, fi) {
return nil
}
// Irregular files include devices, sockets, and other uses of files that
// are not regular files. In Go they have a file mode type bit set.
// See https://golang.org/pkg/os/#FileMode for examples.
if !fi.Mode().IsRegular() {
return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name)
}
data, err := ioutil.ReadFile(name)
if err != nil {
return errors.Wrapf(err, "error reading %s", n)
}
data = bytes.TrimPrefix(data, utf8bom)
files = append(files, &BufferedFile{Name: n, Data: data})
return nil
}
if err = sympath.Walk(topdir, walk); err != nil {
return c, err
}
return LoadFiles(files)
}

View File

@@ -0,0 +1,185 @@
/*
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 loader
import (
"bytes"
"log"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart"
)
// ChartLoader loads a chart.
type ChartLoader interface {
Load() (*chart.Chart, error)
}
// Loader returns a new ChartLoader appropriate for the given chart name
func Loader(name string) (ChartLoader, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if fi.IsDir() {
return DirLoader(name), nil
}
return FileLoader(name), nil
}
// Load takes a string name, tries to resolve it to a file or directory, and then loads it.
//
// This is the preferred way to load a chart. It will discover the chart encoding
// and hand off to the appropriate chart reader.
//
// If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive.
func Load(name string) (*chart.Chart, error) {
l, err := Loader(name)
if err != nil {
return nil, err
}
return l.Load()
}
// BufferedFile represents an archive file buffered for later processing.
type BufferedFile struct {
Name string
Data []byte
}
// LoadFiles loads from in-memory files.
func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
c := new(chart.Chart)
subcharts := make(map[string][]*BufferedFile)
for _, f := range files {
c.Raw = append(c.Raw, &chart.File{Name: f.Name, Data: f.Data})
switch {
case f.Name == "Chart.yaml":
if c.Metadata == nil {
c.Metadata = new(chart.Metadata)
}
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
return c, errors.Wrap(err, "cannot load Chart.yaml")
}
// NOTE(bacongobbler): while the chart specification says that APIVersion must be set,
// Helm 2 accepted charts that did not provide an APIVersion in their chart metadata.
// Because of that, if APIVersion is unset, we should assume we're loading a v1 chart.
if c.Metadata.APIVersion == "" {
c.Metadata.APIVersion = chart.APIVersionV1
}
case f.Name == "Chart.lock":
c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
return c, errors.Wrap(err, "cannot load Chart.lock")
}
case f.Name == "values.yaml":
c.Values = make(map[string]interface{})
if err := yaml.Unmarshal(f.Data, &c.Values); err != nil {
return c, errors.Wrap(err, "cannot load values.yaml")
}
case f.Name == "values.schema.json":
c.Schema = f.Data
// Deprecated: requirements.yaml is deprecated use Chart.yaml.
// We will handle it for you because we are nice people
case f.Name == "requirements.yaml":
if c.Metadata == nil {
c.Metadata = new(chart.Metadata)
}
if c.Metadata.APIVersion != chart.APIVersionV1 {
log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.")
}
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
return c, errors.Wrap(err, "cannot load requirements.yaml")
}
if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
}
// Deprecated: requirements.lock is deprecated use Chart.lock.
case f.Name == "requirements.lock":
c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
return c, errors.Wrap(err, "cannot load requirements.lock")
}
if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
}
case strings.HasPrefix(f.Name, "templates/"):
c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data})
case strings.HasPrefix(f.Name, "charts/"):
if filepath.Ext(f.Name) == ".prov" {
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
continue
}
fname := strings.TrimPrefix(f.Name, "charts/")
cname := strings.SplitN(fname, "/", 2)[0]
subcharts[cname] = append(subcharts[cname], &BufferedFile{Name: fname, Data: f.Data})
default:
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
}
}
if err := c.Validate(); err != nil {
return c, err
}
for n, files := range subcharts {
var sc *chart.Chart
var err error
switch {
case strings.IndexAny(n, "_.") == 0:
continue
case filepath.Ext(n) == ".tgz":
file := files[0]
if file.Name != n {
return c, errors.Errorf("error unpacking tar in %s: expected %s, got %s", c.Name(), n, file.Name)
}
// Untar the chart and add to c.Dependencies
sc, err = LoadArchive(bytes.NewBuffer(file.Data))
default:
// We have to trim the prefix off of every file, and ignore any file
// that is in charts/, but isn't actually a chart.
buff := make([]*BufferedFile, 0, len(files))
for _, f := range files {
parts := strings.SplitN(f.Name, "/", 2)
if len(parts) < 2 {
continue
}
f.Name = parts[1]
buff = append(buff, f)
}
sc, err = LoadFiles(buff)
}
if err != nil {
return c, errors.Wrapf(err, "error unpacking %s in %s", n, c.Name())
}
c.AddDependency(sc)
}
return c, nil
}

View File

@@ -0,0 +1,94 @@
/*
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 chart
// Maintainer describes a Chart maintainer.
type Maintainer struct {
// Name is a user name or organization name
Name string `json:"name,omitempty"`
// Email is an optional email address to contact the named maintainer
Email string `json:"email,omitempty"`
// URL is an optional URL to an address for the named maintainer
URL string `json:"url,omitempty"`
}
// Metadata for a Chart file. This models the structure of a Chart.yaml file.
type Metadata struct {
// The name of the chart
Name string `json:"name,omitempty"`
// The URL to a relevant project page, git repo, or contact person
Home string `json:"home,omitempty"`
// Source is the URL to the source code of this chart
Sources []string `json:"sources,omitempty"`
// A SemVer 2 conformant version string of the chart
Version string `json:"version,omitempty"`
// A one-sentence description of the chart
Description string `json:"description,omitempty"`
// A list of string keywords
Keywords []string `json:"keywords,omitempty"`
// A list of name and URL/email address combinations for the maintainer(s)
Maintainers []*Maintainer `json:"maintainers,omitempty"`
// The URL to an icon file.
Icon string `json:"icon,omitempty"`
// The API Version of this chart.
APIVersion string `json:"apiVersion,omitempty"`
// The condition to check to enable chart
Condition string `json:"condition,omitempty"`
// The tags to check to enable chart
Tags string `json:"tags,omitempty"`
// The version of the application enclosed inside of this chart.
AppVersion string `json:"appVersion,omitempty"`
// Whether or not this chart is deprecated
Deprecated bool `json:"deprecated,omitempty"`
// Annotations are additional mappings uninterpreted by Helm,
// made available for inspection by other applications.
Annotations map[string]string `json:"annotations,omitempty"`
// KubeVersion is a SemVer constraint specifying the version of Kubernetes required.
KubeVersion string `json:"kubeVersion,omitempty"`
// Dependencies are a list of dependencies for a chart.
Dependencies []*Dependency `json:"dependencies,omitempty"`
// Specifies the chart type: application or library
Type string `json:"type,omitempty"`
}
// Validate checks the metadata for known issues, returning an error if metadata is not correct
func (md *Metadata) Validate() error {
if md == nil {
return ValidationError("chart.metadata is required")
}
if md.APIVersion == "" {
return ValidationError("chart.metadata.apiVersion is required")
}
if md.Name == "" {
return ValidationError("chart.metadata.name is required")
}
if md.Version == "" {
return ValidationError("chart.metadata.version is required")
}
if !isValidChartType(md.Type) {
return ValidationError("chart.metadata.type must be application or library")
}
// TODO validate valid semver here?
return nil
}
func isValidChartType(in string) bool {
switch in {
case "", "application", "library":
return true
}
return false
}

View File

@@ -0,0 +1,140 @@
/*
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 cli describes the operating environment for the Helm CLI.
Helm's environment encapsulates all of the service dependencies Helm has.
These dependencies are expressed as interfaces so that alternate implementations
(mocks, etc.) can be easily generated.
*/
package cli
import (
"fmt"
"os"
"strconv"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
"helm.sh/helm/v3/pkg/helmpath"
)
// EnvSettings describes all of the environment settings.
type EnvSettings struct {
namespace string
config *genericclioptions.ConfigFlags
// KubeConfig is the path to the kubeconfig file
KubeConfig string
// KubeContext is the name of the kubeconfig context.
KubeContext string
// Bearer KubeToken used for authentication
KubeToken string
// Kubernetes API Server Endpoint for authentication
KubeAPIServer string
// Debug indicates whether or not Helm is running in Debug mode.
Debug bool
// RegistryConfig is the path to the registry config file.
RegistryConfig string
// RepositoryConfig is the path to the repositories file.
RepositoryConfig string
// RepositoryCache is the path to the repository cache directory.
RepositoryCache string
// PluginsDirectory is the path to the plugins directory.
PluginsDirectory string
}
func New() *EnvSettings {
env := &EnvSettings{
namespace: os.Getenv("HELM_NAMESPACE"),
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
}
env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG"))
// bind to kubernetes config flags
env.config = &genericclioptions.ConfigFlags{
Namespace: &env.namespace,
Context: &env.KubeContext,
BearerToken: &env.KubeToken,
APIServer: &env.KubeAPIServer,
KubeConfig: &env.KubeConfig,
}
return env
}
// AddFlags binds flags to the given flagset.
func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request")
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use")
fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication")
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes")
}
func envOr(name, def string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
return def
}
func (s *EnvSettings) EnvVars() map[string]string {
envvars := map[string]string{
"HELM_BIN": os.Args[0],
"HELM_CACHE_HOME": helmpath.CachePath(""),
"HELM_CONFIG_HOME": helmpath.ConfigPath(""),
"HELM_DATA_HOME": helmpath.DataPath(""),
"HELM_DEBUG": fmt.Sprint(s.Debug),
"HELM_PLUGINS": s.PluginsDirectory,
"HELM_REGISTRY_CONFIG": s.RegistryConfig,
"HELM_REPOSITORY_CACHE": s.RepositoryCache,
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(),
// broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEAPISERVER": s.KubeAPIServer,
}
if s.KubeConfig != "" {
envvars["KUBECONFIG"] = s.KubeConfig
}
return envvars
}
// Namespace gets the namespace from the configuration
func (s *EnvSettings) Namespace() string {
if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil {
return ns
}
return "default"
}
// RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
return s.config
}

View File

@@ -0,0 +1,21 @@
/*
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 provides a generalize tool for fetching data by scheme.
This provides a method by which the plugin system can load arbitrary protocol
handlers based upon a URL scheme.
*/
package getter

View File

@@ -0,0 +1,150 @@
/*
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"
"time"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/cli"
)
// options are generic parameters to be provided to the getter during instantiation.
//
// Getters may or may not ignore these parameters as they are passed in.
type options struct {
url string
certFile string
keyFile string
caFile string
insecureSkipVerifyTLS bool
username string
password string
userAgent string
timeout time.Duration
}
// Option allows specifying various settings configurable by the user for overriding the defaults
// used when performing Get operations with the Getter.
type Option func(*options)
// WithURL informs the getter the server name that will be used when fetching objects. Used in conjunction with
// WithTLSClientConfig to set the TLSClientConfig's server name.
func WithURL(url string) Option {
return func(opts *options) {
opts.url = url
}
}
// WithBasicAuth sets the request's Authorization header to use the provided credentials
func WithBasicAuth(username, password string) Option {
return func(opts *options) {
opts.username = username
opts.password = password
}
}
// WithUserAgent sets the request's User-Agent header to use the provided agent name.
func WithUserAgent(userAgent string) Option {
return func(opts *options) {
opts.userAgent = userAgent
}
}
// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked
func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option {
return func(opts *options) {
opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS
}
}
// WithTLSClientConfig sets the client auth with the provided credentials.
func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
return func(opts *options) {
opts.certFile = certFile
opts.keyFile = keyFile
opts.caFile = caFile
}
}
// WithTimeout sets the timeout for requests
func WithTimeout(timeout time.Duration) Option {
return func(opts *options) {
opts.timeout = timeout
}
}
// Getter is an interface to support GET to the specified URL.
type Getter interface {
// Get file content by url string
Get(url string, options ...Option) (*bytes.Buffer, error)
}
// Constructor is the function for every getter which creates a specific instance
// according to the configuration
type Constructor func(options ...Option) (Getter, error)
// Provider represents any getter and the schemes that it supports.
//
// For example, an HTTP provider may provide one getter that handles both
// 'http' and 'https' schemes.
type Provider struct {
Schemes []string
New Constructor
}
// Provides returns true if the given scheme is supported by this Provider.
func (p Provider) Provides(scheme string) bool {
for _, i := range p.Schemes {
if i == scheme {
return true
}
}
return false
}
// Providers is a collection of Provider objects.
type Providers []Provider
// ByScheme returns a Provider that handles the given scheme.
//
// If no provider handles this scheme, this will return an error.
func (p Providers) ByScheme(scheme string) (Getter, error) {
for _, pp := range p {
if pp.Provides(scheme) {
return pp.New()
}
}
return nil, errors.Errorf("scheme %q not supported", scheme)
}
var httpProvider = Provider{
Schemes: []string{"http", "https"},
New: NewHTTPGetter,
}
// All finds all of the registered getters as a list of Provider instances.
// Currently, the built-in getters and the discovered plugins with downloader
// notations are collected.
func All(settings *cli.EnvSettings) Providers {
result := Providers{httpProvider}
pluginDownloaders, _ := collectPlugins(settings)
result = append(result, pluginDownloaders...)
return result
}

View File

@@ -0,0 +1,126 @@
/*
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"
"crypto/tls"
"io"
"net/http"
"github.com/pkg/errors"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/internal/version"
)
// HTTPGetter is the default HTTP(/S) backend handler
type HTTPGetter struct {
opts options
}
//Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&g.opts)
}
return g.get(href)
}
func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
// Set a helm specific user agent so that a repo server and metrics can
// separate helm calls from other tools interacting with repos.
req, err := http.NewRequest("GET", href, nil)
if err != nil {
return buf, err
}
req.Header.Set("User-Agent", version.GetUserAgent())
if g.opts.userAgent != "" {
req.Header.Set("User-Agent", g.opts.userAgent)
}
if g.opts.username != "" && g.opts.password != "" {
req.SetBasicAuth(g.opts.username, g.opts.password)
}
client, err := g.httpClient()
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return buf, err
}
if resp.StatusCode != 200 {
return buf, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
}
_, err = io.Copy(buf, resp.Body)
resp.Body.Close()
return buf, err
}
// NewHTTPGetter constructs a valid http/https client as a Getter
func NewHTTPGetter(options ...Option) (Getter, error) {
var client HTTPGetter
for _, opt := range options {
opt(&client.opts)
}
return &client, nil
}
func (g *HTTPGetter) httpClient() (*http.Client, error) {
transport := &http.Transport{
DisableCompression: true,
Proxy: http.ProxyFromEnvironment,
}
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile)
if err != nil {
return nil, errors.Wrap(err, "can't create TLS config for client")
}
tlsConf.BuildNameToCertificate()
sni, err := urlutil.ExtractHostname(g.opts.url)
if err != nil {
return nil, err
}
tlsConf.ServerName = sni
transport.TLSClientConfig = tlsConf
}
if g.opts.insecureSkipVerifyTLS {
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
client := &http.Client{
Transport: transport,
Timeout: g.opts.timeout,
}
return client, nil
}

View File

@@ -0,0 +1,102 @@
/*
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"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/plugin"
)
// collectPlugins scans for getter plugins.
// This will load plugins according to the cli.
func collectPlugins(settings *cli.EnvSettings) (Providers, error) {
plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil {
return nil, err
}
var result Providers
for _, plugin := range plugins {
for _, downloader := range plugin.Metadata.Downloaders {
result = append(result, Provider{
Schemes: downloader.Protocols,
New: NewPluginGetter(
downloader.Command,
settings,
plugin.Metadata.Name,
plugin.Dir,
),
})
}
}
return result, nil
}
// pluginGetter is a generic type to invoke custom downloaders,
// implemented in plugins.
type pluginGetter struct {
command string
settings *cli.EnvSettings
name string
base string
opts options
}
// Get runs downloader plugin command
func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&p.opts)
}
commands := strings.Split(p.command, " ")
argv := append(commands[1:], p.opts.certFile, p.opts.keyFile, p.opts.caFile, href)
prog := exec.Command(filepath.Join(p.base, commands[0]), argv...)
plugin.SetupPluginEnv(p.settings, p.name, p.base)
prog.Env = os.Environ()
buf := bytes.NewBuffer(nil)
prog.Stdout = buf
prog.Stderr = os.Stderr
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
return nil, errors.Errorf("plugin %q exited with error", p.command)
}
return nil, err
}
return buf, nil
}
// NewPluginGetter constructs a valid plugin getter
func NewPluginGetter(command string, settings *cli.EnvSettings, name, base string) Constructor {
return func(options ...Option) (Getter, error) {
result := &pluginGetter{
command: command,
settings: settings,
name: name,
base: base,
}
for _, opt := range options {
opt(&result.opts)
}
return result, nil
}
}

View File

@@ -0,0 +1,44 @@
// 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 helmpath calculates filesystem paths to Helm's configuration, cache and data.
package helmpath
// This helper builds paths to Helm's configuration, cache and data paths.
const lp = lazypath("helm")
// ConfigPath returns the path where Helm stores configuration.
func ConfigPath(elem ...string) string { return lp.configPath(elem...) }
// CachePath returns the path where Helm stores cached objects.
func CachePath(elem ...string) string { return lp.cachePath(elem...) }
// DataPath returns the path where Helm stores data.
func DataPath(elem ...string) string { return lp.dataPath(elem...) }
// CacheIndexFile returns the path to an index for the given named repository.
func CacheIndexFile(name string) string {
if name != "" {
name += "-"
}
return name + "index.yaml"
}
// CacheChartsFile returns the path to a text file listing all the charts
// within the given named repository.
func CacheChartsFile(name string) string {
if name != "" {
name += "-"
}
return name + "charts.txt"
}

View File

@@ -0,0 +1,72 @@
// 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 helmpath
import (
"os"
"path/filepath"
"helm.sh/helm/v3/pkg/helmpath/xdg"
)
const (
// CacheHomeEnvVar is the environment variable used by Helm
// for the cache directory. When no value is set a default is used.
CacheHomeEnvVar = "HELM_CACHE_HOME"
// ConfigHomeEnvVar is the environment variable used by Helm
// for the config directory. When no value is set a default is used.
ConfigHomeEnvVar = "HELM_CONFIG_HOME"
// DataHomeEnvVar is the environment variable used by Helm
// for the data directory. When no value is set a default is used.
DataHomeEnvVar = "HELM_DATA_HOME"
)
// lazypath is an lazy-loaded path buffer for the XDG base directory specification.
type lazypath string
func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string {
// There is an order to checking for a path.
// 1. See if a Helm specific environment variable has been set.
// 2. Check if an XDG environment variable is set
// 3. Fall back to a default
base := os.Getenv(helmEnvVar)
if base != "" {
return filepath.Join(base, filepath.Join(elem...))
}
base = os.Getenv(xdgEnvVar)
if base == "" {
base = defaultFn()
}
return filepath.Join(base, string(l), filepath.Join(elem...))
}
// cachePath defines the base directory relative to which user specific non-essential data files
// should be stored.
func (l lazypath) cachePath(elem ...string) string {
return l.path(CacheHomeEnvVar, xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...))
}
// configPath defines the base directory relative to which user specific configuration files should
// be stored.
func (l lazypath) configPath(elem ...string) string {
return l.path(ConfigHomeEnvVar, xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...))
}
// dataPath defines the base directory relative to which user specific data files should be stored.
func (l lazypath) dataPath(elem ...string) string {
return l.path(DataHomeEnvVar, xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...))
}

View File

@@ -0,0 +1,34 @@
// 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.
// +build darwin
package helmpath
import (
"path/filepath"
"k8s.io/client-go/util/homedir"
)
func dataHome() string {
return filepath.Join(homedir.HomeDir(), "Library")
}
func configHome() string {
return filepath.Join(homedir.HomeDir(), "Library", "Preferences")
}
func cacheHome() string {
return filepath.Join(homedir.HomeDir(), "Library", "Caches")
}

View File

@@ -0,0 +1,45 @@
// 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.
// +build !windows,!darwin
package helmpath
import (
"path/filepath"
"k8s.io/client-go/util/homedir"
)
// dataHome defines the base directory relative to which user specific data files should be stored.
//
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share is used.
func dataHome() string {
return filepath.Join(homedir.HomeDir(), ".local", "share")
}
// configHome defines the base directory relative to which user specific configuration files should
// be stored.
//
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config is used.
func configHome() string {
return filepath.Join(homedir.HomeDir(), ".config")
}
// cacheHome defines the base directory relative to which user specific non-essential data files
// should be stored.
//
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache is used.
func cacheHome() string {
return filepath.Join(homedir.HomeDir(), ".cache")
}

View File

@@ -0,0 +1,24 @@
// 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.
// +build windows
package helmpath
import "os"
func dataHome() string { return configHome() }
func configHome() string { return os.Getenv("APPDATA") }
func cacheHome() string { return os.Getenv("TEMP") }

View File

@@ -0,0 +1,34 @@
/*
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 xdg holds constants pertaining to XDG Base Directory Specification.
//
// The XDG Base Directory Specification https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// specifies the environment variables that define user-specific base directories for various categories of files.
package xdg
const (
// CacheHomeEnvVar is the environment variable used by the
// XDG base directory specification for the cache directory.
CacheHomeEnvVar = "XDG_CACHE_HOME"
// ConfigHomeEnvVar is the environment variable used by the
// XDG base directory specification for the config directory.
ConfigHomeEnvVar = "XDG_CONFIG_HOME"
// DataHomeEnvVar is the environment variable used by the
// XDG base directory specification for the data directory.
DataHomeEnvVar = "XDG_DATA_HOME"
)

View File

@@ -0,0 +1,29 @@
/*
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 plugin // import "helm.sh/helm/v3/pkg/plugin"
// Types of hooks
const (
// Install is executed after the plugin is added.
Install = "install"
// Delete is executed after the plugin is removed.
Delete = "delete"
// Update is executed after the plugin is updated.
Update = "update"
)
// Hooks is a map of events to commands.
type Hooks map[string]string

View File

@@ -0,0 +1,225 @@
/*
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 plugin // import "helm.sh/helm/v3/pkg/plugin"
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/cli"
)
const PluginFileName = "plugin.yaml"
// Downloaders represents the plugins capability if it can retrieve
// charts from special sources
type Downloaders struct {
// Protocols are the list of schemes from the charts URL.
Protocols []string `json:"protocols"`
// Command is the executable path with which the plugin performs
// the actual download for the corresponding Protocols
Command string `json:"command"`
}
// PlatformCommand represents a command for a particular operating system and architecture
type PlatformCommand struct {
OperatingSystem string `json:"os"`
Architecture string `json:"arch"`
Command string `json:"command"`
}
// Metadata describes a plugin.
//
// This is the plugin equivalent of a chart.Metadata.
type Metadata struct {
// Name is the name of the plugin
Name string `json:"name"`
// Version is a SemVer 2 version of the plugin.
Version string `json:"version"`
// Usage is the single-line usage text shown in help
Usage string `json:"usage"`
// Description is a long description shown in places like `helm help`
Description string `json:"description"`
// Command is the command, as a single string.
//
// The command will be passed through environment expansion, so env vars can
// be present in this command. Unless IgnoreFlags is set, this will
// also merge the flags passed from Helm.
//
// Note that command is not executed in a shell. To do so, we suggest
// pointing the command to a shell script.
//
// The following rules will apply to processing commands:
// - If platformCommand is present, it will be searched first
// - If both OS and Arch match the current platform, search will stop and the command will be executed
// - If OS matches and there is no more specific match, the command will be executed
// - If no OS/Arch match is found, the default command will be executed
// - If no command is present and no matches are found in platformCommand, Helm will exit with an error
PlatformCommand []PlatformCommand `json:"platformCommand"`
Command string `json:"command"`
// IgnoreFlags ignores any flags passed in from Helm
//
// For example, if the plugin is invoked as `helm --debug myplugin`, if this
// is false, `--debug` will be appended to `--command`. If this is true,
// the `--debug` flag will be discarded.
IgnoreFlags bool `json:"ignoreFlags"`
// Hooks are commands that will run on events.
Hooks Hooks
// Downloaders field is used if the plugin supply downloader mechanism
// for special protocols.
Downloaders []Downloaders `json:"downloaders"`
}
// Plugin represents a plugin.
type Plugin struct {
// Metadata is a parsed representation of a plugin.yaml
Metadata *Metadata
// Dir is the string path to the directory that holds the plugin.
Dir string
}
// The following rules will apply to processing the Plugin.PlatformCommand.Command:
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
// - If OS matches and there is no more specific match, the command will be prepared for execution
// - If no OS/Arch match is found, return nil
func getPlatformCommand(cmds []PlatformCommand) []string {
var command []string
eq := strings.EqualFold
for _, c := range cmds {
if eq(c.OperatingSystem, runtime.GOOS) {
command = strings.Split(os.ExpandEnv(c.Command), " ")
}
if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) {
return strings.Split(os.ExpandEnv(c.Command), " ")
}
}
return command
}
// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing:
// - If platformCommand is present, it will be searched first
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
// - If OS matches and there is no more specific match, the command will be prepared for execution
// - If no OS/Arch match is found, the default command will be prepared for execution
// - If no command is present and no matches are found in platformCommand, will exit with an error
//
// It merges extraArgs into any arguments supplied in the plugin. It
// returns the name of the command and an args array.
//
// The result is suitable to pass to exec.Command.
func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
var parts []string
platCmdLen := len(p.Metadata.PlatformCommand)
if platCmdLen > 0 {
parts = getPlatformCommand(p.Metadata.PlatformCommand)
}
if platCmdLen == 0 || parts == nil {
parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
}
if len(parts) == 0 || parts[0] == "" {
return "", nil, fmt.Errorf("No plugin command is applicable")
}
main := parts[0]
baseArgs := []string{}
if len(parts) > 1 {
baseArgs = parts[1:]
}
if !p.Metadata.IgnoreFlags {
baseArgs = append(baseArgs, extraArgs...)
}
return main, baseArgs, nil
}
// LoadDir loads a plugin from the given directory.
func LoadDir(dirname string) (*Plugin, error) {
data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName))
if err != nil {
return nil, err
}
plug := &Plugin{Dir: dirname}
if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
return nil, err
}
return plug, nil
}
// LoadAll loads all plugins found beneath the base directory.
//
// This scans only one directory level.
func LoadAll(basedir string) ([]*Plugin, error) {
plugins := []*Plugin{}
// We want basedir/*/plugin.yaml
scanpath := filepath.Join(basedir, "*", PluginFileName)
matches, err := filepath.Glob(scanpath)
if err != nil {
return plugins, err
}
if matches == nil {
return plugins, nil
}
for _, yaml := range matches {
dir := filepath.Dir(yaml)
p, err := LoadDir(dir)
if err != nil {
return plugins, err
}
plugins = append(plugins, p)
}
return plugins, nil
}
// FindPlugins returns a list of YAML files that describe plugins.
func FindPlugins(plugdirs string) ([]*Plugin, error) {
found := []*Plugin{}
// Let's get all UNIXy and allow path separators
for _, p := range filepath.SplitList(plugdirs) {
matches, err := LoadAll(p)
if err != nil {
return matches, err
}
found = append(found, matches...)
}
return found, nil
}
// SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because
// the plugin subsystem itself needs access to the environment variables
// created here.
func SetupPluginEnv(settings *cli.EnvSettings, name, base string) {
env := settings.EnvVars()
env["HELM_PLUGIN_NAME"] = name
env["HELM_PLUGIN_DIR"] = base
for key, val := range env {
os.Setenv(key, val)
}
}

View File

@@ -0,0 +1,37 @@
/*
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 provenance provides tools for establishing the authenticity of a chart.
In Helm, provenance is established via several factors. The primary factor is the
cryptographic signature of a chart. Chart authors may sign charts, which in turn
provide the necessary metadata to ensure the integrity of the chart file, the
Chart.yaml, and the referenced Docker images.
A provenance file is clear-signed. This provides cryptographic verification that
a particular block of information (Chart.yaml, archive file, images) have not
been tampered with or altered. To learn more, read the GnuPG documentation on
clear signatures:
https://www.gnupg.org/gph/en/manual/x135.html
The cryptography used by Helm should be compatible with OpenGPG. For example,
you should be able to verify a signature by importing the desired public key
and using `gpg --verify`, `keybase pgp verify`, or similar:
$ gpg --verify some.sig
gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762
gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>" [ultimate]
*/
package provenance // import "helm.sh/helm/v3/pkg/provenance"

View File

@@ -0,0 +1,409 @@
/*
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 provenance
import (
"bytes"
"crypto"
"encoding/hex"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/clearsign"
"golang.org/x/crypto/openpgp/packet"
"sigs.k8s.io/yaml"
hapi "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)
var defaultPGPConfig = packet.Config{
DefaultHash: crypto.SHA512,
}
// SumCollection represents a collection of file and image checksums.
//
// Files are of the form:
// FILENAME: "sha256:SUM"
// Images are of the form:
// "IMAGE:TAG": "sha256:SUM"
// Docker optionally supports sha512, and if this is the case, the hash marker
// will be 'sha512' instead of 'sha256'.
type SumCollection struct {
Files map[string]string `json:"files"`
Images map[string]string `json:"images,omitempty"`
}
// Verification contains information about a verification operation.
type Verification struct {
// SignedBy contains the entity that signed a chart.
SignedBy *openpgp.Entity
// FileHash is the hash, prepended with the scheme, for the file that was verified.
FileHash string
// FileName is the name of the file that FileHash verifies.
FileName string
}
// Signatory signs things.
//
// Signatories can be constructed from a PGP private key file using NewFromFiles
// or they can be constructed manually by setting the Entity to a valid
// PGP entity.
//
// The same Signatory can be used to sign or validate multiple charts.
type Signatory struct {
// The signatory for this instance of Helm. This is used for signing.
Entity *openpgp.Entity
// The keyring for this instance of Helm. This is used for verification.
KeyRing openpgp.EntityList
}
// NewFromFiles constructs a new Signatory from the PGP key in the given filename.
//
// This will emit an error if it cannot find a valid GPG keyfile (entity) at the
// given location.
//
// Note that the keyfile may have just a public key, just a private key, or
// both. The Signatory methods may have different requirements of the keys. For
// example, ClearSign must have a valid `openpgp.Entity.PrivateKey` before it
// can sign something.
func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) {
e, err := loadKey(keyfile)
if err != nil {
return nil, err
}
ring, err := loadKeyRing(keyringfile)
if err != nil {
return nil, err
}
return &Signatory{
Entity: e,
KeyRing: ring,
}, nil
}
// NewFromKeyring reads a keyring file and creates a Signatory.
//
// If id is not the empty string, this will also try to find an Entity in the
// keyring whose name matches, and set that as the signing entity. It will return
// an error if the id is not empty and also not found.
func NewFromKeyring(keyringfile, id string) (*Signatory, error) {
ring, err := loadKeyRing(keyringfile)
if err != nil {
return nil, err
}
s := &Signatory{KeyRing: ring}
// If the ID is empty, we can return now.
if id == "" {
return s, nil
}
// We're gonna go all GnuPG on this and look for a string that _contains_. If
// two or more keys contain the string and none are a direct match, we error
// out.
var candidate *openpgp.Entity
vague := false
for _, e := range ring {
for n := range e.Identities {
if n == id {
s.Entity = e
return s, nil
}
if strings.Contains(n, id) {
if candidate != nil {
vague = true
}
candidate = e
}
}
}
if vague {
return s, errors.Errorf("more than one key contain the id %q", id)
}
s.Entity = candidate
return s, nil
}
// PassphraseFetcher returns a passphrase for decrypting keys.
//
// This is used as a callback to read a passphrase from some other location. The
// given name is the Name field on the key, typically of the form:
//
// USER_NAME (COMMENT) <EMAIL>
type PassphraseFetcher func(name string) ([]byte, error)
// DecryptKey decrypts a private key in the Signatory.
//
// If the key is not encrypted, this will return without error.
//
// If the key does not exist, this will return an error.
//
// If the key exists, but cannot be unlocked with the passphrase returned by
// the PassphraseFetcher, this will return an error.
//
// If the key is successfully unlocked, it will return nil.
func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
if s.Entity == nil {
return errors.New("private key not found")
} else if s.Entity.PrivateKey == nil {
return errors.New("provided key is not a private key. Try providing a keyring with secret keys")
}
// Nothing else to do if key is not encrypted.
if !s.Entity.PrivateKey.Encrypted {
return nil
}
fname := "Unknown"
for i := range s.Entity.Identities {
if i != "" {
fname = i
break
}
}
p, err := fn(fname)
if err != nil {
return err
}
return s.Entity.PrivateKey.Decrypt(p)
}
// ClearSign signs a chart with the given key.
//
// This takes the path to a chart archive file and a key, and it returns a clear signature.
//
// The Signatory must have a valid Entity.PrivateKey for this to work. If it does
// not, an error will be returned.
func (s *Signatory) ClearSign(chartpath string) (string, error) {
if s.Entity == nil {
return "", errors.New("private key not found")
} else if s.Entity.PrivateKey == nil {
return "", errors.New("provided key is not a private key. Try providing a keyring with secret keys")
}
if fi, err := os.Stat(chartpath); err != nil {
return "", err
} else if fi.IsDir() {
return "", errors.New("cannot sign a directory")
}
out := bytes.NewBuffer(nil)
b, err := messageBlock(chartpath)
if err != nil {
return "", nil
}
// Sign the buffer
w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig)
if err != nil {
return "", err
}
_, err = io.Copy(w, b)
w.Close()
return out.String(), err
}
// Verify checks a signature and verifies that it is legit for a chart.
func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) {
ver := &Verification{}
for _, fname := range []string{chartpath, sigpath} {
if fi, err := os.Stat(fname); err != nil {
return ver, err
} else if fi.IsDir() {
return ver, errors.Errorf("%s cannot be a directory", fname)
}
}
// First verify the signature
sig, err := s.decodeSignature(sigpath)
if err != nil {
return ver, errors.Wrap(err, "failed to decode signature")
}
by, err := s.verifySignature(sig)
if err != nil {
return ver, err
}
ver.SignedBy = by
// Second, verify the hash of the tarball.
sum, err := DigestFile(chartpath)
if err != nil {
return ver, err
}
_, sums, err := parseMessageBlock(sig.Plaintext)
if err != nil {
return ver, err
}
sum = "sha256:" + sum
basename := filepath.Base(chartpath)
if sha, ok := sums.Files[basename]; !ok {
return ver, errors.Errorf("provenance does not contain a SHA for a file named %q", basename)
} else if sha != sum {
return ver, errors.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum)
}
ver.FileHash = sum
ver.FileName = basename
// TODO: when image signing is added, verify that here.
return ver, nil
}
func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := clearsign.Decode(data)
if block == nil {
// There was no sig in the file.
return nil, errors.New("signature block not found")
}
return block, nil
}
// verifySignature verifies that the given block is validly signed, and returns the signer.
func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) {
return openpgp.CheckDetachedSignature(
s.KeyRing,
bytes.NewBuffer(block.Bytes),
block.ArmoredSignature.Body,
)
}
func messageBlock(chartpath string) (*bytes.Buffer, error) {
var b *bytes.Buffer
// Checksum the archive
chash, err := DigestFile(chartpath)
if err != nil {
return b, err
}
base := filepath.Base(chartpath)
sums := &SumCollection{
Files: map[string]string{
base: "sha256:" + chash,
},
}
// Load the archive into memory.
chart, err := loader.LoadFile(chartpath)
if err != nil {
return b, err
}
// Buffer a hash + checksums YAML file
data, err := yaml.Marshal(chart.Metadata)
if err != nil {
return b, err
}
// FIXME: YAML uses ---\n as a file start indicator, but this is not legal in a PGP
// clearsign block. So we use ...\n, which is the YAML document end marker.
// http://yaml.org/spec/1.2/spec.html#id2800168
b = bytes.NewBuffer(data)
b.WriteString("\n...\n")
data, err = yaml.Marshal(sums)
if err != nil {
return b, err
}
b.Write(data)
return b, nil
}
// parseMessageBlock
func parseMessageBlock(data []byte) (*hapi.Metadata, *SumCollection, error) {
// This sucks.
parts := bytes.Split(data, []byte("\n...\n"))
if len(parts) < 2 {
return nil, nil, errors.New("message block must have at least two parts")
}
md := &hapi.Metadata{}
sc := &SumCollection{}
if err := yaml.Unmarshal(parts[0], md); err != nil {
return md, sc, err
}
err := yaml.Unmarshal(parts[1], sc)
return md, sc, err
}
// loadKey loads a GPG key found at a particular path.
func loadKey(keypath string) (*openpgp.Entity, error) {
f, err := os.Open(keypath)
if err != nil {
return nil, err
}
defer f.Close()
pr := packet.NewReader(f)
return openpgp.ReadEntity(pr)
}
func loadKeyRing(ringpath string) (openpgp.EntityList, error) {
f, err := os.Open(ringpath)
if err != nil {
return nil, err
}
defer f.Close()
return openpgp.ReadKeyRing(f)
}
// DigestFile calculates a SHA256 hash (like Docker) for a given file.
//
// It takes the path to the archive file, and returns a string representation of
// the SHA256 sum.
//
// The intended use of this function is to generate a sum of a chart TGZ file.
func DigestFile(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
return Digest(f)
}
// Digest hashes a reader and returns a SHA256 digest.
//
// Helm uses SHA256 as its default hash for all non-cryptographic applications.
func Digest(in io.Reader) (string, error) {
hash := crypto.SHA256.New()
if _, err := io.Copy(hash, in); err != nil {
return "", nil
}
return hex.EncodeToString(hash.Sum(nil)), nil
}

View File

@@ -0,0 +1,285 @@
/*
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 repo // import "helm.sh/helm/v3/pkg/repo"
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance"
)
// Entry represents a collection of parameters for chart repository
type Entry struct {
Name string `json:"name"`
URL string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
CAFile string `json:"caFile"`
InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"`
}
// ChartRepository represents a chart repository
type ChartRepository struct {
Config *Entry
ChartPaths []string
IndexFile *IndexFile
Client getter.Getter
CachePath string
}
// NewChartRepository constructs ChartRepository
func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) {
u, err := url.Parse(cfg.URL)
if err != nil {
return nil, errors.Errorf("invalid chart URL format: %s", cfg.URL)
}
client, err := getters.ByScheme(u.Scheme)
if err != nil {
return nil, errors.Errorf("could not find protocol handler for: %s", u.Scheme)
}
return &ChartRepository{
Config: cfg,
IndexFile: NewIndexFile(),
Client: client,
CachePath: helmpath.CachePath("repository"),
}, nil
}
// Load loads a directory of charts as if it were a repository.
//
// It requires the presence of an index.yaml file in the directory.
func (r *ChartRepository) Load() error {
dirInfo, err := os.Stat(r.Config.Name)
if err != nil {
return err
}
if !dirInfo.IsDir() {
return errors.Errorf("%q is not a directory", r.Config.Name)
}
// FIXME: Why are we recursively walking directories?
// FIXME: Why are we not reading the repositories.yaml to figure out
// what repos to use?
filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error {
if !f.IsDir() {
if strings.Contains(f.Name(), "-index.yaml") {
i, err := LoadIndexFile(path)
if err != nil {
return nil
}
r.IndexFile = i
} else if strings.HasSuffix(f.Name(), ".tgz") {
r.ChartPaths = append(r.ChartPaths, path)
}
}
return nil
})
return nil
}
// DownloadIndexFile fetches the index from a repository.
func (r *ChartRepository) DownloadIndexFile() (string, error) {
parsedURL, err := url.Parse(r.Config.URL)
if err != nil {
return "", err
}
parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml")
parsedURL.Path = path.Join(parsedURL.Path, "index.yaml")
indexURL := parsedURL.String()
// TODO add user-agent
resp, err := r.Client.Get(indexURL,
getter.WithURL(r.Config.URL),
getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify),
getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile),
getter.WithBasicAuth(r.Config.Username, r.Config.Password),
)
if err != nil {
return "", err
}
index, err := ioutil.ReadAll(resp)
if err != nil {
return "", err
}
indexFile, err := loadIndex(index)
if err != nil {
return "", err
}
// Create the chart list file in the cache directory
var charts strings.Builder
for name := range indexFile.Entries {
fmt.Fprintln(&charts, name)
}
chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))
os.MkdirAll(filepath.Dir(chartsFile), 0755)
ioutil.WriteFile(chartsFile, []byte(charts.String()), 0644)
// Create the index file in the cache directory
fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))
os.MkdirAll(filepath.Dir(fname), 0755)
return fname, ioutil.WriteFile(fname, index, 0644)
}
// Index generates an index for the chart repository and writes an index.yaml file.
func (r *ChartRepository) Index() error {
err := r.generateIndex()
if err != nil {
return err
}
return r.saveIndexFile()
}
func (r *ChartRepository) saveIndexFile() error {
index, err := yaml.Marshal(r.IndexFile)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
}
func (r *ChartRepository) generateIndex() error {
for _, path := range r.ChartPaths {
ch, err := loader.Load(path)
if err != nil {
return err
}
digest, err := provenance.DigestFile(path)
if err != nil {
return err
}
if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) {
r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
}
// TODO: If a chart exists, but has a different Digest, should we error?
}
r.IndexFile.SortEntries()
return nil
}
// FindChartInRepoURL finds chart in chart repository pointed by repoURL
// without adding repo to repositories
func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters)
}
// FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL
// without adding repo to repositories, like FindChartInRepoURL,
// but it also receives credentials for the chart repository.
func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
// Download and write the index file to a temporary location
buf := make([]byte, 20)
rand.Read(buf)
name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-")
c := Entry{
URL: repoURL,
Username: username,
Password: password,
CertFile: certFile,
KeyFile: keyFile,
CAFile: caFile,
Name: name,
}
r, err := NewChartRepository(&c, getters)
if err != nil {
return "", err
}
idx, err := r.DownloadIndexFile()
if err != nil {
return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
}
// Read the index file for the repository to get chart information and return chart URL
repoIndex, err := LoadIndexFile(idx)
if err != nil {
return "", err
}
errMsg := fmt.Sprintf("chart %q", chartName)
if chartVersion != "" {
errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
}
cv, err := repoIndex.Get(chartName, chartVersion)
if err != nil {
return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL)
}
if len(cv.URLs) == 0 {
return "", errors.Errorf("%s has no downloadable URLs", errMsg)
}
chartURL := cv.URLs[0]
absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL)
if err != nil {
return "", errors.Wrap(err, "failed to make chart URL absolute")
}
return absoluteChartURL, nil
}
// ResolveReferenceURL resolves refURL relative to baseURL.
// If refURL is absolute, it simply returns refURL.
func ResolveReferenceURL(baseURL, refURL string) (string, error) {
parsedBaseURL, err := url.Parse(baseURL)
if err != nil {
return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
}
parsedRefURL, err := url.Parse(refURL)
if err != nil {
return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
}
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
}
func (e *Entry) String() string {
buf, err := json.Marshal(e)
if err != nil {
log.Panic(err)
}
return string(buf)
}

93
vendor/helm.sh/helm/v3/pkg/repo/doc.go vendored Normal file
View File

@@ -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 repo implements the Helm Chart Repository.
A chart repository is an HTTP server that provides information on charts. A local
repository cache is an on-disk representation of a chart repository.
There are two important file formats for chart repositories.
The first is the 'index.yaml' format, which is expressed like this:
apiVersion: v1
entries:
frobnitz:
- created: 2016-09-29T12:14:34.830161306-06:00
description: This is a frobnitz.
digest: 587bd19a9bd9d2bc4a6d25ab91c8c8e7042c47b4ac246e37bf8e1e74386190f4
home: http://example.com
keywords:
- frobnitz
- sprocket
- dodad
maintainers:
- email: helm@example.com
name: The Helm Team
- email: nobody@example.com
name: Someone Else
name: frobnitz
urls:
- http://example-charts.com/testdata/repository/frobnitz-1.2.3.tgz
version: 1.2.3
sprocket:
- created: 2016-09-29T12:14:34.830507606-06:00
description: This is a sprocket"
digest: 8505ff813c39502cc849a38e1e4a8ac24b8e6e1dcea88f4c34ad9b7439685ae6
home: http://example.com
keywords:
- frobnitz
- sprocket
- dodad
maintainers:
- email: helm@example.com
name: The Helm Team
- email: nobody@example.com
name: Someone Else
name: sprocket
urls:
- http://example-charts.com/testdata/repository/sprocket-1.2.0.tgz
version: 1.2.0
generated: 2016-09-29T12:14:34.829721375-06:00
An index.yaml file contains the necessary descriptive information about what
charts are available in a repository, and how to get them.
The second file format is the repositories.yaml file format. This file is for
facilitating local cached copies of one or more chart repositories.
The format of a repository.yaml file is:
apiVersion: v1
generated: TIMESTAMP
repositories:
- name: stable
url: http://example.com/charts
cache: stable-index.yaml
- name: incubator
url: http://example.com/incubator
cache: incubator-index.yaml
This file maps three bits of information about a repository:
- The name the user uses to refer to it
- The fully qualified URL to the repository (index.yaml will be appended)
- The name of the local cachefile
The format for both files was changed after Helm v2.0.0-Alpha.4. Helm is not
backwards compatible with those earlier versions.
*/
package repo

292
vendor/helm.sh/helm/v3/pkg/repo/index.go vendored Normal file
View File

@@ -0,0 +1,292 @@
/*
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 repo
import (
"bytes"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/fileutil"
"helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/provenance"
)
var indexPath = "index.yaml"
// APIVersionV1 is the v1 API version for index and repository files.
const APIVersionV1 = "v1"
var (
// ErrNoAPIVersion indicates that an API version was not specified.
ErrNoAPIVersion = errors.New("no API version specified")
// ErrNoChartVersion indicates that a chart with the given version is not found.
ErrNoChartVersion = errors.New("no chart version found")
// ErrNoChartName indicates that a chart with the given name is not found.
ErrNoChartName = errors.New("no chart name found")
)
// ChartVersions is a list of versioned chart references.
// Implements a sorter on Version.
type ChartVersions []*ChartVersion
// Len returns the length.
func (c ChartVersions) Len() int { return len(c) }
// Swap swaps the position of two items in the versions slice.
func (c ChartVersions) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
// Less returns true if the version of entry a is less than the version of entry b.
func (c ChartVersions) Less(a, b int) bool {
// Failed parse pushes to the back.
i, err := semver.NewVersion(c[a].Version)
if err != nil {
return true
}
j, err := semver.NewVersion(c[b].Version)
if err != nil {
return false
}
return i.LessThan(j)
}
// IndexFile represents the index file in a chart repository
type IndexFile struct {
APIVersion string `json:"apiVersion"`
Generated time.Time `json:"generated"`
Entries map[string]ChartVersions `json:"entries"`
PublicKeys []string `json:"publicKeys,omitempty"`
}
// NewIndexFile initializes an index.
func NewIndexFile() *IndexFile {
return &IndexFile{
APIVersion: APIVersionV1,
Generated: time.Now(),
Entries: map[string]ChartVersions{},
PublicKeys: []string{},
}
}
// LoadIndexFile takes a file at the given path and returns an IndexFile object
func LoadIndexFile(path string) (*IndexFile, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return loadIndex(b)
}
// Add adds a file to the index
// This can leave the index in an unsorted state
func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) {
u := filename
if baseURL != "" {
var err error
_, file := filepath.Split(filename)
u, err = urlutil.URLJoin(baseURL, file)
if err != nil {
u = path.Join(baseURL, file)
}
}
cr := &ChartVersion{
URLs: []string{u},
Metadata: md,
Digest: digest,
Created: time.Now(),
}
if ee, ok := i.Entries[md.Name]; !ok {
i.Entries[md.Name] = ChartVersions{cr}
} else {
i.Entries[md.Name] = append(ee, cr)
}
}
// Has returns true if the index has an entry for a chart with the given name and exact version.
func (i IndexFile) Has(name, version string) bool {
_, err := i.Get(name, version)
return err == nil
}
// SortEntries sorts the entries by version in descending order.
//
// In canonical form, the individual version records should be sorted so that
// the most recent release for every version is in the 0th slot in the
// Entries.ChartVersions array. That way, tooling can predict the newest
// version without needing to parse SemVers.
func (i IndexFile) SortEntries() {
for _, versions := range i.Entries {
sort.Sort(sort.Reverse(versions))
}
}
// Get returns the ChartVersion for the given name.
//
// If version is empty, this will return the chart with the latest stable version,
// prerelease versions will be skipped.
func (i IndexFile) Get(name, version string) (*ChartVersion, error) {
vs, ok := i.Entries[name]
if !ok {
return nil, ErrNoChartName
}
if len(vs) == 0 {
return nil, ErrNoChartVersion
}
var constraint *semver.Constraints
if version == "" {
constraint, _ = semver.NewConstraint("*")
} else {
var err error
constraint, err = semver.NewConstraint(version)
if err != nil {
return nil, err
}
}
// when customer input exact version, check whether have exact match one first
if len(version) != 0 {
for _, ver := range vs {
if version == ver.Version {
return ver, nil
}
}
}
for _, ver := range vs {
test, err := semver.NewVersion(ver.Version)
if err != nil {
continue
}
if constraint.Check(test) {
return ver, nil
}
}
return nil, errors.Errorf("no chart version found for %s-%s", name, version)
}
// WriteFile writes an index file to the given destination path.
//
// The mode on the file is set to 'mode'.
func (i IndexFile) WriteFile(dest string, mode os.FileMode) error {
b, err := yaml.Marshal(i)
if err != nil {
return err
}
return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode)
}
// Merge merges the given index file into this index.
//
// This merges by name and version.
//
// If one of the entries in the given index does _not_ already exist, it is added.
// In all other cases, the existing record is preserved.
//
// This can leave the index in an unsorted state
func (i *IndexFile) Merge(f *IndexFile) {
for _, cvs := range f.Entries {
for _, cv := range cvs {
if !i.Has(cv.Name, cv.Version) {
e := i.Entries[cv.Name]
i.Entries[cv.Name] = append(e, cv)
}
}
}
}
// ChartVersion represents a chart entry in the IndexFile
type ChartVersion struct {
*chart.Metadata
URLs []string `json:"urls"`
Created time.Time `json:"created,omitempty"`
Removed bool `json:"removed,omitempty"`
Digest string `json:"digest,omitempty"`
}
// IndexDirectory reads a (flat) directory and generates an index.
//
// It indexes only charts that have been packaged (*.tgz).
//
// The index returned will be in an unsorted state
func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
archives, err := filepath.Glob(filepath.Join(dir, "*.tgz"))
if err != nil {
return nil, err
}
moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz"))
if err != nil {
return nil, err
}
archives = append(archives, moreArchives...)
index := NewIndexFile()
for _, arch := range archives {
fname, err := filepath.Rel(dir, arch)
if err != nil {
return index, err
}
var parentDir string
parentDir, fname = filepath.Split(fname)
// filepath.Split appends an extra slash to the end of parentDir. We want to strip that out.
parentDir = strings.TrimSuffix(parentDir, string(os.PathSeparator))
parentURL, err := urlutil.URLJoin(baseURL, parentDir)
if err != nil {
parentURL = path.Join(baseURL, parentDir)
}
c, err := loader.Load(arch)
if err != nil {
// Assume this is not a chart.
continue
}
hash, err := provenance.DigestFile(arch)
if err != nil {
return index, err
}
index.Add(c.Metadata, fname, parentURL, hash)
}
return index, nil
}
// loadIndex loads an index file and does minimal validity checking.
//
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
func loadIndex(data []byte) (*IndexFile, error) {
i := &IndexFile{}
if err := yaml.Unmarshal(data, i); err != nil {
return i, err
}
i.SortEntries()
if i.APIVersion == "" {
return i, ErrNoAPIVersion
}
return i, nil
}

123
vendor/helm.sh/helm/v3/pkg/repo/repo.go vendored Normal file
View File

@@ -0,0 +1,123 @@
/*
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 repo // import "helm.sh/helm/v3/pkg/repo"
import (
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
)
// File represents the repositories.yaml file
type File struct {
APIVersion string `json:"apiVersion"`
Generated time.Time `json:"generated"`
Repositories []*Entry `json:"repositories"`
}
// NewFile generates an empty repositories file.
//
// Generated and APIVersion are automatically set.
func NewFile() *File {
return &File{
APIVersion: APIVersionV1,
Generated: time.Now(),
Repositories: []*Entry{},
}
}
// LoadFile takes a file at the given path and returns a File object
func LoadFile(path string) (*File, error) {
r := new(File)
b, err := ioutil.ReadFile(path)
if err != nil {
return r, errors.Wrapf(err, "couldn't load repositories file (%s)", path)
}
err = yaml.Unmarshal(b, r)
return r, err
}
// Add adds one or more repo entries to a repo file.
func (r *File) Add(re ...*Entry) {
r.Repositories = append(r.Repositories, re...)
}
// Update attempts to replace one or more repo entries in a repo file. If an
// entry with the same name doesn't exist in the repo file it will add it.
func (r *File) Update(re ...*Entry) {
for _, target := range re {
r.update(target)
}
}
func (r *File) update(e *Entry) {
for j, repo := range r.Repositories {
if repo.Name == e.Name {
r.Repositories[j] = e
return
}
}
r.Add(e)
}
// Has returns true if the given name is already a repository name.
func (r *File) Has(name string) bool {
entry := r.Get(name)
return entry != nil
}
// Get returns an entry with the given name if it exists, otherwise returns nil
func (r *File) Get(name string) *Entry {
for _, entry := range r.Repositories {
if entry.Name == name {
return entry
}
}
return nil
}
// Remove removes the entry from the list of repositories.
func (r *File) Remove(name string) bool {
cp := []*Entry{}
found := false
for _, rf := range r.Repositories {
if rf.Name == name {
found = true
continue
}
cp = append(cp, rf)
}
r.Repositories = cp
return found
}
// WriteFile writes a repositories file to the given path.
func (r *File) WriteFile(path string, perm os.FileMode) error {
data, err := yaml.Marshal(r)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
return ioutil.WriteFile(path, data, perm)
}