diff --git a/pkg/action/action.go b/pkg/action/action.go deleted file mode 100644 index 82760250f..000000000 --- a/pkg/action/action.go +++ /dev/null @@ -1,421 +0,0 @@ -/* -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 action - -import ( - "bytes" - "fmt" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/discovery" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" -) - -// Timestamper is a function capable of producing a timestamp.Timestamper. -// -// By default, this is a time.Time function from the Helm time package. This can -// be overridden for testing though, so that timestamps are predictable. -var Timestamper = time.Now - -var ( - // errMissingChart indicates that a chart was not provided. - errMissingChart = errors.New("no chart provided") - // errMissingRelease indicates that a release (name) was not provided. - errMissingRelease = errors.New("no release provided") - // errInvalidRevision indicates that an invalid release revision number was provided. - errInvalidRevision = errors.New("invalid release revision") - // errPending indicates that another instance of Helm is already applying an operation on a release. - errPending = errors.New("another operation (install/upgrade/rollback) is in progress") -) - -// ValidName is a regular expression for resource names. -// -// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See -// pkg/lint/rules.validateMetadataNameFunc for the replacement. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var ValidName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - -// Configuration injects the dependencies that all actions share. -type Configuration struct { - // RESTClientGetter is an interface that loads Kubernetes clients. - RESTClientGetter RESTClientGetter - - // Releases stores records of releases. - Releases *storage.Storage - - // KubeClient is a Kubernetes API client. - KubeClient kube.Interface - - // RegistryClient is a client for working with registries - RegistryClient *registry.Client - - // Capabilities describes the capabilities of the Kubernetes cluster. - Capabilities *chartutil.Capabilities - - Log func(string, ...interface{}) -} - -// renderResources renders the templates in a chart -// -// TODO: This function is badly in need of a refactor. -// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed -// This code has to do with writing files to disk. -func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { - hs := []*release.Hook{} - b := bytes.NewBuffer(nil) - - caps, err := cfg.getCapabilities() - if err != nil { - return hs, b, "", err - } - - if ch.Metadata.KubeVersion != "" { - if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { - return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) - } - } - - var files map[string]string - var err2 error - - // A `helm template` or `helm install --dry-run` should not talk to the remote cluster. - // It will break in interesting and exotic ways because other data (e.g. discovery) - // is mocked. It is not up to the template author to decide when the user wants to - // connect to the cluster. So when the user says to dry run, respect the user's - // wishes and do not connect to the cluster. - if !dryRun && cfg.RESTClientGetter != nil { - restConfig, err := cfg.RESTClientGetter.ToRESTConfig() - if err != nil { - return hs, b, "", err - } - files, err2 = engine.RenderWithClient(ch, values, restConfig) - } else { - files, err2 = engine.Render(ch, values) - } - - if err2 != nil { - return hs, b, "", err2 - } - - // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, - // pull it out of here into a separate file so that we can actually use the output of the rendered - // text file. We have to spin through this map because the file contains path information, so we - // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip - // it in the sortHooks. - var notesBuffer bytes.Buffer - for k, v := range files { - if strings.HasSuffix(k, notesFileSuffix) { - if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) { - // If buffer contains data, add newline before adding more - if notesBuffer.Len() > 0 { - notesBuffer.WriteString("\n") - } - notesBuffer.WriteString(v) - } - delete(files, k) - } - } - notes := notesBuffer.String() - - // Sort hooks, manifests, and partials. Only hooks and manifests are returned, - // as partials are not used after renderer.Render. Empty manifests are also - // removed here. - hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder) - if err != nil { - // By catching parse errors here, we can prevent bogus releases from going - // to Kubernetes. - // - // We return the files as a big blob of data to help the user debug parser - // errors. - for name, content := range files { - if strings.TrimSpace(content) == "" { - continue - } - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) - } - return hs, b, "", err - } - - // Aggregate all valid manifests into one big doc. - fileWritten := make(map[string]bool) - - if includeCrds { - for _, crd := range ch.CRDObjects() { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:])) - } else { - err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[crd.Name] = true - } - } - } - - for _, m := range manifests { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) - } else { - newDir := outputDir - if useReleaseName { - newDir = filepath.Join(outputDir, releaseName) - } - // NOTE: We do not have to worry about the post-renderer because - // output dir is only used by `helm template`. In the next major - // release, we should move this logic to template only as it is not - // used by install or upgrade - err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[m.Name] = true - } - } - - if pr != nil { - b, err = pr.Run(b) - if err != nil { - return hs, b, notes, errors.Wrap(err, "error while running post render on files") - } - } - - return hs, b, notes, nil -} - -// RESTClientGetter gets the rest client -type RESTClientGetter interface { - ToRESTConfig() (*rest.Config, error) - ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) - ToRESTMapper() (meta.RESTMapper, error) -} - -// DebugLog sets the logger that writes debug strings -type DebugLog func(format string, v ...interface{}) - -// capabilities builds a Capabilities from discovery information. -func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { - if cfg.Capabilities != nil { - return cfg.Capabilities, nil - } - dc, err := cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return nil, errors.Wrap(err, "could not get Kubernetes discovery client") - } - // force a discovery cache invalidation to always fetch the latest server version/capabilities. - dc.Invalidate() - kubeVersion, err := dc.ServerVersion() - if err != nil { - return nil, errors.Wrap(err, "could not get server version from Kubernetes") - } - // Issue #6361: - // Client-Go emits an error when an API service is registered but unimplemented. - // We trap that error here and print a warning. But since the discovery client continues - // building the API object, it is correctly populated with all valid APIs. - // See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642 - apiVersions, err := GetVersionSet(dc) - if err != nil { - if discovery.IsGroupDiscoveryFailedError(err) { - cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err) - cfg.Log("WARNING: To fix this, kubectl delete apiservice ") - } else { - return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") - } - } - - cfg.Capabilities = &chartutil.Capabilities{ - APIVersions: apiVersions, - KubeVersion: chartutil.KubeVersion{ - Version: kubeVersion.GitVersion, - Major: kubeVersion.Major, - Minor: kubeVersion.Minor, - }, - HelmVersion: chartutil.DefaultCapabilities.HelmVersion, - } - return cfg.Capabilities, nil -} - -// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration -func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) { - conf, err := cfg.RESTClientGetter.ToRESTConfig() - if err != nil { - return nil, errors.Wrap(err, "unable to generate config for kubernetes client") - } - - return kubernetes.NewForConfig(conf) -} - -// Now generates a timestamp -// -// If the configuration has a Timestamper on it, that will be used. -// Otherwise, this will use time.Now(). -func (cfg *Configuration) Now() time.Time { - return Timestamper() -} - -func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) { - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name) - } - - if version <= 0 { - return cfg.Releases.Last(name) - } - - return cfg.Releases.Get(name, version) -} - -// GetVersionSet retrieves a set of available k8s API versions -func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) { - groups, resources, err := client.ServerGroupsAndResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - return chartutil.DefaultVersionSet, errors.Wrap(err, "could not get apiVersions from Kubernetes") - } - - // FIXME: The Kubernetes test fixture for cli appears to always return nil - // for calls to Discovery().ServerGroupsAndResources(). So in this case, we - // return the default API list. This is also a safe value to return in any - // other odd-ball case. - if len(groups) == 0 && len(resources) == 0 { - return chartutil.DefaultVersionSet, nil - } - - versionMap := make(map[string]interface{}) - versions := []string{} - - // Extract the groups - for _, g := range groups { - for _, gv := range g.Versions { - versionMap[gv.GroupVersion] = struct{}{} - } - } - - // Extract the resources - var id string - var ok bool - for _, r := range resources { - for _, rl := range r.APIResources { - - // A Kind at a GroupVersion can show up more than once. We only want - // it displayed once in the final output. - id = path.Join(r.GroupVersion, rl.Kind) - if _, ok = versionMap[id]; !ok { - versionMap[id] = struct{}{} - } - } - } - - // Convert to a form that NewVersionSet can use - for k := range versionMap { - versions = append(versions, k) - } - - return chartutil.VersionSet(versions), nil -} - -// recordRelease with an update operation in case reuse has been set. -func (cfg *Configuration) recordRelease(r *release.Release) { - if err := cfg.Releases.Update(r); err != nil { - cfg.Log("warning: Failed to update release %s: %s", r.Name, err) - } -} - -// Init initializes the action configuration -func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { - kc := kube.New(getter) - kc.Log = log - - lazyClient := &lazyClient{ - namespace: namespace, - clientFn: kc.Factory.KubernetesClientSet, - } - - var store *storage.Storage - switch helmDriver { - case "secret", "secrets", "": - d := driver.NewSecrets(newSecretClient(lazyClient)) - d.Log = log - store = storage.Init(d) - case "configmap", "configmaps": - d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) - d.Log = log - store = storage.Init(d) - case "memory": - var d *driver.Memory - if cfg.Releases != nil { - if mem, ok := cfg.Releases.Driver.(*driver.Memory); ok { - // This function can be called more than once (e.g., helm list --all-namespaces). - // If a memory driver was already initialized, re-use it but set the possibly new namespace. - // We re-use it in case some releases where already created in the existing memory driver. - d = mem - } - } - if d == nil { - d = driver.NewMemory() - } - d.SetNamespace(namespace) - store = storage.Init(d) - case "sql": - d, err := driver.NewSQL( - os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), - log, - namespace, - ) - if err != nil { - panic(fmt.Sprintf("Unable to instantiate SQL driver: %v", err)) - } - store = storage.Init(d) - default: - // Not sure what to do here. - panic("Unknown driver in HELM_DRIVER: " + helmDriver) - } - - cfg.RESTClientGetter = getter - cfg.KubeClient = kc - cfg.Releases = store - cfg.Log = log - - return nil -} diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go deleted file mode 100644 index c816c84af..000000000 --- a/pkg/action/action_test.go +++ /dev/null @@ -1,283 +0,0 @@ -/* -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 action - -import ( - "flag" - "io/ioutil" - "testing" - - fakeclientset "k8s.io/client-go/kubernetes/fake" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" -) - -var verbose = flag.Bool("test.log", false, "enable test logging") - -func actionConfigFixture(t *testing.T) *Configuration { - t.Helper() - - registryClient, err := registry.NewClient() - if err != nil { - t.Fatal(err) - } - - return &Configuration{ - Releases: storage.Init(driver.NewMemory()), - KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}}, - Capabilities: chartutil.DefaultCapabilities, - RegistryClient: registryClient, - Log: func(format string, v ...interface{}) { - t.Helper() - if *verbose { - t.Logf(format, v...) - } - }, - } -} - -var manifestWithHook = `kind: ConfigMap -metadata: - name: test-cm - annotations: - "helm.sh/hook": post-install,pre-delete,post-upgrade -data: - name: value` - -var manifestWithTestHook = `kind: Pod - metadata: - name: finding-nemo, - annotations: - "helm.sh/hook": test - spec: - containers: - - name: nemo-test - image: fake-image - cmd: fake-command - ` - -var rbacManifests = `apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: schedule-agents -rules: -- apiGroups: [""] - resources: ["pods", "pods/exec", "pods/log"] - verbs: ["*"] - ---- - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: schedule-agents - namespace: {{ default .Release.Namespace}} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: schedule-agents -subjects: -- kind: ServiceAccount - name: schedule-agents - namespace: {{ .Release.Namespace }} -` - -type chartOptions struct { - *chart.Chart -} - -type chartOption func(*chartOptions) - -func buildChart(opts ...chartOption) *chart.Chart { - c := &chartOptions{ - Chart: &chart.Chart{ - // TODO: This should be more complete. - Metadata: &chart.Metadata{ - APIVersion: "v1", - Name: "hello", - Version: "0.1.0", - }, - // This adds a basic template and hooks. - Templates: []*chart.File{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - }, - }, - } - - for _, opt := range opts { - opt(c) - } - - return c.Chart -} - -func withName(name string) chartOption { - return func(opts *chartOptions) { - opts.Metadata.Name = name - } -} - -func withSampleValues() chartOption { - values := map[string]interface{}{ - "someKey": "someValue", - "nestedKey": map[string]interface{}{ - "simpleKey": "simpleValue", - "anotherNestedKey": map[string]interface{}{ - "yetAnotherNestedKey": map[string]interface{}{ - "youReadyForAnotherNestedKey": "No", - }, - }, - }, - } - return func(opts *chartOptions) { - opts.Values = values - } -} - -func withValues(values map[string]interface{}) chartOption { - return func(opts *chartOptions) { - opts.Values = values - } -} - -func withNotes(notes string) chartOption { - return func(opts *chartOptions) { - opts.Templates = append(opts.Templates, &chart.File{ - Name: "templates/NOTES.txt", - Data: []byte(notes), - }) - } -} - -func withDependency(dependencyOpts ...chartOption) chartOption { - return func(opts *chartOptions) { - opts.AddDependency(buildChart(dependencyOpts...)) - } -} - -func withMetadataDependency(dependency chart.Dependency) chartOption { - return func(opts *chartOptions) { - opts.Metadata.Dependencies = append(opts.Metadata.Dependencies, &dependency) - } -} - -func withSampleTemplates() chartOption { - return func(opts *chartOptions) { - sampleTemplates := []*chart.File{ - // This adds basic templates and partials. - {Name: "templates/goodbye", Data: []byte("goodbye: world")}, - {Name: "templates/empty", Data: []byte("")}, - {Name: "templates/with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)}, - {Name: "templates/partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)}, - } - opts.Templates = append(opts.Templates, sampleTemplates...) - } -} - -func withSampleIncludingIncorrectTemplates() chartOption { - return func(opts *chartOptions) { - sampleTemplates := []*chart.File{ - // This adds basic templates and partials. - {Name: "templates/goodbye", Data: []byte("goodbye: world")}, - {Name: "templates/empty", Data: []byte("")}, - {Name: "templates/incorrect", Data: []byte("{{ .Values.bad.doh }}")}, - {Name: "templates/with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)}, - {Name: "templates/partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)}, - } - opts.Templates = append(opts.Templates, sampleTemplates...) - } -} - -func withMultipleManifestTemplate() chartOption { - return func(opts *chartOptions) { - sampleTemplates := []*chart.File{ - {Name: "templates/rbac", Data: []byte(rbacManifests)}, - } - opts.Templates = append(opts.Templates, sampleTemplates...) - } -} - -func withKube(version string) chartOption { - return func(opts *chartOptions) { - opts.Metadata.KubeVersion = version - } -} - -// releaseStub creates a release stub, complete with the chartStub as its chart. -func releaseStub() *release.Release { - return namedReleaseStub("angry-panda", release.StatusDeployed) -} - -func namedReleaseStub(name string, status release.Status) *release.Release { - now := time.Now() - return &release.Release{ - Name: name, - Info: &release.Info{ - FirstDeployed: now, - LastDeployed: now, - Status: status, - Description: "Named Release Stub", - }, - Chart: buildChart(withSampleTemplates()), - Config: map[string]interface{}{"name": "value"}, - Version: 1, - Hooks: []*release.Hook{ - { - Name: "test-cm", - Kind: "ConfigMap", - Path: "test-cm", - Manifest: manifestWithHook, - Events: []release.HookEvent{ - release.HookPostInstall, - release.HookPreDelete, - }, - }, - { - Name: "finding-nemo", - Kind: "Pod", - Path: "finding-nemo", - Manifest: manifestWithTestHook, - Events: []release.HookEvent{ - release.HookTest, - }, - }, - }, - } -} - -func TestGetVersionSet(t *testing.T) { - client := fakeclientset.NewSimpleClientset() - - vs, err := GetVersionSet(client.Discovery()) - if err != nil { - t.Error(err) - } - - if !vs.Has("v1") { - t.Errorf("Expected supported versions to at least include v1.") - } - if vs.Has("nosuchversion/v1") { - t.Error("Non-existent version is reported found.") - } -} diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go deleted file mode 100644 index 3265f1f17..000000000 --- a/pkg/action/dependency.go +++ /dev/null @@ -1,230 +0,0 @@ -/* -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 action - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/gosuri/uitable" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -// Dependency is the action for building a given chart's dependency tree. -// -// It provides the implementation of 'helm dependency' and its respective subcommands. -type Dependency struct { - Verify bool - Keyring string - SkipRefresh bool - ColumnWidth uint -} - -// NewDependency creates a new Dependency object with the given configuration. -func NewDependency() *Dependency { - return &Dependency{ - ColumnWidth: 80, - } -} - -// List executes 'helm dependency list'. -func (d *Dependency) List(chartpath string, out io.Writer) error { - c, err := loader.Load(chartpath) - if err != nil { - return err - } - - if c.Metadata.Dependencies == nil { - fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(chartpath, "charts")) - return nil - } - - d.printDependencies(chartpath, out, c) - fmt.Fprintln(out) - d.printMissing(chartpath, out, c.Metadata.Dependencies) - return nil -} - -// dependencyStatus returns a string describing the status of a dependency viz a viz the parent chart. -func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string { - filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") - - // If a chart is unpacked, this will check the unpacked chart's `charts/` directory for tarballs. - // Technically, this is COMPLETELY unnecessary, and should be removed in Helm 4. It is here - // to preserved backward compatibility. In Helm 2/3, there is a "difference" between - // the tgz version (which outputs "ok" if it unpacks) and the loaded version (which outputs - // "unpacked"). Early in Helm 2's history, this would have made a difference. But it no - // longer does. However, since this code shipped with Helm 3, the output must remain stable - // until Helm 4. - switch archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename)); { - case err != nil: - return "bad pattern" - case len(archives) > 1: - // See if the second part is a SemVer - found := []string{} - for _, arc := range archives { - // we need to trip the prefix dirs and the extension off. - filename = strings.TrimSuffix(filepath.Base(arc), ".tgz") - maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name)) - - if _, err := semver.StrictNewVersion(maybeVersion); err == nil { - // If the version parsed without an error, it is possibly a valid - // version. - found = append(found, arc) - } - } - - if l := len(found); l == 1 { - // If we get here, we do the same thing as in len(archives) == 1. - if r := statArchiveForStatus(found[0], dep); r != "" { - return r - } - - // Fall through and look for directories - } else if l > 1 { - return "too many matches" - } - - // The sanest thing to do here is to fall through and see if we have any directory - // matches. - - case len(archives) == 1: - archive := archives[0] - if r := statArchiveForStatus(archive, dep); r != "" { - return r - } - - } - // End unnecessary code. - - var depChart *chart.Chart - for _, item := range parent.Dependencies() { - if item.Name() == dep.Name { - depChart = item - } - } - - if depChart == nil { - return "missing" - } - - if depChart.Metadata.Version != dep.Version { - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return "invalid version" - } - - v, err := semver.NewVersion(depChart.Metadata.Version) - if err != nil { - return "invalid version" - } - - if !constraint.Check(v) { - return "wrong version" - } - } - - return "unpacked" -} - -// stat an archive and return a message if the stat is successful -// -// This is a refactor of the code originally in dependencyStatus. It is here to -// support legacy behavior, and should be removed in Helm 4. -func statArchiveForStatus(archive string, dep *chart.Dependency) string { - if _, err := os.Stat(archive); err == nil { - c, err := loader.Load(archive) - if err != nil { - return "corrupt" - } - if c.Name() != dep.Name { - return "misnamed" - } - - if c.Metadata.Version != dep.Version { - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return "invalid version" - } - - v, err := semver.NewVersion(c.Metadata.Version) - if err != nil { - return "invalid version" - } - - if !constraint.Check(v) { - return "wrong version" - } - } - return "ok" - } - return "" -} - -// printDependencies prints all of the dependencies in the yaml file. -func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) { - table := uitable.New() - table.MaxColWidth = d.ColumnWidth - table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") - for _, row := range c.Metadata.Dependencies { - table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c)) - } - fmt.Fprintln(out, table) -} - -// printMissing prints warnings about charts that are present on disk, but are -// not in Chart.yaml. -func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) { - folder := filepath.Join(chartpath, "charts/*") - files, err := filepath.Glob(folder) - if err != nil { - fmt.Fprintln(out, err) - return - } - - for _, f := range files { - fi, err := os.Stat(f) - if err != nil { - fmt.Fprintf(out, "Warning: %s\n", err) - } - // Skip anything that is not a directory and not a tgz file. - if !fi.IsDir() && filepath.Ext(f) != ".tgz" { - continue - } - c, err := loader.Load(f) - if err != nil { - fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f) - continue - } - found := false - for _, d := range reqs { - if d.Name == c.Name() { - found = true - break - } - } - if !found { - fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f) - } - } -} diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go deleted file mode 100644 index c29587aec..000000000 --- a/pkg/action/dependency_test.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -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 action - -import ( - "bytes" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" -) - -func TestList(t *testing.T) { - for _, tcase := range []struct { - chart string - golden string - }{ - { - chart: "testdata/charts/chart-with-compressed-dependencies", - golden: "output/list-compressed-deps.txt", - }, - { - chart: "testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz", - golden: "output/list-compressed-deps-tgz.txt", - }, - { - chart: "testdata/charts/chart-with-uncompressed-dependencies", - golden: "output/list-uncompressed-deps.txt", - }, - { - chart: "testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz", - golden: "output/list-uncompressed-deps-tgz.txt", - }, - { - chart: "testdata/charts/chart-missing-deps", - golden: "output/list-missing-deps.txt", - }, - } { - buf := bytes.Buffer{} - if err := NewDependency().List(tcase.chart, &buf); err != nil { - t.Fatal(err) - } - test.AssertGoldenString(t, buf.String(), tcase.golden) - } -} - -// TestDependencyStatus_Dashes is a regression test to make sure that dashes in -// chart names do not cause resolution problems. -func TestDependencyStatus_Dashes(t *testing.T) { - // Make a temp dir - dir := t.TempDir() - - chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0700); err != nil { - t.Fatal(err) - } - - // Add some fake charts - first := buildChart(withName("first-chart")) - _, err := chartutil.Save(first, chartpath) - if err != nil { - t.Fatal(err) - } - - second := buildChart(withName("first-chart-second-chart")) - _, err = chartutil.Save(second, chartpath) - if err != nil { - t.Fatal(err) - } - - dep := &chart.Dependency{ - Name: "first-chart", - Version: "0.1.0", - } - - // Now try to get the deps - stat := NewDependency().dependencyStatus(dir, dep, first) - if stat != "ok" { - t.Errorf("Unexpected status: %q", stat) - } -} - -func TestStatArchiveForStatus(t *testing.T) { - // Make a temp dir - dir := t.TempDir() - - chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0700); err != nil { - t.Fatal(err) - } - - // unsaved chart - lilith := buildChart(withName("lilith")) - - // dep referring to chart - dep := &chart.Dependency{ - Name: "lilith", - Version: "1.2.3", - } - - is := assert.New(t) - - lilithpath := filepath.Join(chartpath, "lilith-1.2.3.tgz") - is.Empty(statArchiveForStatus(lilithpath, dep)) - - // save the chart (version 0.1.0, because that is the default) - where, err := chartutil.Save(lilith, chartpath) - is.NoError(err) - - // Should get "wrong version" because we asked for 1.2.3 and got 0.1.0 - is.Equal("wrong version", statArchiveForStatus(where, dep)) - - // Break version on dep - dep = &chart.Dependency{ - Name: "lilith", - Version: "1.2.3.4.5", - } - is.Equal("invalid version", statArchiveForStatus(where, dep)) - - // Break the name - dep = &chart.Dependency{ - Name: "lilith2", - Version: "1.2.3", - } - is.Equal("misnamed", statArchiveForStatus(where, dep)) - - // Now create the right version - dep = &chart.Dependency{ - Name: "lilith", - Version: "0.1.0", - } - is.Equal("ok", statArchiveForStatus(where, dep)) -} diff --git a/pkg/action/doc.go b/pkg/action/doc.go deleted file mode 100644 index 3c91bd618..000000000 --- a/pkg/action/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -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 action contains the logic for each action that Helm can perform. -// -// This is a library for calling top-level Helm actions like 'install', -// 'upgrade', or 'list'. Actions approximately match the command line -// invocations that the Helm client uses. -package action diff --git a/pkg/action/get.go b/pkg/action/get.go deleted file mode 100644 index f44b53307..000000000 --- a/pkg/action/get.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -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 action - -import ( - "helm.sh/helm/v3/pkg/release" -) - -// Get is the action for checking a given release's information. -// -// It provides the implementation of 'helm get' and its respective subcommands (except `helm get values`). -type Get struct { - cfg *Configuration - - // Initializing Version to 0 will get the latest revision of the release. - Version int -} - -// NewGet creates a new Get object with the given configuration. -func NewGet(cfg *Configuration) *Get { - return &Get{ - cfg: cfg, - } -} - -// Run executes 'helm get' against the given release. -func (g *Get) Run(name string) (*release.Release, error) { - if err := g.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - return g.cfg.releaseContent(name, g.Version) -} diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go deleted file mode 100644 index 9c32db213..000000000 --- a/pkg/action/get_values.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -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 action - -import ( - "helm.sh/helm/v3/pkg/chartutil" -) - -// GetValues is the action for checking a given release's values. -// -// It provides the implementation of 'helm get values'. -type GetValues struct { - cfg *Configuration - - Version int - AllValues bool -} - -// NewGetValues creates a new GetValues object with the given configuration. -func NewGetValues(cfg *Configuration) *GetValues { - return &GetValues{ - cfg: cfg, - } -} - -// Run executes 'helm get values' against the given release. -func (g *GetValues) Run(name string) (map[string]interface{}, error) { - if err := g.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - rel, err := g.cfg.releaseContent(name, g.Version) - if err != nil { - return nil, err - } - - // If the user wants all values, compute the values and return. - if g.AllValues { - cfg, err := chartutil.CoalesceValues(rel.Chart, rel.Config) - if err != nil { - return nil, err - } - return cfg, nil - } - return rel.Config, nil -} diff --git a/pkg/action/history.go b/pkg/action/history.go deleted file mode 100644 index 0430aaf7a..000000000 --- a/pkg/action/history.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -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 action - -import ( - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -// History is the action for checking the release's ledger. -// -// It provides the implementation of 'helm history'. -// It returns all the revisions for a specific release. -// To list up to one revision of every release in one specific, or in all, -// namespaces, see the List action. -type History struct { - cfg *Configuration - - Max int - Version int -} - -// NewHistory creates a new History object with the given configuration. -func NewHistory(cfg *Configuration) *History { - return &History{ - cfg: cfg, - } -} - -// Run executes 'helm history' against the given release. -func (h *History) Run(name string) ([]*release.Release, error) { - if err := h.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("release name is invalid: %s", name) - } - - h.cfg.Log("getting history for release %s", name) - return h.cfg.Releases.History(name) -} diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go deleted file mode 100644 index 40c1ffdb6..000000000 --- a/pkg/action/hooks.go +++ /dev/null @@ -1,151 +0,0 @@ -/* -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 action - -import ( - "bytes" - "sort" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { - executingHooks := []*release.Hook{} - - for _, h := range rl.Hooks { - for _, e := range h.Events { - if e == hook { - executingHooks = append(executingHooks, h) - } - } - } - - // hooke are pre-ordered by kind, so keep order stable - sort.Stable(hookByWeight(executingHooks)) - - for _, h := range executingHooks { - // Set default delete policy to before-hook-creation - if h.DeletePolicies == nil || len(h.DeletePolicies) == 0 { - // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion - // resources. For all other resource types update in place if a - // resource with the same name already exists and is owned by the - // current release. - h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} - } - - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation); err != nil { - return err - } - - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) - if err != nil { - return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) - } - - // Record the time at which the hook was applied to the cluster - h.LastRun = release.HookExecution{ - StartedAt: helmtime.Now(), - Phase: release.HookPhaseRunning, - } - cfg.recordRelease(rl) - - // As long as the implementation of WatchUntilReady does not panic, HookPhaseFailed or HookPhaseSucceeded - // should always be set by this function. If we fail to do that for any reason, then HookPhaseUnknown is - // the most appropriate value to surface. - h.LastRun.Phase = release.HookPhaseUnknown - - // Create hook resources - if _, err := cfg.KubeClient.Create(resources); err != nil { - h.LastRun.CompletedAt = helmtime.Now() - h.LastRun.Phase = release.HookPhaseFailed - return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) - } - - // Watch hook resources until they have completed - err = cfg.KubeClient.WatchUntilReady(resources, timeout) - // Note the time of success/failure - h.LastRun.CompletedAt = helmtime.Now() - // Mark hook as succeeded or failed - if err != nil { - h.LastRun.Phase = release.HookPhaseFailed - // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted - // under failed condition. If so, then clear the corresponding resource object in the hook - if err := cfg.deleteHookByPolicy(h, release.HookFailed); err != nil { - return err - } - return err - } - h.LastRun.Phase = release.HookPhaseSucceeded - } - - // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted - // under succeeded condition. If so, then clear the corresponding resource object in each hook - for _, h := range executingHooks { - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded); err != nil { - return err - } - } - - return nil -} - -// hookByWeight is a sorter for hooks -type hookByWeight []*release.Hook - -func (x hookByWeight) Len() int { return len(x) } -func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x hookByWeight) Less(i, j int) bool { - if x[i].Weight == x[j].Weight { - return x[i].Name < x[j].Name - } - return x[i].Weight < x[j].Weight -} - -// deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy) error { - // Never delete CustomResourceDefinitions; this could cause lots of - // cascading garbage collection. - if h.Kind == "CustomResourceDefinition" { - return nil - } - if hookHasDeletePolicy(h, policy) { - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) - if err != nil { - return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path) - } - _, errs := cfg.KubeClient.Delete(resources) - if len(errs) > 0 { - return errors.New(joinErrors(errs)) - } - } - return nil -} - -// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices -// supported by helm. If so, mark the hook as one should be deleted. -func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { - for _, v := range h.DeletePolicies { - if policy == v { - return true - } - } - return false -} diff --git a/pkg/action/install.go b/pkg/action/install.go deleted file mode 100644 index fa5508234..000000000 --- a/pkg/action/install.go +++ /dev/null @@ -1,767 +0,0 @@ -/* -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 action - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "net/url" - "os" - "path" - "path/filepath" - "strings" - "sync" - "text/template" - "time" - - "github.com/Masterminds/sprig/v3" - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/resource" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/kube" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" -) - -// NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine -// but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually -// wants to see this file after rendering in the status command. However, it must be a suffix -// since there can be filepath in front of it. -const notesFileSuffix = "NOTES.txt" - -const defaultDirectoryPermission = 0755 - -// Install performs an installation operation. -type Install struct { - cfg *Configuration - - ChartPathOptions - - ClientOnly bool - CreateNamespace bool - DryRun bool - DisableHooks bool - Replace bool - Wait bool - WaitForJobs bool - Devel bool - DependencyUpdate bool - Timeout time.Duration - Namespace string - ReleaseName string - GenerateName bool - NameTemplate string - Description string - OutputDir string - Atomic bool - SkipCRDs bool - SubNotes bool - DisableOpenAPIValidation bool - IncludeCRDs bool - // KubeVersion allows specifying a custom kubernetes version to use and - // APIVersions allows a manual set of supported API Versions to be passed - // (for things like templating). These are ignored if ClientOnly is false - KubeVersion *chartutil.KubeVersion - APIVersions chartutil.VersionSet - // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false - IsUpgrade bool - // Used by helm template to add the release as part of OutputDir path - // OutputDir/ - UseReleaseName bool - PostRenderer postrender.PostRenderer - // Lock to control raceconditions when the process receives a SIGTERM - Lock sync.Mutex -} - -// ChartPathOptions captures common options used for controlling chart paths -type ChartPathOptions struct { - CaFile string // --ca-file - CertFile string // --cert-file - KeyFile string // --key-file - InsecureSkipTLSverify bool // --insecure-skip-verify - Keyring string // --keyring - Password string // --password - PassCredentialsAll bool // --pass-credentials - RepoURL string // --repo - Username string // --username - Verify bool // --verify - Version string // --version - - // registryClient provides a registry client but is not added with - // options from a flag - registryClient *registry.Client -} - -// NewInstall creates a new Install object with the given configuration. -func NewInstall(cfg *Configuration) *Install { - in := &Install{ - cfg: cfg, - } - in.ChartPathOptions.registryClient = cfg.RegistryClient - - return in -} - -func (i *Install) installCRDs(crds []chart.CRD) error { - // We do these one file at a time in the order they were read. - totalItems := []*resource.Info{} - for _, obj := range crds { - // Read in the resources - res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) - if err != nil { - return errors.Wrapf(err, "failed to install CRD %s", obj.Name) - } - - // Send them to Kube - if _, err := i.cfg.KubeClient.Create(res); err != nil { - // If the error is CRD already exists, continue. - if apierrors.IsAlreadyExists(err) { - crdName := res[0].Name - i.cfg.Log("CRD %s is already present. Skipping.", crdName) - continue - } - return errors.Wrapf(err, "failed to install CRD %s", obj.Name) - } - totalItems = append(totalItems, res...) - } - if len(totalItems) > 0 { - // Invalidate the local cache, since it will not have the new CRDs - // present. - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return err - } - i.cfg.Log("Clearing discovery cache") - discoveryClient.Invalidate() - // Give time for the CRD to be recognized. - - if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { - return err - } - - // Make sure to force a rebuild of the cache. - discoveryClient.ServerGroups() - } - return nil -} - -// Run executes the installation -// -// If DryRun is set to true, this will prepare the release, but not install it - -func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - ctx := context.Background() - return i.RunWithContext(ctx, chrt, vals) -} - -// Run executes the installation with Context -func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) - if !i.ClientOnly { - if err := i.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - } - - if err := i.availableName(); err != nil { - return nil, err - } - - if err := chartutil.ProcessDependencies(chrt, vals); err != nil { - return nil, err - } - - // Pre-install anything in the crd/ directory. We do this before Helm - // contacts the upstream server and builds the capabilities object. - if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { - // On dry run, bail here - if i.DryRun { - i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") - } else if err := i.installCRDs(crds); err != nil { - return nil, err - } - } - - if i.ClientOnly { - // Add mock objects in here so it doesn't use Kube API server - // NOTE(bacongobbler): used for `helm template` - i.cfg.Capabilities = chartutil.DefaultCapabilities.Copy() - if i.KubeVersion != nil { - i.cfg.Capabilities.KubeVersion = *i.KubeVersion - } - i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) - i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} - - mem := driver.NewMemory() - mem.SetNamespace(i.Namespace) - i.cfg.Releases = storage.Init(mem) - } else if !i.ClientOnly && len(i.APIVersions) > 0 { - i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") - } - - // Make sure if Atomic is set, that wait is set as well. This makes it so - // the user doesn't have to specify both - i.Wait = i.Wait || i.Atomic - - caps, err := i.cfg.getCapabilities() - if err != nil { - return nil, err - } - - // special case for helm template --is-upgrade - isUpgrade := i.IsUpgrade && i.DryRun - options := chartutil.ReleaseOptions{ - Name: i.ReleaseName, - Namespace: i.Namespace, - Revision: 1, - IsInstall: !isUpgrade, - IsUpgrade: isUpgrade, - } - valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) - if err != nil { - return nil, err - } - - rel := i.createRelease(chrt, vals) - - var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) - // Even for errors, attach this if available - if manifestDoc != nil { - rel.Manifest = manifestDoc.String() - } - // Check error from render - if err != nil { - rel.SetStatus(release.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error())) - // Return a release with partial data so that the client can show debugging information. - return rel, err - } - - // Mark this release as in-progress - rel.SetStatus(release.StatusPendingInstall, "Initial install underway") - - var toBeAdopted kube.ResourceList - resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) - if err != nil { - return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") - } - - // It is safe to use "force" here because these are resources currently rendered by the chart. - err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) - if err != nil { - return nil, err - } - - // Install requires an extra validation step of checking that resources - // don't already exist before we actually create resources. If we continue - // forward and create the release object with resources that already exist, - // we'll end up in a state where we will delete those resources upon - // deleting the release because the manifest will be pointing at that - // resource - if !i.ClientOnly && !isUpgrade && len(resources) > 0 { - toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) - if err != nil { - return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") - } - } - - // Bail out here if it is a dry run - if i.DryRun { - rel.Info.Description = "Dry run complete" - return rel, nil - } - - if i.CreateNamespace { - ns := &v1.Namespace{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Namespace", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: i.Namespace, - Labels: map[string]string{ - "name": i.Namespace, - }, - }, - } - buf, err := yaml.Marshal(ns) - if err != nil { - return nil, err - } - resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) - if err != nil { - return nil, err - } - if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { - return nil, err - } - } - - // If Replace is true, we need to supercede the last release. - if i.Replace { - if err := i.replaceRelease(rel); err != nil { - return nil, err - } - } - - // Store the release in history before continuing (new in Helm 3). We always know - // that this is a create operation. - if err := i.cfg.Releases.Create(rel); err != nil { - // We could try to recover gracefully here, but since nothing has been installed - // yet, this is probably safer than trying to continue when we know storage is - // not working. - return rel, err - } - rChan := make(chan resultMessage) - doneChan := make(chan struct{}) - defer close(doneChan) - go i.performInstall(rChan, rel, toBeAdopted, resources) - go i.handleContext(ctx, rChan, doneChan, rel) - result := <-rChan - //start preformInstall go routine - return result.r, result.e -} - -func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) { - - // pre-install hooks - if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { - i.reportToRun(c, rel, fmt.Errorf("failed pre-install: %s", err)) - return - } - } - - // At this point, we can do the install. Note that before we were detecting whether to - // do an update, but it's not clear whether we WANT to do an update if the re-use is set - // to true, since that is basically an upgrade operation. - if len(toBeAdopted) == 0 && len(resources) > 0 { - if _, err := i.cfg.KubeClient.Create(resources); err != nil { - i.reportToRun(c, rel, err) - return - } - } else if len(resources) > 0 { - if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { - i.reportToRun(c, rel, err) - return - } - } - - if i.Wait { - if i.WaitForJobs { - if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil { - i.reportToRun(c, rel, err) - return - } - } else { - if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil { - i.reportToRun(c, rel, err) - return - } - } - } - - if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { - i.reportToRun(c, rel, fmt.Errorf("failed post-install: %s", err)) - return - } - } - - if len(i.Description) > 0 { - rel.SetStatus(release.StatusDeployed, i.Description) - } else { - rel.SetStatus(release.StatusDeployed, "Install complete") - } - - // This is a tricky case. The release has been created, but the result - // cannot be recorded. The truest thing to tell the user is that the - // release was created. However, the user will not be able to do anything - // further with this release. - // - // One possible strategy would be to do a timed retry to see if we can get - // this stored in the future. - if err := i.recordRelease(rel); err != nil { - i.cfg.Log("failed to record the release: %s", err) - } - - i.reportToRun(c, rel, nil) -} -func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, done chan struct{}, rel *release.Release) { - select { - case <-ctx.Done(): - err := ctx.Err() - i.reportToRun(c, rel, err) - case <-done: - return - } -} -func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) { - i.Lock.Lock() - if err != nil { - rel, err = i.failRelease(rel, err) - } - c <- resultMessage{r: rel, e: err} - i.Lock.Unlock() -} -func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { - rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) - if i.Atomic { - i.cfg.Log("Install failed and atomic is set, uninstalling release") - uninstall := NewUninstall(i.cfg) - uninstall.DisableHooks = i.DisableHooks - uninstall.KeepHistory = false - uninstall.Timeout = i.Timeout - if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { - return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err) - } - return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName) - } - i.recordRelease(rel) // Ignore the error, since we have another error to deal with. - return rel, err -} - -// availableName tests whether a name is available -// -// Roughly, this will return an error if name is -// -// - empty -// - too long -// - already in use, and not deleted -// - used by a deleted release, and i.Replace is false -func (i *Install) availableName() error { - start := i.ReleaseName - - if err := chartutil.ValidateReleaseName(start); err != nil { - return errors.Wrapf(err, "release name %q", start) - } - if i.DryRun { - return nil - } - - h, err := i.cfg.Releases.History(start) - if err != nil || len(h) < 1 { - return nil - } - releaseutil.Reverse(h, releaseutil.SortByRevision) - rel := h[0] - - if st := rel.Info.Status; i.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) { - return nil - } - return errors.New("cannot re-use a name that is still in use") -} - -// createRelease creates a new release object -func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}) *release.Release { - ts := i.cfg.Now() - return &release.Release{ - Name: i.ReleaseName, - Namespace: i.Namespace, - Chart: chrt, - Config: rawVals, - Info: &release.Info{ - FirstDeployed: ts, - LastDeployed: ts, - Status: release.StatusUnknown, - }, - Version: 1, - } -} - -// recordRelease with an update operation in case reuse has been set. -func (i *Install) recordRelease(r *release.Release) error { - // This is a legacy function which has been reduced to a oneliner. Could probably - // refactor it out. - return i.cfg.Releases.Update(r) -} - -// replaceRelease replaces an older release with this one -// -// This allows us to re-use names by superseding an existing release with a new one -func (i *Install) replaceRelease(rel *release.Release) error { - hist, err := i.cfg.Releases.History(rel.Name) - if err != nil || len(hist) == 0 { - // No releases exist for this name, so we can return early - return nil - } - - releaseutil.Reverse(hist, releaseutil.SortByRevision) - last := hist[0] - - // Update version to the next available - rel.Version = last.Version + 1 - - // Do not change the status of a failed release. - if last.Info.Status == release.StatusFailed { - return nil - } - - // For any other status, mark it as superseded and store the old record - last.SetStatus(release.StatusSuperseded, "superseded by new release") - return i.recordRelease(last) -} - -// write the to /. controls if the file is created or content will be appended -func writeToFile(outputDir string, name string, data string, append bool) error { - outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) - - err := ensureDirectoryForFile(outfileName) - if err != nil { - return err - } - - f, err := createOrOpenFile(outfileName, append) - if err != nil { - return err - } - - defer f.Close() - - _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) - - if err != nil { - return err - } - - fmt.Printf("wrote %s\n", outfileName) - return nil -} - -func createOrOpenFile(filename string, append bool) (*os.File, error) { - if append { - return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) - } - return os.Create(filename) -} - -// check if the directory exists to create file. creates if don't exists -func ensureDirectoryForFile(file string) error { - baseDir := path.Dir(file) - _, err := os.Stat(baseDir) - if err != nil && !os.IsNotExist(err) { - return err - } - - return os.MkdirAll(baseDir, defaultDirectoryPermission) -} - -// NameAndChart returns the name and chart that should be used. -// -// This will read the flags and handle name generation if necessary. -func (i *Install) NameAndChart(args []string) (string, string, error) { - flagsNotSet := func() error { - if i.GenerateName { - return errors.New("cannot set --generate-name and also specify a name") - } - if i.NameTemplate != "" { - return errors.New("cannot set --name-template and also specify a name") - } - return nil - } - - if len(args) > 2 { - return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", ")) - } - - if len(args) == 2 { - return args[0], args[1], flagsNotSet() - } - - if i.NameTemplate != "" { - name, err := TemplateName(i.NameTemplate) - return name, args[0], err - } - - if i.ReleaseName != "" { - return i.ReleaseName, args[0], nil - } - - if !i.GenerateName { - return "", args[0], errors.New("must either provide a name or specify --generate-name") - } - - base := filepath.Base(args[0]) - if base == "." || base == "" { - base = "chart" - } - // if present, strip out the file extension from the name - if idx := strings.Index(base, "."); idx != -1 { - base = base[0:idx] - } - - return fmt.Sprintf("%s-%d", base, time.Now().Unix()), args[0], nil -} - -// TemplateName renders a name template, returning the name or an error. -func TemplateName(nameTemplate string) (string, error) { - if nameTemplate == "" { - return "", nil - } - - t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) - if err != nil { - return "", err - } - var b bytes.Buffer - if err := t.Execute(&b, nil); err != nil { - return "", err - } - - return b.String(), nil -} - -// CheckDependencies checks the dependencies for a chart. -func CheckDependencies(ch *chart.Chart, reqs []*chart.Dependency) error { - var missing []string - -OUTER: - for _, r := range reqs { - for _, d := range ch.Dependencies() { - if d.Name() == r.Name { - continue OUTER - } - } - missing = append(missing, r.Name) - } - - if len(missing) > 0 { - return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) - } - return nil -} - -// LocateChart looks for a chart directory in known places, and returns either the full path or an error. -// -// This does not ensure that the chart is well-formed; only that the requested filename exists. -// -// Order of resolution: -// - relative to current working directory -// - if path is absolute or begins with '.', error out here -// - URL -// -// If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. -func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { - // If there is no registry client and the name is in an OCI registry return - // an error and a lookup will not occur. - if registry.IsOCI(name) && c.registryClient == nil { - return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) - } - - name = strings.TrimSpace(name) - version := strings.TrimSpace(c.Version) - - if _, err := os.Stat(name); err == nil { - abs, err := filepath.Abs(name) - if err != nil { - return abs, err - } - if c.Verify { - if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil { - return "", err - } - } - return abs, nil - } - if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { - return name, errors.Errorf("path %q not found", name) - } - - dl := downloader.ChartDownloader{ - Out: os.Stdout, - Keyring: c.Keyring, - Getters: getter.All(settings), - Options: []getter.Option{ - getter.WithPassCredentialsAll(c.PassCredentialsAll), - getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), - getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), - }, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - RegistryClient: c.registryClient, - } - - if registry.IsOCI(name) { - dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient)) - } - - if c.Verify { - dl.Verify = downloader.VerifyAlways - } - if c.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version, - c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings)) - if err != nil { - return "", err - } - name = chartURL - - // Only pass the user/pass on when the user has said to or when the - // location of the chart repo and the chart are the same domain. - u1, err := url.Parse(c.RepoURL) - if err != nil { - return "", err - } - u2, err := url.Parse(chartURL) - if err != nil { - return "", err - } - - // Host on URL (returned from url.Parse) contains the port if present. - // This check ensures credentials are not passed between different - // services on different ports. - if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { - dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) - } else { - dl.Options = append(dl.Options, getter.WithBasicAuth("", "")) - } - } else { - dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) - } - - if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { - return "", err - } - - filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache) - if err != nil { - return "", err - } - - lname, err := filepath.Abs(filename) - if err != nil { - return filename, err - } - return lname, nil -} diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go deleted file mode 100644 index 45e5a2670..000000000 --- a/pkg/action/install_test.go +++ /dev/null @@ -1,719 +0,0 @@ -/* -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 action - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" - helmtime "helm.sh/helm/v3/pkg/time" -) - -type nameTemplateTestCase struct { - tpl string - expected string - expectedErrorStr string -} - -func installAction(t *testing.T) *Install { - config := actionConfigFixture(t) - instAction := NewInstall(config) - instAction.Namespace = "spaced" - instAction.ReleaseName = "test-install-release" - - return instAction -} - -func TestInstallRelease(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - instAction := installAction(t) - vals := map[string]interface{}{} - ctx, done := context.WithCancel(context.Background()) - res, err := instAction.RunWithContext(ctx, buildChart(), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - is.Equal(res.Name, "test-install-release", "Expected release name.") - is.Equal(res.Namespace, "spaced") - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - - is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) - is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal(rel.Info.Description, "Install complete") - - // Detecting previous bug where context termination after successful release - // caused release to fail. - done() - time.Sleep(time.Millisecond * 100) - lastRelease, err := instAction.cfg.Releases.Last(rel.Name) - req.NoError(err) - is.Equal(lastRelease.Info.Status, release.StatusDeployed) -} - -func TestInstallReleaseWithValues(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - userVals := map[string]interface{}{ - "nestedKey": map[string]interface{}{ - "simpleKey": "simpleValue", - }, - } - expectedUserValues := map[string]interface{}{ - "nestedKey": map[string]interface{}{ - "simpleKey": "simpleValue", - }, - } - res, err := instAction.Run(buildChart(withSampleValues()), userVals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - is.Equal(res.Name, "test-install-release", "Expected release name.") - is.Equal(res.Namespace, "spaced") - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - - is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) - is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal("Install complete", rel.Info.Description) - is.Equal(expectedUserValues, rel.Config) -} - -func TestInstallReleaseClientOnly(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ClientOnly = true - instAction.Run(buildChart(), nil) // disregard output - - is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) - is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) -} - -func TestInstallRelease_NoName(t *testing.T) { - instAction := installAction(t) - instAction.ReleaseName = "" - vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(), vals) - if err == nil { - t.Fatal("expected failure when no name is specified") - } - assert.Contains(t, err.Error(), "no name provided") -} - -func TestInstallRelease_WithNotes(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("note here")), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.Equal(res.Name, "with-notes") - is.Equal(res.Namespace, "spaced") - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) - is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal(rel.Info.Description, "Install complete") - - is.Equal(rel.Info.Notes, "note here") -} - -func TestInstallRelease_WithNotesRendered(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - - expectedNotes := fmt.Sprintf("got-%s", res.Name) - is.Equal(expectedNotes, rel.Info.Notes) - is.Equal(rel.Info.Description, "Install complete") -} - -func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { - // Regression: Make sure that the child's notes don't override the parent's - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.Equal("with-notes", rel.Name) - is.NoError(err) - is.Equal("parent", rel.Info.Notes) - is.Equal(rel.Info.Description, "Install complete") -} - -func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { - // Regression: Make sure that the child's notes don't override the parent's - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - instAction.SubNotes = true - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.Equal("with-notes", rel.Name) - is.NoError(err) - // test run can return as either 'parent\nchild' or 'child\nparent' - if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") { - t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes) - } - is.Equal(rel.Info.Description, "Install complete") -} - -func TestInstallRelease_DryRun(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DryRun = true - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withSampleTemplates()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") - is.Contains(res.Manifest, "hello: Earth") - is.NotContains(res.Manifest, "hello: {{ template \"_planet\" . }}") - is.NotContains(res.Manifest, "empty") - - _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Len(res.Hooks, 1) - is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run") - is.Equal(res.Info.Description, "Dry run complete") -} - -// Regression test for #7955: Lookup must not connect to Kubernetes on a dry-run. -func TestInstallRelease_DryRun_Lookup(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DryRun = true - vals := map[string]interface{}{} - - mockChart := buildChart(withSampleTemplates()) - mockChart.Templates = append(mockChart.Templates, &chart.File{ - Name: "templates/lookup", - Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`), - }) - - res, err := instAction.Run(mockChart, vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.Contains(res.Manifest, "goodbye: map[]") -} - -func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DryRun = true - vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) - expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" - if err == nil { - t.Fatalf("Install should fail containing error: %s", expectedErr) - } - if err != nil { - is.Contains(err.Error(), expectedErr) - } -} - -func TestInstallRelease_NoHooks(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DisableHooks = true - instAction.ReleaseName = "no-hooks" - instAction.cfg.Releases.Create(releaseStub()) - - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks") -} - -func TestInstallRelease_FailedHooks(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "failed-hooks" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WatchUntilReadyError = fmt.Errorf("Failed watch") - instAction.cfg.KubeClient = failer - - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "failed post-install") - is.Equal(release.StatusFailed, res.Info.Status) -} - -func TestInstallRelease_ReplaceRelease(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.Replace = true - - rel := releaseStub() - rel.Info.Status = release.StatusUninstalled - instAction.cfg.Releases.Create(rel) - instAction.ReleaseName = rel.Name - - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) - is.NoError(err) - - // This should have been auto-incremented - is.Equal(2, res.Version) - is.Equal(res.Name, rel.Name) - - getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version) - is.NoError(err) - is.Equal(getres.Info.Status, release.StatusDeployed) -} - -func TestInstallRelease_KubeVersion(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) - is.NoError(err) - - // This should fail for a few hundred years - instAction.ReleaseName = "should-fail" - vals = map[string]interface{}{} - _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) - is.Error(err) - is.Contains(err.Error(), "chart requires kubeVersion") -} - -func TestInstallRelease_Wait(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer - instAction.Wait = true - vals := map[string]interface{}{} - - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} -func TestInstallRelease_Wait_Interrupted(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "interrupted-release" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 10 * time.Second - instAction.cfg.KubeClient = failer - instAction.Wait = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := instAction.RunWithContext(ctx, buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "Release \"interrupted-release\" failed: context canceled") - is.Equal(res.Info.Status, release.StatusFailed) -} -func TestInstallRelease_WaitForJobs(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer - instAction.Wait = true - instAction.WaitForJobs = true - vals := map[string]interface{}{} - - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestInstallRelease_Atomic(t *testing.T) { - is := assert.New(t) - - t.Run("atomic uninstall succeeds", func(t *testing.T) { - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer - instAction.Atomic = true - vals := map[string]interface{}{} - - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "atomic") - - // Now make sure it isn't in storage any more - _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Equal(err, driver.ErrReleaseNotFound) - }) - - t.Run("atomic uninstall fails", func(t *testing.T) { - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away-with-me" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - failer.DeleteError = fmt.Errorf("uninstall fail") - instAction.cfg.KubeClient = failer - instAction.Atomic = true - vals := map[string]interface{}{} - - _, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "uninstall fail") - is.Contains(err.Error(), "an error occurred while uninstalling the release") - }) -} -func TestInstallRelease_Atomic_Interrupted(t *testing.T) { - - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "interrupted-release" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 10 * time.Second - instAction.cfg.KubeClient = failer - instAction.Atomic = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := instAction.RunWithContext(ctx, buildChart(), vals) - is.Error(err) - is.Contains(err.Error(), "context canceled") - is.Contains(err.Error(), "atomic") - is.Contains(err.Error(), "uninstalled") - - // Now make sure it isn't in storage any more - _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Equal(err, driver.ErrReleaseNotFound) - -} -func TestNameTemplate(t *testing.T) { - testCases := []nameTemplateTestCase{ - // Just a straight up nop please - { - tpl: "foobar", - expected: "foobar", - expectedErrorStr: "", - }, - // Random numbers at the end for fun & profit - { - tpl: "foobar-{{randNumeric 6}}", - expected: "foobar-[0-9]{6}$", - expectedErrorStr: "", - }, - // Random numbers in the middle for fun & profit - { - tpl: "foobar-{{randNumeric 4}}-baz", - expected: "foobar-[0-9]{4}-baz$", - expectedErrorStr: "", - }, - // No such function - { - tpl: "foobar-{{randInteger}}", - expected: "", - expectedErrorStr: "function \"randInteger\" not defined", - }, - // Invalid template - { - tpl: "foobar-{{", - expected: "", - expectedErrorStr: "template: name-template:1: unclosed action", - }, - } - - for _, tc := range testCases { - - n, err := TemplateName(tc.tpl) - if err != nil { - if tc.expectedErrorStr == "" { - t.Errorf("Was not expecting error, but got: %v", err) - continue - } - re, compErr := regexp.Compile(tc.expectedErrorStr) - if compErr != nil { - t.Errorf("Expected error string failed to compile: %v", compErr) - continue - } - if !re.MatchString(err.Error()) { - t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err) - continue - } - } - if err == nil && tc.expectedErrorStr != "" { - t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr) - } - - if tc.expected != "" { - re, err := regexp.Compile(tc.expected) - if err != nil { - t.Errorf("Expected string failed to compile: %v", err) - continue - } - if !re.MatchString(n) { - t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n) - } - } - } -} - -func TestInstallReleaseOutputDir(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - - dir := t.TempDir() - - instAction.OutputDir = dir - - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) - is.NoError(err) - - test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") - - _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) - is.True(os.IsNotExist(err)) -} - -func TestInstallOutputDirWithReleaseName(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - - dir := t.TempDir() - - instAction.OutputDir = dir - instAction.UseReleaseName = true - instAction.ReleaseName = "madra" - - newDir := filepath.Join(dir, instAction.ReleaseName) - - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/hello")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac")) - is.NoError(err) - - test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) - is.True(os.IsNotExist(err)) -} - -func TestNameAndChart(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - chartName := "./foo" - - name, chrt, err := instAction.NameAndChart([]string{chartName}) - if err != nil { - t.Fatal(err) - } - is.Equal(instAction.ReleaseName, name) - is.Equal(chartName, chrt) - - instAction.GenerateName = true - _, _, err = instAction.NameAndChart([]string{"foo", chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("cannot set --generate-name and also specify a name", err.Error()) - - instAction.GenerateName = false - instAction.NameTemplate = "{{ . }}" - _, _, err = instAction.NameAndChart([]string{"foo", chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("cannot set --name-template and also specify a name", err.Error()) - - instAction.NameTemplate = "" - instAction.ReleaseName = "" - _, _, err = instAction.NameAndChart([]string{chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("must either provide a name or specify --generate-name", err.Error()) - - instAction.NameTemplate = "" - instAction.ReleaseName = "" - _, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error()) -} - -func TestNameAndChartGenerateName(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - - instAction.ReleaseName = "" - instAction.GenerateName = true - - tests := []struct { - Name string - Chart string - ExpectedName string - }{ - { - "local filepath", - "./chart", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "dot filepath", - ".", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "empty filepath", - "", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "packaged chart", - "chart.tgz", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "packaged chart with .tar.gz extension", - "chart.tar.gz", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "packaged chart with local extension", - "./chart.tgz", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.Name, func(t *testing.T) { - t.Parallel() - - name, chrt, err := instAction.NameAndChart([]string{tc.Chart}) - if err != nil { - t.Fatal(err) - } - - is.Equal(tc.ExpectedName, name) - is.Equal(tc.Chart, chrt) - }) - } -} diff --git a/pkg/action/lazyclient.go b/pkg/action/lazyclient.go deleted file mode 100644 index 9037782bb..000000000 --- a/pkg/action/lazyclient.go +++ /dev/null @@ -1,197 +0,0 @@ -/* -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 action - -import ( - "context" - "sync" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/watch" - applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" - "k8s.io/client-go/kubernetes" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" -) - -// lazyClient is a workaround to deal with Kubernetes having an unstable client API. -// In Kubernetes v1.18 the defaults where removed which broke creating a -// client without an explicit configuration. ಠ_ಠ -type lazyClient struct { - // client caches an initialized kubernetes client - initClient sync.Once - client kubernetes.Interface - clientErr error - - // clientFn loads a kubernetes client - clientFn func() (*kubernetes.Clientset, error) - - // namespace passed to each client request - namespace string -} - -func (s *lazyClient) init() error { - s.initClient.Do(func() { - s.client, s.clientErr = s.clientFn() - }) - return s.clientErr -} - -// secretClient implements a corev1.SecretsInterface -type secretClient struct{ *lazyClient } - -var _ corev1.SecretInterface = (*secretClient)(nil) - -func newSecretClient(lc *lazyClient) *secretClient { - return &secretClient{lazyClient: lc} -} - -func (s *secretClient) Create(ctx context.Context, secret *v1.Secret, opts metav1.CreateOptions) (result *v1.Secret, err error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Create(ctx, secret, opts) -} - -func (s *secretClient) Update(ctx context.Context, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Update(ctx, secret, opts) -} - -func (s *secretClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - if err := s.init(); err != nil { - return err - } - return s.client.CoreV1().Secrets(s.namespace).Delete(ctx, name, opts) -} - -func (s *secretClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - if err := s.init(); err != nil { - return err - } - return s.client.CoreV1().Secrets(s.namespace).DeleteCollection(ctx, opts, listOpts) -} - -func (s *secretClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Get(ctx, name, opts) -} - -func (s *secretClient) List(ctx context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).List(ctx, opts) -} - -func (s *secretClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Watch(ctx, opts) -} - -func (s *secretClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Patch(ctx, name, pt, data, opts, subresources...) -} - -func (s *secretClient) Apply(ctx context.Context, secretConfiguration *applycorev1.SecretApplyConfiguration, opts metav1.ApplyOptions) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Apply(ctx, secretConfiguration, opts) -} - -// configMapClient implements a corev1.ConfigMapInterface -type configMapClient struct{ *lazyClient } - -var _ corev1.ConfigMapInterface = (*configMapClient)(nil) - -func newConfigMapClient(lc *lazyClient) *configMapClient { - return &configMapClient{lazyClient: lc} -} - -func (c *configMapClient) Create(ctx context.Context, configMap *v1.ConfigMap, opts metav1.CreateOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Create(ctx, configMap, opts) -} - -func (c *configMapClient) Update(ctx context.Context, configMap *v1.ConfigMap, opts metav1.UpdateOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Update(ctx, configMap, opts) -} - -func (c *configMapClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - if err := c.init(); err != nil { - return err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Delete(ctx, name, opts) -} - -func (c *configMapClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - if err := c.init(); err != nil { - return err - } - return c.client.CoreV1().ConfigMaps(c.namespace).DeleteCollection(ctx, opts, listOpts) -} - -func (c *configMapClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Get(ctx, name, opts) -} - -func (c *configMapClient) List(ctx context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).List(ctx, opts) -} - -func (c *configMapClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Watch(ctx, opts) -} - -func (c *configMapClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Patch(ctx, name, pt, data, opts, subresources...) -} - -func (c *configMapClient) Apply(ctx context.Context, configMap *applycorev1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Apply(ctx, configMap, opts) -} diff --git a/pkg/action/lint.go b/pkg/action/lint.go deleted file mode 100644 index 5b566e9d3..000000000 --- a/pkg/action/lint.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -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 action - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Lint is the action for checking that the semantics of a chart are well-formed. -// -// It provides the implementation of 'helm lint'. -type Lint struct { - Strict bool - Namespace string - WithSubcharts bool - Quiet bool -} - -// LintResult is the result of Lint -type LintResult struct { - TotalChartsLinted int - Messages []support.Message - Errors []error -} - -// NewLint creates a new Lint object with the given configuration. -func NewLint() *Lint { - return &Lint{} -} - -// Run executes 'helm Lint' against the given chart. -func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { - lowestTolerance := support.ErrorSev - if l.Strict { - lowestTolerance = support.WarningSev - } - result := &LintResult{} - for _, path := range paths { - linter, err := lintChart(path, vals, l.Namespace, l.Strict) - if err != nil { - result.Errors = append(result.Errors, err) - continue - } - - result.Messages = append(result.Messages, linter.Messages...) - result.TotalChartsLinted++ - for _, msg := range linter.Messages { - if msg.Severity >= lowestTolerance { - result.Errors = append(result.Errors, msg.Err) - } - } - } - return result -} - -// HasWaringsOrErrors checks is LintResult has any warnings or errors -func HasWarningsOrErrors(result *LintResult) bool { - for _, msg := range result.Messages { - if msg.Severity > support.InfoSev { - return true - } - } - return false -} - -func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { - var chartPath string - linter := support.Linter{} - - if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { - tempDir, err := ioutil.TempDir("", "helm-lint") - if err != nil { - return linter, errors.Wrap(err, "unable to create temp dir to extract tarball") - } - defer os.RemoveAll(tempDir) - - file, err := os.Open(path) - if err != nil { - return linter, errors.Wrap(err, "unable to open tarball") - } - defer file.Close() - - if err = chartutil.Expand(tempDir, file); err != nil { - return linter, errors.Wrap(err, "unable to extract tarball") - } - - files, err := os.ReadDir(tempDir) - if err != nil { - return linter, errors.Wrapf(err, "unable to read temporary output directory %s", tempDir) - } - if !files[0].IsDir() { - return linter, errors.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir) - } - - chartPath = filepath.Join(tempDir, files[0].Name()) - } else { - chartPath = path - } - - // Guard: Error out if this is not a chart. - if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { - return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart") - } - - return lint.All(chartPath, vals, namespace, strict), nil -} diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go deleted file mode 100644 index 1828461f3..000000000 --- a/pkg/action/lint_test.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -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 action - -import ( - "testing" -) - -var ( - values = make(map[string]interface{}) - namespace = "testNamespace" - strict = false - chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" - chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" - corruptedTgzChart = "testdata/charts/corrupted-compressed-chart.tgz" - chartWithNoTemplatesDir = "testdata/charts/chart-with-no-templates-dir" -) - -func TestLintChart(t *testing.T) { - tests := []struct { - name string - chartPath string - err bool - }{ - { - name: "decompressed-chart", - chartPath: "testdata/charts/decompressedchart/", - }, - { - name: "archived-chart-path", - chartPath: "testdata/charts/compressedchart-0.1.0.tgz", - }, - { - name: "archived-chart-path-with-hyphens", - chartPath: "testdata/charts/compressedchart-with-hyphens-0.1.0.tgz", - }, - { - name: "archived-tar-gz-chart-path", - chartPath: "testdata/charts/compressedchart-0.1.0.tar.gz", - }, - { - name: "invalid-archived-chart-path", - chartPath: "testdata/charts/invalidcompressedchart0.1.0.tgz", - err: true, - }, - { - name: "chart-missing-manifest", - chartPath: "testdata/charts/chart-missing-manifest", - err: true, - }, - { - name: "chart-with-schema", - chartPath: "testdata/charts/chart-with-schema", - }, - { - name: "chart-with-schema-negative", - chartPath: "testdata/charts/chart-with-schema-negative", - }, - { - name: "pre-release-chart", - chartPath: "testdata/charts/pre-release-chart-0.1.0-alpha.tgz", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict) - switch { - case err != nil && !tt.err: - t.Errorf("%s", err) - case err == nil && tt.err: - t.Errorf("Expected a chart parsing error") - } - }) - } -} - -func TestNonExistentChart(t *testing.T) { - t.Run("should error out for non existent tgz chart", func(t *testing.T) { - testCharts := []string{"non-existent-chart.tgz"} - expectedError := "unable to open tarball: open non-existent-chart.tgz: no such file or directory" - testLint := NewLint() - - result := testLint.Run(testCharts, values) - if len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } - - actual := result.Errors[0].Error() - if actual != expectedError { - t.Errorf("expected '%s', but got '%s'", expectedError, actual) - } - }) - - t.Run("should error out for corrupted tgz chart", func(t *testing.T) { - testCharts := []string{corruptedTgzChart} - expectedEOFError := "unable to extract tarball: EOF" - testLint := NewLint() - - result := testLint.Run(testCharts, values) - if len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } - - actual := result.Errors[0].Error() - if actual != expectedEOFError { - t.Errorf("expected '%s', but got '%s'", expectedEOFError, actual) - } - }) -} - -func TestLint_MultipleCharts(t *testing.T) { - testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint} - testLint := NewLint() - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error(result.Errors) - } -} - -func TestLint_EmptyResultErrors(t *testing.T) { - testCharts := []string{chart2MultipleChartLint} - testLint := NewLint() - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error("Expected no error, got more") - } -} - -func TestLint_ChartWithWarnings(t *testing.T) { - t.Run("should pass when not strict", func(t *testing.T) { - testCharts := []string{chartWithNoTemplatesDir} - testLint := NewLint() - testLint.Strict = false - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error("Expected no error, got more") - } - }) - - t.Run("should fail with errors when strict", func(t *testing.T) { - testCharts := []string{chartWithNoTemplatesDir} - testLint := NewLint() - testLint.Strict = true - if result := testLint.Run(testCharts, values); len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } - }) -} diff --git a/pkg/action/list.go b/pkg/action/list.go deleted file mode 100644 index af0725c4a..000000000 --- a/pkg/action/list.go +++ /dev/null @@ -1,324 +0,0 @@ -/* -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 action - -import ( - "path" - "regexp" - - "k8s.io/apimachinery/pkg/labels" - - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" -) - -// ListStates represents zero or more status codes that a list item may have set -// -// Because this is used as a bitmask filter, more than one bit can be flipped -// in the ListStates. -type ListStates uint - -const ( - // ListDeployed filters on status "deployed" - ListDeployed ListStates = 1 << iota - // ListUninstalled filters on status "uninstalled" - ListUninstalled - // ListUninstalling filters on status "uninstalling" (uninstall in progress) - ListUninstalling - // ListPendingInstall filters on status "pending" (deployment in progress) - ListPendingInstall - // ListPendingUpgrade filters on status "pending_upgrade" (upgrade in progress) - ListPendingUpgrade - // ListPendingRollback filters on status "pending_rollback" (rollback in progress) - ListPendingRollback - // ListSuperseded filters on status "superseded" (historical release version that is no longer deployed) - ListSuperseded - // ListFailed filters on status "failed" (release version not deployed because of error) - ListFailed - // ListUnknown filters on an unknown status - ListUnknown -) - -// FromName takes a state name and returns a ListStates representation. -// -// Currently, there are only names for individual flipped bits, so the returned -// ListStates will only match one of the constants. However, it is possible that -// this behavior could change in the future. -func (s ListStates) FromName(str string) ListStates { - switch str { - case "deployed": - return ListDeployed - case "uninstalled": - return ListUninstalled - case "superseded": - return ListSuperseded - case "failed": - return ListFailed - case "uninstalling": - return ListUninstalling - case "pending-install": - return ListPendingInstall - case "pending-upgrade": - return ListPendingUpgrade - case "pending-rollback": - return ListPendingRollback - } - return ListUnknown -} - -// ListAll is a convenience for enabling all list filters -const ListAll = ListDeployed | ListUninstalled | ListUninstalling | ListPendingInstall | ListPendingRollback | ListPendingUpgrade | ListSuperseded | ListFailed - -// Sorter is a top-level sort -type Sorter uint - -const ( - // ByNameDesc sorts by descending lexicographic order - ByNameDesc Sorter = iota + 1 - // ByDateAsc sorts by ascending dates (oldest updated release first) - ByDateAsc - // ByDateDesc sorts by descending dates (latest updated release first) - ByDateDesc -) - -// List is the action for listing releases. -// -// It provides, for example, the implementation of 'helm list'. -// It returns no more than one revision of every release in one specific, or in -// all, namespaces. -// To list all the revisions of a specific release, see the History action. -type List struct { - cfg *Configuration - - // All ignores the limit/offset - All bool - // AllNamespaces searches across namespaces - AllNamespaces bool - // Sort indicates the sort to use - // - // see pkg/releaseutil for several useful sorters - Sort Sorter - // Overrides the default lexicographic sorting - ByDate bool - SortReverse bool - // StateMask accepts a bitmask of states for items to show. - // The default is ListDeployed - StateMask ListStates - // Limit is the number of items to return per Run() - Limit int - // Offset is the starting index for the Run() call - Offset int - // Filter is a filter that is applied to the results - Filter string - Short bool - NoHeaders bool - TimeFormat string - Uninstalled bool - Superseded bool - Uninstalling bool - Deployed bool - Failed bool - Pending bool - Selector string -} - -// NewList constructs a new *List -func NewList(cfg *Configuration) *List { - return &List{ - StateMask: ListDeployed | ListFailed, - cfg: cfg, - } -} - -// Run executes the list command, returning a set of matches. -func (l *List) Run() ([]*release.Release, error) { - if err := l.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - var filter *regexp.Regexp - if l.Filter != "" { - var err error - filter, err = regexp.Compile(l.Filter) - if err != nil { - return nil, err - } - } - - results, err := l.cfg.Releases.List(func(rel *release.Release) bool { - // Skip anything that doesn't match the filter. - if filter != nil && !filter.MatchString(rel.Name) { - return false - } - - return true - }) - - if err != nil { - return nil, err - } - - if results == nil { - return results, nil - } - - // by definition, superseded releases are never shown if - // only the latest releases are returned. so if requested statemask - // is _only_ ListSuperseded, skip the latest release filter - if l.StateMask != ListSuperseded { - results = filterLatestReleases(results) - } - - // State mask application must occur after filtering to - // latest releases, otherwise outdated entries can be returned - results = l.filterStateMask(results) - - // Skip anything that doesn't match the selector - selectorObj, err := labels.Parse(l.Selector) - if err != nil { - return nil, err - } - results = l.filterSelector(results, selectorObj) - - // Unfortunately, we have to sort before truncating, which can incur substantial overhead - l.sort(results) - - // Guard on offset - if l.Offset >= len(results) { - return []*release.Release{}, nil - } - - // Calculate the limit and offset, and then truncate results if necessary. - limit := len(results) - if l.Limit > 0 && l.Limit < limit { - limit = l.Limit - } - last := l.Offset + limit - if l := len(results); l < last { - last = l - } - results = results[l.Offset:last] - - return results, err -} - -// sort is an in-place sort where order is based on the value of a.Sort -func (l *List) sort(rels []*release.Release) { - if l.SortReverse { - l.Sort = ByNameDesc - } - - if l.ByDate { - l.Sort = ByDateDesc - if l.SortReverse { - l.Sort = ByDateAsc - } - } - - switch l.Sort { - case ByDateDesc: - releaseutil.SortByDate(rels) - case ByDateAsc: - releaseutil.Reverse(rels, releaseutil.SortByDate) - case ByNameDesc: - releaseutil.Reverse(rels, releaseutil.SortByName) - default: - releaseutil.SortByName(rels) - } -} - -// filterLatestReleases returns a list scrubbed of old releases. -func filterLatestReleases(releases []*release.Release) []*release.Release { - latestReleases := make(map[string]*release.Release) - - for _, rls := range releases { - name, namespace := rls.Name, rls.Namespace - key := path.Join(namespace, name) - if latestRelease, exists := latestReleases[key]; exists && latestRelease.Version > rls.Version { - continue - } - latestReleases[key] = rls - } - - var list = make([]*release.Release, 0, len(latestReleases)) - for _, rls := range latestReleases { - list = append(list, rls) - } - return list -} - -func (l *List) filterStateMask(releases []*release.Release) []*release.Release { - desiredStateReleases := make([]*release.Release, 0) - - for _, rls := range releases { - currentStatus := l.StateMask.FromName(rls.Info.Status.String()) - mask := l.StateMask & currentStatus - if mask == 0 { - continue - } - desiredStateReleases = append(desiredStateReleases, rls) - } - - return desiredStateReleases -} - -func (l *List) filterSelector(releases []*release.Release, selector labels.Selector) []*release.Release { - desiredStateReleases := make([]*release.Release, 0) - - for _, rls := range releases { - if selector.Matches(labels.Set(rls.Labels)) { - desiredStateReleases = append(desiredStateReleases, rls) - } - } - - return desiredStateReleases -} - -// SetStateMask calculates the state mask based on parameters. -func (l *List) SetStateMask() { - if l.All { - l.StateMask = ListAll - return - } - - state := ListStates(0) - if l.Deployed { - state |= ListDeployed - } - if l.Uninstalled { - state |= ListUninstalled - } - if l.Uninstalling { - state |= ListUninstalling - } - if l.Pending { - state |= ListPendingInstall | ListPendingRollback | ListPendingUpgrade - } - if l.Failed { - state |= ListFailed - } - if l.Superseded { - state |= ListSuperseded - } - - // Apply a default - if state == 0 { - state = ListDeployed | ListFailed - } - - l.StateMask = state -} diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go deleted file mode 100644 index 73009d523..000000000 --- a/pkg/action/list_test.go +++ /dev/null @@ -1,368 +0,0 @@ -/* -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 action - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" -) - -func TestListStates(t *testing.T) { - for input, expect := range map[string]ListStates{ - "deployed": ListDeployed, - "uninstalled": ListUninstalled, - "uninstalling": ListUninstalling, - "superseded": ListSuperseded, - "failed": ListFailed, - "pending-install": ListPendingInstall, - "pending-rollback": ListPendingRollback, - "pending-upgrade": ListPendingUpgrade, - "unknown": ListUnknown, - "totally made up key": ListUnknown, - } { - if expect != expect.FromName(input) { - t.Errorf("Expected %d for %s", expect, input) - } - // This is a cheap way to verify that ListAll actually allows everything but Unknown - if got := expect.FromName(input); got != ListUnknown && got&ListAll == 0 { - t.Errorf("Expected %s to match the ListAll filter", input) - } - } - - filter := ListDeployed | ListPendingRollback - if status := filter.FromName("deployed"); filter&status == 0 { - t.Errorf("Expected %d to match mask %d", status, filter) - } - if status := filter.FromName("failed"); filter&status != 0 { - t.Errorf("Expected %d to fail to match mask %d", status, filter) - } -} - -func TestList_Empty(t *testing.T) { - lister := NewList(actionConfigFixture(t)) - list, err := lister.Run() - assert.NoError(t, err) - assert.Len(t, list, 0) -} - -func newListFixture(t *testing.T) *List { - return NewList(actionConfigFixture(t)) -} - -func TestList_OneNamespace(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) -} - -func TestList_AllNamespaces(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - makeMeSomeReleases(lister.cfg.Releases, t) - lister.AllNamespaces = true - lister.SetStateMask() - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) -} - -func TestList_Sort(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Sort = ByNameDesc // Other sorts are tested elsewhere - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) - is.Equal("two", list[0].Name) - is.Equal("three", list[1].Name) - is.Equal("one", list[2].Name) -} - -func TestList_Limit(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 2 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 2) - // Lex order means one, three, two - is.Equal("one", list[0].Name) - is.Equal("three", list[1].Name) -} - -func TestList_BigLimit(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 20 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) - - // Lex order means one, three, two - is.Equal("one", list[0].Name) - is.Equal("three", list[1].Name) - is.Equal("two", list[2].Name) -} - -func TestList_LimitOffset(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 2 - lister.Offset = 1 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 2) - - // Lex order means one, three, two - is.Equal("three", list[0].Name) - is.Equal("two", list[1].Name) -} - -func TestList_LimitOffsetOutOfBounds(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 2 - lister.Offset = 3 // Last item is index 2 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 0) - - lister.Limit = 10 - lister.Offset = 1 - list, err = lister.Run() - is.NoError(err) - is.Len(list, 2) -} - -func TestList_StateMask(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - makeMeSomeReleases(lister.cfg.Releases, t) - one, err := lister.cfg.Releases.Get("one", 1) - is.NoError(err) - one.SetStatus(release.StatusUninstalled, "uninstalled") - err = lister.cfg.Releases.Update(one) - is.NoError(err) - - res, err := lister.Run() - is.NoError(err) - is.Len(res, 2) - is.Equal("three", res[0].Name) - is.Equal("two", res[1].Name) - - lister.StateMask = ListUninstalled - res, err = lister.Run() - is.NoError(err) - is.Len(res, 1) - is.Equal("one", res[0].Name) - - lister.StateMask |= ListDeployed - res, err = lister.Run() - is.NoError(err) - is.Len(res, 3) -} - -func TestList_StateMaskWithStaleRevisions(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.StateMask = ListFailed - - makeMeSomeReleasesWithStaleFailure(lister.cfg.Releases, t) - - res, err := lister.Run() - - is.NoError(err) - is.Len(res, 1) - - // "dirty" release should _not_ be present as most recent - // release is deployed despite failed release in past - is.Equal("failed", res[0].Name) -} - -func makeMeSomeReleasesWithStaleFailure(store *storage.Storage, t *testing.T) { - t.Helper() - one := namedReleaseStub("clean", release.StatusDeployed) - one.Namespace = "default" - one.Version = 1 - - two := namedReleaseStub("dirty", release.StatusDeployed) - two.Namespace = "default" - two.Version = 1 - - three := namedReleaseStub("dirty", release.StatusFailed) - three.Namespace = "default" - three.Version = 2 - - four := namedReleaseStub("dirty", release.StatusDeployed) - four.Namespace = "default" - four.Version = 3 - - five := namedReleaseStub("failed", release.StatusFailed) - five.Namespace = "default" - five.Version = 1 - - for _, rel := range []*release.Release{one, two, three, four, five} { - if err := store.Create(rel); err != nil { - t.Fatal(err) - } - } - - all, err := store.ListReleases() - assert.NoError(t, err) - assert.Len(t, all, 5, "sanity test: five items added") -} - -func TestList_Filter(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Filter = "th." - makeMeSomeReleases(lister.cfg.Releases, t) - - res, err := lister.Run() - is.NoError(err) - is.Len(res, 1) - is.Equal("three", res[0].Name) -} - -func TestList_FilterFailsCompile(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Filter = "t[h.{{{" - makeMeSomeReleases(lister.cfg.Releases, t) - - _, err := lister.Run() - is.Error(err) -} - -func makeMeSomeReleases(store *storage.Storage, t *testing.T) { - t.Helper() - one := releaseStub() - one.Name = "one" - one.Namespace = "default" - one.Version = 1 - two := releaseStub() - two.Name = "two" - two.Namespace = "default" - two.Version = 2 - three := releaseStub() - three.Name = "three" - three.Namespace = "default" - three.Version = 3 - - for _, rel := range []*release.Release{one, two, three} { - if err := store.Create(rel); err != nil { - t.Fatal(err) - } - } - - all, err := store.ListReleases() - assert.NoError(t, err) - assert.Len(t, all, 3, "sanity test: three items added") -} - -func TestFilterLatestReleases(t *testing.T) { - t.Run("should filter old versions of the same release", func(t *testing.T) { - r1 := releaseStub() - r1.Name = "r" - r1.Version = 1 - r2 := releaseStub() - r2.Name = "r" - r2.Version = 2 - another := releaseStub() - another.Name = "another" - another.Version = 1 - - filteredList := filterLatestReleases([]*release.Release{r1, r2, another}) - expectedFilteredList := []*release.Release{r2, another} - - assert.ElementsMatch(t, expectedFilteredList, filteredList) - }) - - t.Run("should not filter out any version across namespaces", func(t *testing.T) { - r1 := releaseStub() - r1.Name = "r" - r1.Namespace = "default" - r1.Version = 1 - r2 := releaseStub() - r2.Name = "r" - r2.Namespace = "testing" - r2.Version = 2 - - filteredList := filterLatestReleases([]*release.Release{r1, r2}) - expectedFilteredList := []*release.Release{r1, r2} - - assert.ElementsMatch(t, expectedFilteredList, filteredList) - }) -} - -func TestSelectorList(t *testing.T) { - r1 := releaseStub() - r1.Name = "r1" - r1.Version = 1 - r1.Labels = map[string]string{"key": "value1"} - r2 := releaseStub() - r2.Name = "r2" - r2.Version = 1 - r2.Labels = map[string]string{"key": "value2"} - r3 := releaseStub() - r3.Name = "r3" - r3.Version = 1 - r3.Labels = map[string]string{} - - lister := newListFixture(t) - for _, rel := range []*release.Release{r1, r2, r3} { - if err := lister.cfg.Releases.Create(rel); err != nil { - t.Fatal(err) - } - } - - t.Run("should fail selector parsing", func(t *testing.T) { - is := assert.New(t) - lister.Selector = "a?=b" - - _, err := lister.Run() - is.Error(err) - }) - - t.Run("should select one release with matching label", func(t *testing.T) { - lister.Selector = "key==value1" - res, _ := lister.Run() - - expectedFilteredList := []*release.Release{r1} - assert.ElementsMatch(t, expectedFilteredList, res) - }) - - t.Run("should select two releases with non matching label", func(t *testing.T) { - lister.Selector = "key!=value1" - res, _ := lister.Run() - - expectedFilteredList := []*release.Release{r2, r3} - assert.ElementsMatch(t, expectedFilteredList, res) - }) -} diff --git a/pkg/action/package.go b/pkg/action/package.go deleted file mode 100644 index 52920956f..000000000 --- a/pkg/action/package.go +++ /dev/null @@ -1,182 +0,0 @@ -/* -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 action - -import ( - "bufio" - "fmt" - "io/ioutil" - "os" - "syscall" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "golang.org/x/term" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/provenance" -) - -// Package is the action for packaging a chart. -// -// It provides the implementation of 'helm package'. -type Package struct { - Sign bool - Key string - Keyring string - PassphraseFile string - Version string - AppVersion string - Destination string - DependencyUpdate bool - - RepositoryConfig string - RepositoryCache string -} - -// NewPackage creates a new Package object with the given configuration. -func NewPackage() *Package { - return &Package{} -} - -// Run executes 'helm package' against the given chart and returns the path to the packaged chart. -func (p *Package) Run(path string, vals map[string]interface{}) (string, error) { - ch, err := loader.LoadDir(path) - if err != nil { - return "", err - } - - // If version is set, modify the version. - if p.Version != "" { - ch.Metadata.Version = p.Version - } - - if err := validateVersion(ch.Metadata.Version); err != nil { - return "", err - } - - if p.AppVersion != "" { - ch.Metadata.AppVersion = p.AppVersion - } - - if reqs := ch.Metadata.Dependencies; reqs != nil { - if err := CheckDependencies(ch, reqs); err != nil { - return "", err - } - } - - var dest string - if p.Destination == "." { - // Save to the current working directory. - dest, err = os.Getwd() - if err != nil { - return "", err - } - } else { - // Otherwise save to set destination - dest = p.Destination - } - - name, err := chartutil.Save(ch, dest) - if err != nil { - return "", errors.Wrap(err, "failed to save") - } - - if p.Sign { - err = p.Clearsign(name) - } - - return name, err -} - -// validateVersion Verify that version is a Version, and error out if it is not. -func validateVersion(ver string) error { - if _, err := semver.NewVersion(ver); err != nil { - return err - } - return nil -} - -// Clearsign signs a chart -func (p *Package) Clearsign(filename string) error { - // Load keyring - signer, err := provenance.NewFromKeyring(p.Keyring, p.Key) - if err != nil { - return err - } - - passphraseFetcher := promptUser - if p.PassphraseFile != "" { - passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin) - if err != nil { - return err - } - } - - if err := signer.DecryptKey(passphraseFetcher); err != nil { - return err - } - - sig, err := signer.ClearSign(filename) - if err != nil { - return err - } - - return ioutil.WriteFile(filename+".prov", []byte(sig), 0644) -} - -// promptUser implements provenance.PassphraseFetcher -func promptUser(name string) ([]byte, error) { - fmt.Printf("Password for key %q > ", name) - // syscall.Stdin is not an int in all environments and needs to be coerced - // into one there (e.g., Windows) - pw, err := term.ReadPassword(int(syscall.Stdin)) - fmt.Println() - return pw, err -} - -func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { - file, err := openPassphraseFile(passphraseFile, stdin) - if err != nil { - return nil, err - } - defer file.Close() - - reader := bufio.NewReader(file) - passphrase, _, err := reader.ReadLine() - if err != nil { - return nil, err - } - return func(name string) ([]byte, error) { - return passphrase, nil - }, nil -} - -func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { - if passphraseFile == "-" { - stat, err := stdin.Stat() - if err != nil { - return nil, err - } - if (stat.Mode() & os.ModeNamedPipe) == 0 { - return nil, errors.New("specified reading passphrase from stdin, without input on stdin") - } - return stdin, nil - } - return os.Open(passphraseFile) -} diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go deleted file mode 100644 index 5c5fed571..000000000 --- a/pkg/action/package_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -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 action - -import ( - "io/ioutil" - "os" - "path" - "testing" - - "github.com/Masterminds/semver/v3" - - "helm.sh/helm/v3/internal/test/ensure" -) - -func TestPassphraseFileFetcher(t *testing.T) { - secret := "secret" - directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) - defer os.RemoveAll(directory) - - fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) - if err != nil { - t.Fatal("Unable to create passphraseFileFetcher", err) - } - - passphrase, err := fetcher("key") - if err != nil { - t.Fatal("Unable to fetch passphrase") - } - - if string(passphrase) != secret { - t.Errorf("Expected %s got %s", secret, string(passphrase)) - } -} - -func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { - secret := "secret" - directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n.")) - defer os.RemoveAll(directory) - - fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) - if err != nil { - t.Fatal("Unable to create passphraseFileFetcher", err) - } - - passphrase, err := fetcher("key") - if err != nil { - t.Fatal("Unable to fetch passphrase") - } - - if string(passphrase) != secret { - t.Errorf("Expected %s got %s", secret, string(passphrase)) - } -} - -func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { - directory := ensure.TempDir(t) - defer os.RemoveAll(directory) - - stdin, err := ioutil.TempFile(directory, "non-existing") - if err != nil { - t.Fatal("Unable to create test file", err) - } - - if _, err := passphraseFileFetcher("-", stdin); err == nil { - t.Error("Expected passphraseFileFetcher returning an error") - } -} - -func TestValidateVersion(t *testing.T) { - type args struct { - ver string - } - tests := []struct { - name string - args args - wantErr error - }{ - { - "normal semver version", - args{ - ver: "1.1.3-23658", - }, - nil, - }, - { - "Pre version number starting with 0", - args{ - ver: "1.1.3-023658", - }, - semver.ErrSegmentStartsZero, - }, - { - "Invalid version number", - args{ - ver: "1.1.3.sd.023658", - }, - semver.ErrInvalidSemVer, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := validateVersion(tt.args.ver); err != nil { - if err != tt.wantErr { - t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) - } - - } - }) - } -} diff --git a/pkg/action/pull.go b/pkg/action/pull.go deleted file mode 100644 index b4018869e..000000000 --- a/pkg/action/pull.go +++ /dev/null @@ -1,166 +0,0 @@ -/* -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 action - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// Pull is the action for checking a given release's information. -// -// It provides the implementation of 'helm pull'. -type Pull struct { - ChartPathOptions - - Settings *cli.EnvSettings // TODO: refactor this out of pkg/action - - Devel bool - Untar bool - VerifyLater bool - UntarDir string - DestDir string - cfg *Configuration -} - -type PullOpt func(*Pull) - -func WithConfig(cfg *Configuration) PullOpt { - return func(p *Pull) { - p.cfg = cfg - } -} - -// NewPull creates a new Pull object. -func NewPull() *Pull { - return NewPullWithOpts() -} - -// NewPullWithOpts creates a new pull, with configuration options. -func NewPullWithOpts(opts ...PullOpt) *Pull { - p := &Pull{} - for _, fn := range opts { - fn(p) - } - - return p -} - -// Run executes 'helm pull' against the given release. -func (p *Pull) Run(chartRef string) (string, error) { - var out strings.Builder - - c := downloader.ChartDownloader{ - Out: &out, - Keyring: p.Keyring, - Verify: downloader.VerifyNever, - Getters: getter.All(p.Settings), - Options: []getter.Option{ - getter.WithBasicAuth(p.Username, p.Password), - getter.WithPassCredentialsAll(p.PassCredentialsAll), - getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), - getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify), - }, - RegistryClient: p.cfg.RegistryClient, - RepositoryConfig: p.Settings.RepositoryConfig, - RepositoryCache: p.Settings.RepositoryCache, - } - - if registry.IsOCI(chartRef) { - c.Options = append(c.Options, - getter.WithRegistryClient(p.cfg.RegistryClient)) - } - - if p.Verify { - c.Verify = downloader.VerifyAlways - } else if p.VerifyLater { - c.Verify = downloader.VerifyLater - } - - // If untar is set, we fetch to a tempdir, then untar and copy after - // verification. - dest := p.DestDir - if p.Untar { - var err error - dest, err = ioutil.TempDir("", "helm-") - if err != nil { - return out.String(), errors.Wrap(err, "failed to untar") - } - defer os.RemoveAll(dest) - } - - if p.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, p.PassCredentialsAll, getter.All(p.Settings)) - if err != nil { - return out.String(), err - } - chartRef = chartURL - } - - saved, v, err := c.DownloadTo(chartRef, p.Version, dest) - if err != nil { - return out.String(), err - } - - if p.Verify { - for name := range v.SignedBy.Identities { - fmt.Fprintf(&out, "Signed by: %v\n", name) - } - fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint) - fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash) - } - - // After verification, untar the chart into the requested directory. - if p.Untar { - ud := p.UntarDir - if !filepath.IsAbs(ud) { - ud = filepath.Join(p.DestDir, ud) - } - // Let udCheck to check conflict file/dir without replacing ud when untarDir is the current directory(.). - udCheck := ud - if udCheck == "." { - _, udCheck = filepath.Split(chartRef) - } else { - _, chartName := filepath.Split(chartRef) - udCheck = filepath.Join(udCheck, chartName) - } - - if _, err := os.Stat(udCheck); err != nil { - if err := os.MkdirAll(udCheck, 0755); err != nil { - return out.String(), errors.Wrap(err, "failed to untar (mkdir)") - } - - } else { - return out.String(), errors.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck) - } - - return out.String(), chartutil.ExpandFile(ud, saved) - } - return out.String(), nil -} diff --git a/pkg/action/push.go b/pkg/action/push.go deleted file mode 100644 index 99d1beadc..000000000 --- a/pkg/action/push.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -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 action - -import ( - "strings" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/pusher" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/uploader" -) - -// Push is the action for uploading a chart. -// -// It provides the implementation of 'helm push'. -type Push struct { - Settings *cli.EnvSettings - cfg *Configuration -} - -// PushOpt is a type of function that sets options for a push action. -type PushOpt func(*Push) - -// WithPushConfig sets the cfg field on the push configuration object. -func WithPushConfig(cfg *Configuration) PushOpt { - return func(p *Push) { - p.cfg = cfg - } -} - -// NewPushWithOpts creates a new push, with configuration options. -func NewPushWithOpts(opts ...PushOpt) *Push { - p := &Push{} - for _, fn := range opts { - fn(p) - } - return p -} - -// Run executes 'helm push' against the given chart archive. -func (p *Push) Run(chartRef string, remote string) (string, error) { - var out strings.Builder - - c := uploader.ChartUploader{ - Out: &out, - Pushers: pusher.All(p.Settings), - Options: []pusher.Option{}, - } - - if registry.IsOCI(remote) { - c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient)) - } - - return out.String(), c.UploadTo(chartRef, remote) -} diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go deleted file mode 100644 index 68bcc7442..000000000 --- a/pkg/action/registry_login.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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 action - -import ( - "io" - - "helm.sh/helm/v3/pkg/registry" -) - -// RegistryLogin performs a registry login operation. -type RegistryLogin struct { - cfg *Configuration -} - -// NewRegistryLogin creates a new RegistryLogin object with the given configuration. -func NewRegistryLogin(cfg *Configuration) *RegistryLogin { - return &RegistryLogin{ - cfg: cfg, - } -} - -// Run executes the registry login operation -func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, insecure bool) error { - return a.cfg.RegistryClient.Login( - hostname, - registry.LoginOptBasicAuth(username, password), - registry.LoginOptInsecure(insecure)) -} diff --git a/pkg/action/registry_logout.go b/pkg/action/registry_logout.go deleted file mode 100644 index 69add4163..000000000 --- a/pkg/action/registry_logout.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -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 action - -import ( - "io" -) - -// RegistryLogout performs a registry login operation. -type RegistryLogout struct { - cfg *Configuration -} - -// NewRegistryLogout creates a new RegistryLogout object with the given configuration. -func NewRegistryLogout(cfg *Configuration) *RegistryLogout { - return &RegistryLogout{ - cfg: cfg, - } -} - -// Run executes the registry logout operation -func (a *RegistryLogout) Run(out io.Writer, hostname string) error { - return a.cfg.RegistryClient.Logout(hostname) -} diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go deleted file mode 100644 index ecaeaf59f..000000000 --- a/pkg/action/release_testing.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -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 action - -import ( - "context" - "fmt" - "io" - "time" - - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -// ReleaseTesting is the action for testing a release. -// -// It provides the implementation of 'helm test'. -type ReleaseTesting struct { - cfg *Configuration - Timeout time.Duration - // Used for fetching logs from test pods - Namespace string - Filters map[string][]string -} - -// NewReleaseTesting creates a new ReleaseTesting object with the given configuration. -func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { - return &ReleaseTesting{ - cfg: cfg, - Filters: map[string][]string{}, - } -} - -// Run executes 'helm test' against the given release. -func (r *ReleaseTesting) Run(name string) (*release.Release, error) { - if err := r.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name) - } - - // finds the non-deleted release with the given name - rel, err := r.cfg.Releases.Last(name) - if err != nil { - return rel, err - } - - skippedHooks := []*release.Hook{} - executingHooks := []*release.Hook{} - if len(r.Filters["!name"]) != 0 { - for _, h := range rel.Hooks { - if contains(r.Filters["!name"], h.Name) { - skippedHooks = append(skippedHooks, h) - } else { - executingHooks = append(executingHooks, h) - } - } - rel.Hooks = executingHooks - } - if len(r.Filters["name"]) != 0 { - executingHooks = nil - for _, h := range rel.Hooks { - if contains(r.Filters["name"], h.Name) { - executingHooks = append(executingHooks, h) - } else { - skippedHooks = append(skippedHooks, h) - } - } - rel.Hooks = executingHooks - } - - if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { - rel.Hooks = append(skippedHooks, rel.Hooks...) - r.cfg.Releases.Update(rel) - return rel, err - } - - rel.Hooks = append(skippedHooks, rel.Hooks...) - return rel, r.cfg.Releases.Update(rel) -} - -// GetPodLogs will write the logs for all test pods in the given release into -// the given writer. These can be immediately output to the user or captured for -// other uses -func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { - client, err := r.cfg.KubernetesClientSet() - if err != nil { - return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs") - } - - for _, h := range rel.Hooks { - for _, e := range h.Events { - if e == release.HookTest { - req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) - logReader, err := req.Stream(context.Background()) - if err != nil { - return errors.Wrapf(err, "unable to get pod logs for %s", h.Name) - } - - fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) - _, err = io.Copy(out, logReader) - fmt.Fprintln(out) - if err != nil { - return errors.Wrapf(err, "unable to write pod logs for %s", h.Name) - } - } - } - } - return nil -} - -func contains(arr []string, value string) bool { - for _, item := range arr { - if item == value { - return true - } - } - return false -} diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go deleted file mode 100644 index 63e83f3d9..000000000 --- a/pkg/action/resource_policy.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -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 action - -import ( - "strings" - - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/releaseutil" -) - -func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) { - for _, m := range manifests { - if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 { - remaining = append(remaining, m) - continue - } - - resourcePolicyType, ok := m.Head.Metadata.Annotations[kube.ResourcePolicyAnno] - if !ok { - remaining = append(remaining, m) - continue - } - - resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType)) - if resourcePolicyType == kube.KeepPolicy { - keep = append(keep, m) - } - - } - return keep, remaining -} diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go deleted file mode 100644 index dda8c700b..000000000 --- a/pkg/action/rollback.go +++ /dev/null @@ -1,246 +0,0 @@ -/* -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 action - -import ( - "bytes" - "fmt" - "strings" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// Rollback is the action for rolling back to a given release. -// -// It provides the implementation of 'helm rollback'. -type Rollback struct { - cfg *Configuration - - Version int - Timeout time.Duration - Wait bool - WaitForJobs bool - DisableHooks bool - DryRun bool - Recreate bool // will (if true) recreate pods after a rollback. - Force bool // will (if true) force resource upgrade through uninstall/recreate if needed - CleanupOnFail bool - MaxHistory int // MaxHistory limits the maximum number of revisions saved per release -} - -// NewRollback creates a new Rollback object with the given configuration. -func NewRollback(cfg *Configuration) *Rollback { - return &Rollback{ - cfg: cfg, - } -} - -// Run executes 'helm rollback' against the given release. -func (r *Rollback) Run(name string) error { - if err := r.cfg.KubeClient.IsReachable(); err != nil { - return err - } - - r.cfg.Releases.MaxHistory = r.MaxHistory - - r.cfg.Log("preparing rollback of %s", name) - currentRelease, targetRelease, err := r.prepareRollback(name) - if err != nil { - return err - } - - if !r.DryRun { - r.cfg.Log("creating rolled back release for %s", name) - if err := r.cfg.Releases.Create(targetRelease); err != nil { - return err - } - } - - r.cfg.Log("performing rollback of %s", name) - if _, err := r.performRollback(currentRelease, targetRelease); err != nil { - return err - } - - if !r.DryRun { - r.cfg.Log("updating status for rolled back release for %s", name) - if err := r.cfg.Releases.Update(targetRelease); err != nil { - return err - } - } - return nil -} - -// prepareRollback finds the previous release and prepares a new release object with -// the previous release's configuration -func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) - } - - if r.Version < 0 { - return nil, nil, errInvalidRevision - } - - currentRelease, err := r.cfg.Releases.Last(name) - if err != nil { - return nil, nil, err - } - - previousVersion := r.Version - if r.Version == 0 { - previousVersion = currentRelease.Version - 1 - } - - r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) - - previousRelease, err := r.cfg.Releases.Get(name, previousVersion) - if err != nil { - return nil, nil, err - } - - // Store a new release object with previous release's configuration - targetRelease := &release.Release{ - Name: name, - Namespace: currentRelease.Namespace, - Chart: previousRelease.Chart, - Config: previousRelease.Config, - Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: helmtime.Now(), - Status: release.StatusPendingRollback, - Notes: previousRelease.Info.Notes, - // Because we lose the reference to previous version elsewhere, we set the - // message here, and only override it later if we experience failure. - Description: fmt.Sprintf("Rollback to %d", previousVersion), - }, - Version: currentRelease.Version + 1, - Manifest: previousRelease.Manifest, - Hooks: previousRelease.Hooks, - } - - return currentRelease, targetRelease, nil -} - -func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { - if r.DryRun { - r.cfg.Log("dry run for %s", targetRelease.Name) - return targetRelease, nil - } - - current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false) - if err != nil { - return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") - } - target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false) - if err != nil { - return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") - } - - // pre-rollback hooks - if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { - return targetRelease, err - } - } else { - r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) - } - - // It is safe to use "force" here because these are resources currently rendered by the chart. - err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true)) - if err != nil { - return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release") - } - results, err := r.cfg.KubeClient.Update(current, target, r.Force) - - if err != nil { - msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) - r.cfg.Log("warning: %s", msg) - currentRelease.Info.Status = release.StatusSuperseded - targetRelease.Info.Status = release.StatusFailed - targetRelease.Info.Description = msg - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - if r.CleanupOnFail { - r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) - _, errs := r.cfg.KubeClient.Delete(results.Created) - if errs != nil { - var errorList []string - for _, e := range errs { - errorList = append(errorList, e.Error()) - } - return targetRelease, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original rollback error: %s", err) - } - r.cfg.Log("Resource cleanup complete") - } - return targetRelease, err - } - - if r.Recreate { - // NOTE: Because this is not critical for a release to succeed, we just - // log if an error occurs and continue onward. If we ever introduce log - // levels, we should make these error level logs so users are notified - // that they'll need to go do the cleanup on their own - if err := recreate(r.cfg, results.Updated); err != nil { - r.cfg.Log(err.Error()) - } - } - - if r.Wait { - if r.WaitForJobs { - if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) - } - } else { - if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) - } - } - } - - // post-rollback hooks - if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { - return targetRelease, err - } - } - - deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { - return nil, err - } - // Supersede all previous deployments, see issue #2941. - for _, rel := range deployed { - r.cfg.Log("superseding previous deployment %d", rel.Version) - rel.Info.Status = release.StatusSuperseded - r.cfg.recordRelease(rel) - } - - targetRelease.Info.Status = release.StatusDeployed - - return targetRelease, nil -} diff --git a/pkg/action/show.go b/pkg/action/show.go deleted file mode 100644 index 9ba85234d..000000000 --- a/pkg/action/show.go +++ /dev/null @@ -1,156 +0,0 @@ -/* -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 action - -import ( - "bytes" - "fmt" - "strings" - - "github.com/pkg/errors" - "k8s.io/cli-runtime/pkg/printers" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" -) - -// ShowOutputFormat is the format of the output of `helm show` -type ShowOutputFormat string - -const ( - // ShowAll is the format which shows all the information of a chart - ShowAll ShowOutputFormat = "all" - // ShowChart is the format which only shows the chart's definition - ShowChart ShowOutputFormat = "chart" - // ShowValues is the format which only shows the chart's values - ShowValues ShowOutputFormat = "values" - // ShowReadme is the format which only shows the chart's README - ShowReadme ShowOutputFormat = "readme" - // ShowCRDs is the format which only shows the chart's CRDs - ShowCRDs ShowOutputFormat = "crds" -) - -var readmeFileNames = []string{"readme.md", "readme.txt", "readme"} - -func (o ShowOutputFormat) String() string { - return string(o) -} - -// Show is the action for checking a given release's information. -// -// It provides the implementation of 'helm show' and its respective subcommands. -type Show struct { - ChartPathOptions - Devel bool - OutputFormat ShowOutputFormat - JSONPathTemplate string - chart *chart.Chart // for testing -} - -// NewShow creates a new Show object with the given configuration. -// Deprecated: Use NewShowWithConfig -// TODO Helm 4: Fold NewShowWithConfig back into NewShow -func NewShow(output ShowOutputFormat) *Show { - return &Show{ - OutputFormat: output, - } -} - -// NewShowWithConfig creates a new Show object with the given configuration. -func NewShowWithConfig(output ShowOutputFormat, cfg *Configuration) *Show { - sh := &Show{ - OutputFormat: output, - } - sh.ChartPathOptions.registryClient = cfg.RegistryClient - - return sh -} - -// Run executes 'helm show' against the given release. -func (s *Show) Run(chartpath string) (string, error) { - if s.chart == nil { - chrt, err := loader.Load(chartpath) - if err != nil { - return "", err - } - s.chart = chrt - } - cf, err := yaml.Marshal(s.chart.Metadata) - if err != nil { - return "", err - } - - var out strings.Builder - if s.OutputFormat == ShowChart || s.OutputFormat == ShowAll { - fmt.Fprintf(&out, "%s\n", cf) - } - - if (s.OutputFormat == ShowValues || s.OutputFormat == ShowAll) && s.chart.Values != nil { - if s.OutputFormat == ShowAll { - fmt.Fprintln(&out, "---") - } - if s.JSONPathTemplate != "" { - printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate) - if err != nil { - return "", errors.Wrapf(err, "error parsing jsonpath %s", s.JSONPathTemplate) - } - printer.Execute(&out, s.chart.Values) - } else { - for _, f := range s.chart.Raw { - if f.Name == chartutil.ValuesfileName { - fmt.Fprintln(&out, string(f.Data)) - } - } - } - } - - if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll { - readme := findReadme(s.chart.Files) - if readme != nil { - if s.OutputFormat == ShowAll { - fmt.Fprintln(&out, "---") - } - fmt.Fprintf(&out, "%s\n", readme.Data) - } - } - - if s.OutputFormat == ShowCRDs || s.OutputFormat == ShowAll { - crds := s.chart.CRDObjects() - if len(crds) > 0 { - if s.OutputFormat == ShowAll && !bytes.HasPrefix(crds[0].File.Data, []byte("---")) { - fmt.Fprintln(&out, "---") - } - for _, crd := range crds { - fmt.Fprintf(&out, "%s\n", string(crd.File.Data)) - } - } - } - return out.String(), nil -} - -func findReadme(files []*chart.File) (file *chart.File) { - for _, file := range files { - for _, n := range readmeFileNames { - if strings.EqualFold(file.Name, n) { - return file - } - } - } - return nil -} diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go deleted file mode 100644 index 8b617ea85..000000000 --- a/pkg/action/show_test.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -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 action - -import ( - "testing" - - "helm.sh/helm/v3/pkg/chart" -) - -func TestShow(t *testing.T) { - config := actionConfigFixture(t) - client := NewShowWithConfig(ShowAll, config) - client.chart = &chart.Chart{ - Metadata: &chart.Metadata{Name: "alpine"}, - Files: []*chart.File{ - {Name: "README.md", Data: []byte("README\n")}, - {Name: "crds/ignoreme.txt", Data: []byte("error")}, - {Name: "crds/foo.yaml", Data: []byte("---\nfoo\n")}, - {Name: "crds/bar.json", Data: []byte("---\nbar\n")}, - }, - Raw: []*chart.File{ - {Name: "values.yaml", Data: []byte("VALUES\n")}, - }, - Values: map[string]interface{}{}, - } - - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - expect := `name: alpine - ---- -VALUES - ---- -README - ---- -foo - ---- -bar - -` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} - -func TestShowNoValues(t *testing.T) { - client := NewShow(ShowAll) - client.chart = new(chart.Chart) - - // Regression tests for missing values. See issue #1024. - client.OutputFormat = ShowValues - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - if len(output) != 0 { - t.Errorf("expected empty values buffer, got %s", output) - } -} - -func TestShowValuesByJsonPathFormat(t *testing.T) { - client := NewShow(ShowValues) - client.JSONPathTemplate = "{$.nestedKey.simpleKey}" - client.chart = buildChart(withSampleValues()) - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - expect := "simpleValue" - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} - -func TestShowCRDs(t *testing.T) { - client := NewShow(ShowCRDs) - client.chart = &chart.Chart{ - Metadata: &chart.Metadata{Name: "alpine"}, - Files: []*chart.File{ - {Name: "crds/ignoreme.txt", Data: []byte("error")}, - {Name: "crds/foo.yaml", Data: []byte("---\nfoo\n")}, - {Name: "crds/bar.json", Data: []byte("---\nbar\n")}, - }, - } - - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - expect := `--- -foo - ---- -bar - -` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} - -func TestShowNoReadme(t *testing.T) { - client := NewShow(ShowAll) - client.chart = &chart.Chart{ - Metadata: &chart.Metadata{Name: "alpine"}, - Files: []*chart.File{ - {Name: "crds/ignoreme.txt", Data: []byte("error")}, - {Name: "crds/foo.yaml", Data: []byte("---\nfoo\n")}, - {Name: "crds/bar.json", Data: []byte("---\nbar\n")}, - }, - } - - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - expect := `name: alpine - ---- -foo - ---- -bar - -` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} diff --git a/pkg/action/status.go b/pkg/action/status.go deleted file mode 100644 index 1c556e28d..000000000 --- a/pkg/action/status.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -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 action - -import ( - "helm.sh/helm/v3/pkg/release" -) - -// Status is the action for checking the deployment status of releases. -// -// It provides the implementation of 'helm status'. -type Status struct { - cfg *Configuration - - Version int - - // If true, display description to output format, - // only affect print type table. - // TODO Helm 4: Remove this flag and output the description by default. - ShowDescription bool -} - -// NewStatus creates a new Status object with the given configuration. -func NewStatus(cfg *Configuration) *Status { - return &Status{ - cfg: cfg, - } -} - -// Run executes 'helm status' against the given release. -func (s *Status) Run(name string) (*release.Release, error) { - if err := s.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - return s.cfg.releaseContent(name, s.Version) -} diff --git a/pkg/action/testdata/charts/chart-missing-deps/Chart.yaml b/pkg/action/testdata/charts/chart-missing-deps/Chart.yaml deleted file mode 100755 index ba10ee803..000000000 --- a/pkg/action/testdata/charts/chart-missing-deps/Chart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -name: chart-with-missing-deps -version: 2.1.8 diff --git a/pkg/action/testdata/charts/chart-missing-deps/requirements.lock b/pkg/action/testdata/charts/chart-missing-deps/requirements.lock deleted file mode 100755 index dcda2b142..000000000 --- a/pkg/action/testdata/charts/chart-missing-deps/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: mariadb - repository: https://charts.helm.sh/stable/ - version: 4.3.1 -digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 -generated: 2018-08-02T22:07:51.905271776Z diff --git a/pkg/action/testdata/charts/chart-missing-deps/requirements.yaml b/pkg/action/testdata/charts/chart-missing-deps/requirements.yaml deleted file mode 100755 index fef7d0b7f..000000000 --- a/pkg/action/testdata/charts/chart-missing-deps/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- name: mariadb - version: 4.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - wordpress-database diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz b/pkg/action/testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz deleted file mode 100644 index 7a22b1d82..000000000 Binary files a/pkg/action/testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml b/pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml deleted file mode 100755 index 1d16590b6..000000000 --- a/pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -name: chart-with-compressed-dependencies -version: 2.1.8 diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/charts/mariadb-4.3.1.tgz b/pkg/action/testdata/charts/chart-with-compressed-dependencies/charts/mariadb-4.3.1.tgz deleted file mode 100644 index 5b38fa1c3..000000000 Binary files a/pkg/action/testdata/charts/chart-with-compressed-dependencies/charts/mariadb-4.3.1.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock b/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock deleted file mode 100755 index dcda2b142..000000000 --- a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: mariadb - repository: https://charts.helm.sh/stable/ - version: 4.3.1 -digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 -generated: 2018-08-02T22:07:51.905271776Z diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml b/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml deleted file mode 100755 index fef7d0b7f..000000000 --- a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- name: mariadb - version: 4.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - wordpress-database diff --git a/pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml b/pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml deleted file mode 100644 index d3458f6a2..000000000 --- a/pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: chart-with-no-templates-dir -description: an example chart -version: 199.44.12345-Alpha.1+cafe009 -icon: http://riverrun.io diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml b/pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml deleted file mode 100644 index 395d24f6a..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -description: Empty testing chart -home: https://k8s.io/helm -name: empty -sources: -- https://github.com/kubernetes/helm -version: 0.1.0 diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml b/pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml deleted file mode 100644 index c80812f6e..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml +++ /dev/null @@ -1 +0,0 @@ -# This file is intentionally blank diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json b/pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json deleted file mode 100644 index 4df89bbe8..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "addresses": { - "description": "List of addresses", - "items": { - "properties": { - "city": { - "type": "string" - }, - "number": { - "type": "number" - }, - "street": { - "type": "string" - } - }, - "type": "object" - }, - "type": "array" - }, - "age": { - "description": "Age", - "minimum": 0, - "type": "integer" - }, - "employmentInfo": { - "properties": { - "salary": { - "minimum": 0, - "type": "number" - }, - "title": { - "type": "string" - } - }, - "required": [ - "salary" - ], - "type": "object" - }, - "firstname": { - "description": "First name", - "type": "string" - }, - "lastname": { - "type": "string" - }, - "likesCoffee": { - "type": "boolean" - }, - "phoneNumbers": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "firstname", - "lastname", - "addresses", - "employmentInfo" - ], - "title": "Values", - "type": "object" -} diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/values.yaml b/pkg/action/testdata/charts/chart-with-schema-negative/values.yaml deleted file mode 100644 index 5a1250bff..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/values.yaml +++ /dev/null @@ -1,14 +0,0 @@ -firstname: John -lastname: Doe -age: -5 -likesCoffee: true -addresses: - - city: Springfield - street: Main - number: 12345 - - city: New York - street: Broadway - number: 67890 -phoneNumbers: - - "(888) 888-8888" - - "(555) 555-5555" diff --git a/pkg/action/testdata/charts/chart-with-schema/Chart.yaml b/pkg/action/testdata/charts/chart-with-schema/Chart.yaml deleted file mode 100644 index 395d24f6a..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -description: Empty testing chart -home: https://k8s.io/helm -name: empty -sources: -- https://github.com/kubernetes/helm -version: 0.1.0 diff --git a/pkg/action/testdata/charts/chart-with-schema/extra-values.yaml b/pkg/action/testdata/charts/chart-with-schema/extra-values.yaml deleted file mode 100644 index 76c290c4f..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/extra-values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -age: -5 -employmentInfo: null diff --git a/pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml b/pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml deleted file mode 100644 index c80812f6e..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml +++ /dev/null @@ -1 +0,0 @@ -# This file is intentionally blank diff --git a/pkg/action/testdata/charts/chart-with-schema/values.schema.json b/pkg/action/testdata/charts/chart-with-schema/values.schema.json deleted file mode 100644 index 4df89bbe8..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/values.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "addresses": { - "description": "List of addresses", - "items": { - "properties": { - "city": { - "type": "string" - }, - "number": { - "type": "number" - }, - "street": { - "type": "string" - } - }, - "type": "object" - }, - "type": "array" - }, - "age": { - "description": "Age", - "minimum": 0, - "type": "integer" - }, - "employmentInfo": { - "properties": { - "salary": { - "minimum": 0, - "type": "number" - }, - "title": { - "type": "string" - } - }, - "required": [ - "salary" - ], - "type": "object" - }, - "firstname": { - "description": "First name", - "type": "string" - }, - "lastname": { - "type": "string" - }, - "likesCoffee": { - "type": "boolean" - }, - "phoneNumbers": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "firstname", - "lastname", - "addresses", - "employmentInfo" - ], - "title": "Values", - "type": "object" -} diff --git a/pkg/action/testdata/charts/chart-with-schema/values.yaml b/pkg/action/testdata/charts/chart-with-schema/values.yaml deleted file mode 100644 index 042dea664..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -firstname: John -lastname: Doe -age: 25 -likesCoffee: true -employmentInfo: - title: Software Developer - salary: 100000 -addresses: - - city: Springfield - street: Main - number: 12345 - - city: New York - street: Broadway - number: 67890 -phoneNumbers: - - "(888) 888-8888" - - "(555) 555-5555" diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz deleted file mode 100644 index ad9e68179..000000000 Binary files a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/.helmignore b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/.helmignore deleted file mode 100755 index e2cf7941f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/.helmignore +++ /dev/null @@ -1,5 +0,0 @@ -.git -# OWNERS file for Kubernetes -OWNERS -# example production yaml -values-production.yaml \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/Chart.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/Chart.yaml deleted file mode 100755 index 4d8569c89..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/Chart.yaml +++ /dev/null @@ -1,20 +0,0 @@ -appVersion: 4.9.8 -description: Web publishing platform for building blogs and websites. -engine: gotpl -home: http://www.wordpress.com/ -icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png -keywords: -- wordpress -- cms -- blog -- http -- web -- application -- php -maintainers: -- email: containers@bitnami.com - name: bitnami-bot -name: chart-with-uncompressed-dependencies -sources: -- https://github.com/bitnami/bitnami-docker-wordpress -version: 2.1.8 diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/README.md b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/README.md deleted file mode 100755 index 341a1ad93..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WordPress - -This is a testing mock, and is not operational. diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/.helmignore b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/.helmignore deleted file mode 100755 index 6b8710a71..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/.helmignore +++ /dev/null @@ -1 +0,0 @@ -.git diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/Chart.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/Chart.yaml deleted file mode 100755 index cefc15836..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -appVersion: 10.1.34 -description: Fast, reliable, scalable, and easy to use open-source relational database - system. MariaDB Server is intended for mission-critical, heavy-load production systems - as well as for embedding into mass-deployed software. Highly available MariaDB cluster. -engine: gotpl -home: https://mariadb.org -icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png -keywords: -- mariadb -- mysql -- database -- sql -- prometheus -maintainers: -- email: containers@bitnami.com - name: bitnami-bot -name: mariadb -sources: -- https://github.com/bitnami/bitnami-docker-mariadb -- https://github.com/prometheus/mysqld_exporter -version: 4.3.1 diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/README.md b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/README.md deleted file mode 100755 index 3463b8b6d..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# MariaDB - -[MariaDB](https://mariadb.org) is one of the most popular database servers in the world. It’s made by the original developers of MySQL and guaranteed to stay open source. Notable users include Wikipedia, Facebook and Google. - -MariaDB is developed as open source software and as a relational database it provides an SQL interface for accessing data. The latest versions of MariaDB also include GIS and JSON features. - -## TL;DR - -```bash -$ helm install stable/mariadb -``` - -## Introduction - -This chart bootstraps a [MariaDB](https://github.com/bitnami/bitnami-docker-mariadb) replication cluster deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. - -## Prerequisites - -- Kubernetes 1.4+ with Beta APIs enabled -- PV provisioner support in the underlying infrastructure - -## Installing the Chart - -To install the chart with the release name `my-release`: - -```bash -$ helm install --name my-release stable/mariadb -``` - -The command deploys MariaDB on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. - -> **Tip**: List all releases using `helm list` - -## Uninstalling the Chart - -To uninstall/delete the `my-release` deployment: - -```bash -$ helm delete my-release -``` - -The command removes all the Kubernetes components associated with the chart and deletes the release. - -## Configuration - -The following table lists the configurable parameters of the MariaDB chart and their default values. - -| Parameter | Description | Default | -|-------------------------------------------|-----------------------------------------------------|-------------------------------------------------------------------| -| `image.registry` | MariaDB image registry | `docker.io` | -| `image.repository` | MariaDB Image name | `bitnami/mariadb` | -| `image.tag` | MariaDB Image tag | `{VERSION}` | -| `image.pullPolicy` | MariaDB image pull policy | `Always` if `imageTag` is `latest`, else `IfNotPresent` | -| `image.pullSecrets` | Specify image pull secrets | `nil` (does not add image pull secrets to deployed pods) | -| `service.type` | Kubernetes service type | `ClusterIP` | -| `service.port` | MySQL service port | `3306` | -| `rootUser.password` | Password for the `root` user | _random 10 character alphanumeric string_ | -| `rootUser.forcePassword` | Force users to specify a password | `false` | -| `db.user` | Username of new user to create | `nil` | -| `db.password` | Password for the new user | _random 10 character alphanumeric string if `db.user` is defined_ | -| `db.name` | Name for new database to create | `my_database` | -| `replication.enabled` | MariaDB replication enabled | `true` | -| `replication.user` | MariaDB replication user | `replicator` | -| `replication.password` | MariaDB replication user password | _random 10 character alphanumeric string_ | -| `master.antiAffinity` | Master pod anti-affinity policy | `soft` | -| `master.persistence.enabled` | Enable persistence using a `PersistentVolumeClaim` | `true` | -| `master.persistence.annotations` | Persistent Volume Claim annotations | `{}` | -| `master.persistence.storageClass` | Persistent Volume Storage Class | `` | -| `master.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | -| `master.persistence.size` | Persistent Volume Size | `8Gi` | -| `master.config` | Config file for the MariaDB Master server | `_default values in the values.yaml file_` | -| `master.resources` | CPU/Memory resource requests/limits for master node | `{}` | -| `master.livenessProbe.enabled` | Turn on and off liveness probe (master) | `true` | -| `master.livenessProbe.initialDelaySeconds`| Delay before liveness probe is initiated (master) | `120` | -| `master.livenessProbe.periodSeconds` | How often to perform the probe (master) | `10` | -| `master.livenessProbe.timeoutSeconds` | When the probe times out (master) | `1` | -| `master.livenessProbe.successThreshold` | Minimum consecutive successes for the probe (master)| `1` | -| `master.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe (master) | `3` | -| `master.readinessProbe.enabled` | Turn on and off readiness probe (master) | `true` | -| `master.readinessProbe.initialDelaySeconds`| Delay before readiness probe is initiated (master) | `15` | -| `master.readinessProbe.periodSeconds` | How often to perform the probe (master) | `10` | -| `master.readinessProbe.timeoutSeconds` | When the probe times out (master) | `1` | -| `master.readinessProbe.successThreshold` | Minimum consecutive successes for the probe (master)| `1` | -| `master.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe (master) | `3` | -| `slave.replicas` | Desired number of slave replicas | `1` | -| `slave.antiAffinity` | Slave pod anti-affinity policy | `soft` | -| `slave.persistence.enabled` | Enable persistence using a `PersistentVolumeClaim` | `true` | -| `slave.persistence.annotations` | Persistent Volume Claim annotations | `{}` | -| `slave.persistence.storageClass` | Persistent Volume Storage Class | `` | -| `slave.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | -| `slave.persistence.size` | Persistent Volume Size | `8Gi` | -| `slave.config` | Config file for the MariaDB Slave replicas | `_default values in the values.yaml file_` | -| `slave.resources` | CPU/Memory resource requests/limits for slave node | `{}` | -| `slave.livenessProbe.enabled` | Turn on and off liveness probe (slave) | `true` | -| `slave.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (slave) | `120` | -| `slave.livenessProbe.periodSeconds` | How often to perform the probe (slave) | `10` | -| `slave.livenessProbe.timeoutSeconds` | When the probe times out (slave) | `1` | -| `slave.livenessProbe.successThreshold` | Minimum consecutive successes for the probe (slave) | `1` | -| `slave.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe (slave) | `3` | -| `slave.readinessProbe.enabled` | Turn on and off readiness probe (slave) | `true` | -| `slave.readinessProbe.initialDelaySeconds`| Delay before readiness probe is initiated (slave) | `15` | -| `slave.readinessProbe.periodSeconds` | How often to perform the probe (slave) | `10` | -| `slave.readinessProbe.timeoutSeconds` | When the probe times out (slave) | `1` | -| `slave.readinessProbe.successThreshold` | Minimum consecutive successes for the probe (slave) | `1` | -| `slave.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe (slave) | `3` | -| `metrics.enabled` | Start a side-car prometheus exporter | `false` | -| `metrics.image.registry` | Exporter image registry | `docker.io` | -`metrics.image.repository` | Exporter image name | `prom/mysqld-exporter` | -| `metrics.image.tag` | Exporter image tag | `v0.10.0` | -| `metrics.image.pullPolicy` | Exporter image pull policy | `IfNotPresent` | -| `metrics.resources` | Exporter resource requests/limit | `nil` | - -The above parameters map to the env variables defined in [bitnami/mariadb](http://github.com/bitnami/bitnami-docker-mariadb). For more information please refer to the [bitnami/mariadb](http://github.com/bitnami/bitnami-docker-mariadb) image documentation. - -Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, - -```bash -$ helm install --name my-release \ - --set root.password=secretpassword,user.database=app_database \ - stable/mariadb -``` - -The above command sets the MariaDB `root` account password to `secretpassword`. Additionally it creates a database named `my_database`. - -Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, - -```bash -$ helm install --name my-release -f values.yaml stable/mariadb -``` - -> **Tip**: You can use the default [values.yaml](values.yaml) - -## Initialize a fresh instance - -The [Bitnami MariaDB](https://github.com/bitnami/bitnami-docker-mariadb) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, they must be located inside the chart folder `files/docker-entrypoint-initdb.d` so they can be consumed as a ConfigMap. - -The allowed extensions are `.sh`, `.sql` and `.sql.gz`. - -## Persistence - -The [Bitnami MariaDB](https://github.com/bitnami/bitnami-docker-mariadb) image stores the MariaDB data and configurations at the `/bitnami/mariadb` path of the container. - -The chart mounts a [Persistent Volume](kubernetes.io/docs/user-guide/persistent-volumes/) volume at this location. The volume is created using dynamic volume provisioning, by default. An existing PersistentVolumeClaim can be defined. diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md deleted file mode 100755 index aaddde303..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md +++ /dev/null @@ -1,3 +0,0 @@ -You can copy here your custom .sh, .sql or .sql.gz file so they are executed during the first boot of the image. - -More info in the [bitnami-docker-mariadb](https://github.com/bitnami/bitnami-docker-mariadb#initializing-a-new-instance) repository. \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt deleted file mode 100755 index 4ba3b668a..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt +++ /dev/null @@ -1,35 +0,0 @@ - -Please be patient while the chart is being deployed - -Tip: - - Watch the deployment status using the command: kubectl get pods -w --namespace {{ .Release.Namespace }} -l release={{ .Release.Name }} - -Services: - - echo Master: {{ template "mariadb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }} -{{- if .Values.replication.enabled }} - echo Slave: {{ template "slave.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }} -{{- end }} - -Administrator credentials: - - Username: root - Password : $(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "mariadb.fullname" . }} -o jsonpath="{.data.mariadb-root-password}" | base64 --decode) - -To connect to your database - - 1. Run a pod that you can use as a client: - - kubectl run {{ template "mariadb.fullname" . }}-client --rm --tty -i --image {{ template "mariadb.image" . }} --namespace {{ .Release.Namespace }} --command -- bash - - 2. To connect to master service (read/write): - - mysql -h {{ template "mariadb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local -uroot -p {{ .Values.db.name }} - -{{- if .Values.replication.enabled }} - - 3. To connect to slave service (read-only): - - mysql -h {{ template "slave.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local -uroot -p {{ .Values.db.name }} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl deleted file mode 100755 index 5afe380ff..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl +++ /dev/null @@ -1,53 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "mariadb.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "mariadb.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "master.fullname" -}} -{{- if .Values.replication.enabled -}} -{{- printf "%s-%s" .Release.Name "mariadb-master" | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name "mariadb" | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - - -{{- define "slave.fullname" -}} -{{- printf "%s-%s" .Release.Name "mariadb-slave" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "mariadb.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Return the proper image name -*/}} -{{- define "mariadb.image" -}} -{{- $registryName := .Values.image.registry -}} -{{- $repositoryName := .Values.image.repository -}} -{{- $tag := .Values.image.tag | toString -}} -{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} - -{{/* -Return the proper image name -*/}} -{{- define "metrics.image" -}} -{{- $registryName := .Values.metrics.image.registry -}} -{{- $repositoryName := .Values.metrics.image.repository -}} -{{- $tag := .Values.metrics.image.tag | toString -}} -{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml deleted file mode 100755 index 7bb969627..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "master.fullname" . }}-init-scripts - labels: - app: {{ template "mariadb.name" . }} - component: "master" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -data: -{{ (.Files.Glob "files/docker-entrypoint-initdb.d/*").AsConfig | indent 2 }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml deleted file mode 100755 index 880a10198..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if .Values.master.config }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "master.fullname" . }} - labels: - app: {{ template "mariadb.name" . }} - component: "master" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -data: - my.cnf: |- -{{ .Values.master.config | indent 4 }} -{{- end -}} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml deleted file mode 100755 index 0d74f01ff..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml +++ /dev/null @@ -1,187 +0,0 @@ -apiVersion: apps/v1beta1 -kind: StatefulSet -metadata: - name: {{ template "master.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "master" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -spec: - serviceName: "{{ template "master.fullname" . }}" - replicas: 1 - updateStrategy: - type: RollingUpdate - template: - metadata: - labels: - app: "{{ template "mariadb.name" . }}" - component: "master" - release: "{{ .Release.Name }}" - chart: {{ template "mariadb.chart" . }} - spec: - securityContext: - runAsUser: 1001 - fsGroup: 1001 - {{- if eq .Values.master.antiAffinity "hard" }} - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: "kubernetes.io/hostname" - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- else if eq .Values.master.antiAffinity "soft" }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- end }} - {{- if .Values.image.pullSecrets }} - imagePullSecrets: - {{- range .Values.image.pullSecrets }} - - name: {{ . }} - {{- end}} - {{- end }} - containers: - - name: "mariadb" - image: {{ template "mariadb.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - {{- if .Values.db.user }} - - name: MARIADB_USER - value: "{{ .Values.db.user }}" - - name: MARIADB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-password - {{- end }} - - name: MARIADB_DATABASE - value: "{{ .Values.db.name }}" - {{- if .Values.replication.enabled }} - - name: MARIADB_REPLICATION_MODE - value: "master" - - name: MARIADB_REPLICATION_USER - value: "{{ .Values.replication.user }}" - - name: MARIADB_REPLICATION_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-replication-password - {{- end }} - ports: - - name: mysql - containerPort: 3306 - {{- if .Values.master.livenessProbe.enabled }} - livenessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.master.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.master.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.master.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.master.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.master.livenessProbe.failureThreshold }} - {{- end }} - {{- if .Values.master.readinessProbe.enabled }} - readinessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.master.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.master.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.master.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.master.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.master.readinessProbe.failureThreshold }} - {{- end }} - resources: -{{ toYaml .Values.master.resources | indent 10 }} - volumeMounts: - - name: data - mountPath: /bitnami/mariadb - - name: custom-init-scripts - mountPath: /docker-entrypoint-initdb.d -{{- if .Values.master.config }} - - name: config - mountPath: /opt/bitnami/mariadb/conf/my.cnf - subPath: my.cnf -{{- end }} -{{- if .Values.metrics.enabled }} - - name: metrics - image: {{ template "metrics.image" . }} - imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - command: [ 'sh', '-c', 'DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter' ] - ports: - - name: metrics - containerPort: 9104 - livenessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 15 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 5 - timeoutSeconds: 1 - resources: -{{ toYaml .Values.metrics.resources | indent 10 }} -{{- end }} - volumes: - {{- if .Values.master.config }} - - name: config - configMap: - name: {{ template "master.fullname" . }} - {{- end }} - - name: custom-init-scripts - configMap: - name: {{ template "master.fullname" . }}-init-scripts -{{- if .Values.master.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "master" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} - spec: - accessModes: - {{- range .Values.master.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.master.persistence.size | quote }} - {{- if .Values.master.persistence.storageClass }} - {{- if (eq "-" .Values.master.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.master.persistence.storageClass | quote }} - {{- end }} - {{- end }} -{{- else }} - - name: "data" - emptyDir: {} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml deleted file mode 100755 index 460ec328e..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "mariadb.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - component: "master" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -{{- if .Values.metrics.enabled }} - annotations: -{{ toYaml .Values.metrics.annotations | indent 4 }} -{{- end }} -spec: - type: {{ .Values.service.type }} - ports: - - name: mysql - port: {{ .Values.service.port }} - targetPort: mysql -{{- if .Values.metrics.enabled }} - - name: metrics - port: 9104 - targetPort: metrics -{{- end }} - selector: - app: "{{ template "mariadb.name" . }}" - component: "master" - release: "{{ .Release.Name }}" diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml deleted file mode 100755 index 17999d609..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if (not .Values.rootUser.existingSecret) -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "mariadb.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -type: Opaque -data: - {{- if .Values.rootUser.password }} - mariadb-root-password: "{{ .Values.rootUser.password | b64enc }}" - {{- else if (not .Values.rootUser.forcePassword) }} - mariadb-root-password: "{{ randAlphaNum 10 | b64enc }}" - {{ else }} - mariadb-root-password: {{ required "A MariaDB Root Password is required!" .Values.rootUser.password }} - {{- end }} - {{- if .Values.db.user }} - {{- if .Values.db.password }} - mariadb-password: "{{ .Values.db.password | b64enc }}" - {{- else if (not .Values.db.forcePassword) }} - mariadb-password: "{{ randAlphaNum 10 | b64enc }}" - {{- else }} - mariadb-password: {{ required "A MariaDB Database Password is required!" .Values.db.password }} - {{- end }} - {{- end }} - {{- if .Values.replication.enabled }} - {{- if .Values.replication.password }} - mariadb-replication-password: "{{ .Values.replication.password | b64enc }}" - {{- else if (not .Values.replication.forcePassword) }} - mariadb-replication-password: "{{ randAlphaNum 10 | b64enc }}" - {{- else }} - mariadb-replication-password: {{ required "A MariaDB Replication Password is required!" .Values.replication.password }} - {{- end }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml deleted file mode 100755 index 056cf5c07..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if and .Values.replication.enabled .Values.slave.config }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "slave.fullname" . }} - labels: - app: {{ template "mariadb.name" . }} - component: "slave" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -data: - my.cnf: |- -{{ .Values.slave.config | indent 4 }} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml deleted file mode 100755 index aa67d4a70..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml +++ /dev/null @@ -1,193 +0,0 @@ -{{- if .Values.replication.enabled }} -apiVersion: apps/v1beta1 -kind: StatefulSet -metadata: - name: {{ template "slave.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "slave" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -spec: - serviceName: "{{ template "slave.fullname" . }}" - replicas: {{ .Values.slave.replicas }} - updateStrategy: - type: RollingUpdate - template: - metadata: - labels: - app: "{{ template "mariadb.name" . }}" - component: "slave" - release: "{{ .Release.Name }}" - chart: {{ template "mariadb.chart" . }} - spec: - securityContext: - runAsUser: 1001 - fsGroup: 1001 - {{- if eq .Values.slave.antiAffinity "hard" }} - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: "kubernetes.io/hostname" - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- else if eq .Values.slave.antiAffinity "soft" }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- end }} - {{- if .Values.image.pullSecrets }} - imagePullSecrets: - {{- range .Values.image.pullSecrets }} - - name: {{ . }} - {{- end}} - {{- end }} - containers: - - name: "mariadb" - image: {{ template "mariadb.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - {{- if .Values.db.user }} - - name: MARIADB_USER - value: "{{ .Values.db.user }}" - - name: MARIADB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-password - {{- end }} - - name: MARIADB_DATABASE - value: "{{ .Values.db.name }}" - - name: MARIADB_REPLICATION_MODE - value: "slave" - - name: MARIADB_MASTER_HOST - value: {{ template "mariadb.fullname" . }} - - name: MARIADB_MASTER_PORT - value: "3306" - - name: MARIADB_MASTER_USER - value: "root" - - name: MARIADB_MASTER_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - - name: MARIADB_REPLICATION_USER - value: "{{ .Values.replication.user }}" - - name: MARIADB_REPLICATION_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-replication-password - ports: - - name: mysql - containerPort: 3306 - {{- if .Values.slave.livenessProbe.enabled }} - livenessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.slave.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.slave.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.slave.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.slave.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.slave.livenessProbe.failureThreshold }} - {{- end }} - {{- if .Values.slave.readinessProbe.enabled }} - readinessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.slave.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.slave.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.slave.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.slave.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.slave.readinessProbe.failureThreshold }} - {{- end }} - resources: -{{ toYaml .Values.slave.resources | indent 10 }} - volumeMounts: - - name: data - mountPath: /bitnami/mariadb -{{- if .Values.slave.config }} - - name: config - mountPath: /opt/bitnami/mariadb/conf/my.cnf - subPath: my.cnf -{{- end }} -{{- if .Values.metrics.enabled }} - - name: metrics - image: {{ template "metrics.image" . }} - imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - command: [ 'sh', '-c', 'DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter' ] - ports: - - name: metrics - containerPort: 9104 - livenessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 15 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 5 - timeoutSeconds: 1 - resources: -{{ toYaml .Values.metrics.resources | indent 10 }} -{{- end }} - volumes: - {{- if .Values.slave.config }} - - name: config - configMap: - name: {{ template "slave.fullname" . }} - {{- end }} -{{- if .Values.slave.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "slave" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} - spec: - accessModes: - {{- range .Values.slave.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.slave.persistence.size | quote }} - {{- if .Values.slave.persistence.storageClass }} - {{- if (eq "-" .Values.slave.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.slave.persistence.storageClass | quote }} - {{- end }} - {{- end }} -{{- else }} - - name: "data" - emptyDir: {} -{{- end }} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml deleted file mode 100755 index fa551371f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.replication.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "slave.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "slave" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -{{- if .Values.metrics.enabled }} - annotations: -{{ toYaml .Values.metrics.annotations | indent 4 }} -{{- end }} -spec: - type: {{ .Values.service.type }} - ports: - - name: mysql - port: {{ .Values.service.port }} - targetPort: mysql -{{- if .Values.metrics.enabled }} - - name: metrics - port: 9104 - targetPort: metrics -{{- end }} - selector: - app: "{{ template "mariadb.name" . }}" - component: "slave" - release: "{{ .Release.Name }}" -{{- end }} \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml deleted file mode 100755 index 99a85d4aa..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ template "mariadb.fullname" . }}-test-{{ randAlphaNum 5 | lower }}" - annotations: - "helm.sh/hook": test-success -spec: - initContainers: - - name: "test-framework" - image: "dduportal/bats:0.4.0" - command: - - "bash" - - "-c" - - | - set -ex - # copy bats to tools dir - cp -R /usr/local/libexec/ /tools/bats/ - volumeMounts: - - mountPath: /tools - name: tools - containers: - - name: mariadb-test - image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - command: ["/tools/bats/bats", "-t", "/tests/run.sh"] - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - volumeMounts: - - mountPath: /tests - name: tests - readOnly: true - - mountPath: /tools - name: tools - volumes: - - name: tests - configMap: - name: {{ template "mariadb.fullname" . }}-tests - - name: tools - emptyDir: {} - restartPolicy: Never diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml deleted file mode 100755 index 957f3fd1e..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "mariadb.fullname" . }}-tests -data: - run.sh: |- - @test "Testing MariaDB is accessible" { - mysql -h {{ template "mariadb.fullname" . }} -uroot -p$MARIADB_ROOT_PASSWORD -e 'show databases;' - } diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml deleted file mode 100755 index ce2414e9f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml +++ /dev/null @@ -1,233 +0,0 @@ -## Bitnami MariaDB image -## ref: https://hub.docker.com/r/bitnami/mariadb/tags/ -## -image: - registry: docker.io - repository: bitnami/mariadb - tag: 10.1.34-debian-9 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistrKeySecretName - -service: - ## Kubernetes service type - type: ClusterIP - port: 3306 - -rootUser: - ## MariaDB admin password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#setting-the-root-password-on-first-run - ## - password: - ## Use existing secret (ignores root, db and replication passwords) - # existingSecret: - ## - ## Option to force users to specify a password. That is required for 'helm upgrade' to work properly. - ## If it is not force, a random password will be generated. - forcePassword: false - -db: - ## MariaDB username and password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#creating-a-database-user-on-first-run - ## - user: - password: - ## Password is ignored if existingSecret is specified. - ## Database to create - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#creating-a-database-on-first-run - ## - name: my_database - ## Option to force users to specify a password. That is required for 'helm upgrade' to work properly. - ## If it is not force, a random password will be generated. - forcePassword: false - -replication: - ## Enable replication. This enables the creation of replicas of MariaDB. If false, only a - ## master deployment would be created - enabled: true - ## - ## MariaDB replication user - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#setting-up-a-replication-cluster - ## - user: replicator - ## MariaDB replication user password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#setting-up-a-replication-cluster - ## - password: - ## Password is ignored if existingSecret is specified. - ## - ## Option to force users to specify a password. That is required for 'helm upgrade' to work properly. - ## If it is not force, a random password will be generated. - forcePassword: false - -master: - antiAffinity: soft - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - ## If true, use a Persistent Volume Claim, If false, use emptyDir - ## - enabled: true - ## Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - ## Persistent Volume Claim annotations - ## - annotations: - ## Persistent Volume Access Mode - ## - accessModes: - - ReadWriteOnce - ## Persistent Volume size - ## - size: 8Gi - ## - - ## Configure MySQL with a custom my.cnf file - ## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file - ## - config: |- - [mysqld] - skip-name-resolve - explicit_defaults_for_timestamp - basedir=/opt/bitnami/mariadb - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - tmpdir=/opt/bitnami/mariadb/tmp - max_allowed_packet=16M - bind-address=0.0.0.0 - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - log-error=/opt/bitnami/mariadb/logs/mysqld.log - character-set-server=UTF8 - collation-server=utf8_general_ci - - [client] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - default-character-set=UTF8 - - [manager] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - - ## Configure master resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: {} - livenessProbe: - enabled: true - ## - ## Initializing the database could take some time - initialDelaySeconds: 120 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - enabled: true - initialDelaySeconds: 15 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - -slave: - replicas: 1 - antiAffinity: soft - persistence: - ## If true, use a Persistent Volume Claim, If false, use emptyDir - ## - enabled: true - # storageClass: "-" - annotations: - accessModes: - - ReadWriteOnce - ## Persistent Volume size - ## - size: 8Gi - ## - - ## Configure MySQL slave with a custom my.cnf file - ## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file - ## - config: |- - [mysqld] - skip-name-resolve - explicit_defaults_for_timestamp - basedir=/opt/bitnami/mariadb - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - tmpdir=/opt/bitnami/mariadb/tmp - max_allowed_packet=16M - bind-address=0.0.0.0 - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - log-error=/opt/bitnami/mariadb/logs/mysqld.log - character-set-server=UTF8 - collation-server=utf8_general_ci - - [client] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - default-character-set=UTF8 - - [manager] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - - ## - ## Configure slave resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: {} - livenessProbe: - enabled: true - ## - ## Initializing the database could take some time - initialDelaySeconds: 120 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - enabled: true - initialDelaySeconds: 15 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - -metrics: - enabled: false - image: - registry: docker.io - repository: prom/mysqld-exporter - tag: v0.10.0 - pullPolicy: IfNotPresent - resources: {} - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9104" diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock deleted file mode 100755 index dcda2b142..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: mariadb - repository: https://charts.helm.sh/stable/ - version: 4.3.1 -digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 -generated: 2018-08-02T22:07:51.905271776Z diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml deleted file mode 100755 index fef7d0b7f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- name: mariadb - version: 4.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - wordpress-database diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt deleted file mode 100755 index 75ed9b64f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -Placeholder. diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml deleted file mode 100755 index 3cb66dafd..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml +++ /dev/null @@ -1,254 +0,0 @@ -## Bitnami WordPress image version -## ref: https://hub.docker.com/r/bitnami/wordpress/tags/ -## -image: - registry: docker.io - repository: bitnami/wordpress - tag: 4.9.8-debian-9 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistrKeySecretName - -## User of the application -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressUsername: user - -## Application password -## Defaults to a random 10-character alphanumeric string if not set -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -# wordpressPassword: - -## Admin email -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressEmail: user@example.com - -## First name -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressFirstName: FirstName - -## Last name -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressLastName: LastName - -## Blog name -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressBlogName: User's Blog! - -## Table prefix -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressTablePrefix: wp_ - -## Set to `yes` to allow the container to be started with blank passwords -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -allowEmptyPassword: yes - -## SMTP mail delivery configuration -## ref: https://github.com/bitnami/bitnami-docker-wordpress/#smtp-configuration -## -# smtpHost: -# smtpPort: -# smtpUser: -# smtpPassword: -# smtpUsername: -# smtpProtocol: - -replicaCount: 1 - -externalDatabase: -## All of these values are only used when mariadb.enabled is set to false - ## Database host - host: localhost - - ## non-root Username for Wordpress Database - user: bn_wordpress - - ## Database password - password: "" - - ## Database name - database: bitnami_wordpress - - ## Database port number - port: 3306 - -## -## MariaDB chart configuration -## -mariadb: - ## Whether to deploy a mariadb server to satisfy the applications database requirements. To use an external database set this to false and configure the externalDatabase parameters - enabled: true - ## Disable MariaDB replication - replication: - enabled: false - - ## Create a database and a database user - ## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#creating-a-database-user-on-first-run - ## - db: - name: bitnami_wordpress - user: bn_wordpress - ## If the password is not specified, mariadb will generates a random password - ## - # password: - - ## MariaDB admin password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#setting-the-root-password-on-first-run - ## - # rootUser: - # password: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - master: - persistence: - enabled: true - ## mariadb data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessMode: ReadWriteOnce - size: 8Gi - -## Kubernetes configuration -## For minikube, set this to NodePort, elsewhere use LoadBalancer or ClusterIP -## -serviceType: LoadBalancer -## -## serviceType: NodePort -## nodePorts: -## http: -## https: -nodePorts: - http: "" - https: "" -## Enable client source IP preservation -## ref http://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip -## -serviceExternalTrafficPolicy: Cluster - -## Allow health checks to be pointed at the https port -healthcheckHttps: false - -## Configure extra options for liveness and readiness probes -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) -livenessProbe: - initialDelaySeconds: 120 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 -readinessProbe: - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 - -## Configure the ingress resource that allows you to access the -## Wordpress installation. Set up the URL -## ref: http://kubernetes.io/docs/user-guide/ingress/ -## -ingress: - ## Set to true to enable ingress record generation - enabled: false - - ## The list of hostnames to be covered with this ingress record. - ## Most likely this will be just one host, but in the event more hosts are needed, this is an array - hosts: - - name: wordpress.local - - ## Set this to true in order to enable TLS on the ingress record - ## A side effect of this will be that the backend wordpress service will be connected at port 443 - tls: false - - ## If TLS is set to true, you must declare what secret will store the key/certificate for TLS - tlsSecret: wordpress.local-tls - - ## Ingress annotations done as key:value pairs - ## If you're using kube-lego, you will want to add: - ## kubernetes.io/tls-acme: true - ## - ## For a full list of possible ingress annotations, please see - ## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/annotations.md - ## - ## If tls is set to true, annotation ingress.kubernetes.io/secure-backends: "true" will automatically be set - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: true - - secrets: - ## If you're providing your own certificates, please use this to add the certificates as secrets - ## key and certificate should start with -----BEGIN CERTIFICATE----- or - ## -----BEGIN RSA PRIVATE KEY----- - ## - ## name should line up with a tlsSecret set further up - ## If you're using kube-lego, this is unneeded, as it will create the secret for you if it is not set - ## - ## It is also possible to create and manage the certificates outside of this helm chart - ## Please see README.md for more information - # - name: wordpress.local-tls - # key: - # certificate: - -## Enable persistence using Persistent Volume Claims -## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ -## -persistence: - enabled: true - ## wordpress data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - ## - ## If you want to reuse an existing claim, you can pass the name of the PVC using - ## the existingClaim variable - # existingClaim: your-claim - accessMode: ReadWriteOnce - size: 10Gi - -## Configure resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: - requests: - memory: 512Mi - cpu: 300m - -## Node labels for pod assignment -## Ref: https://kubernetes.io/docs/user-guide/node-selection/ -## -nodeSelector: {} - -## Tolerations for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -## -tolerations: [] - -## Affinity for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity -## -affinity: {} diff --git a/pkg/action/testdata/charts/compressedchart-0.1.0.tar.gz b/pkg/action/testdata/charts/compressedchart-0.1.0.tar.gz deleted file mode 100644 index 3c9c24d76..000000000 Binary files a/pkg/action/testdata/charts/compressedchart-0.1.0.tar.gz and /dev/null differ diff --git a/pkg/action/testdata/charts/compressedchart-0.1.0.tgz b/pkg/action/testdata/charts/compressedchart-0.1.0.tgz deleted file mode 100644 index 3c9c24d76..000000000 Binary files a/pkg/action/testdata/charts/compressedchart-0.1.0.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/compressedchart-0.2.0.tgz b/pkg/action/testdata/charts/compressedchart-0.2.0.tgz deleted file mode 100644 index 16a644a79..000000000 Binary files a/pkg/action/testdata/charts/compressedchart-0.2.0.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/compressedchart-0.3.0.tgz b/pkg/action/testdata/charts/compressedchart-0.3.0.tgz deleted file mode 100644 index 051bd6fd9..000000000 Binary files a/pkg/action/testdata/charts/compressedchart-0.3.0.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/compressedchart-with-hyphens-0.1.0.tgz b/pkg/action/testdata/charts/compressedchart-with-hyphens-0.1.0.tgz deleted file mode 100644 index 379210a92..000000000 Binary files a/pkg/action/testdata/charts/compressedchart-with-hyphens-0.1.0.tgz and /dev/null differ diff --git a/pkg/action/testdata/charts/corrupted-compressed-chart.tgz b/pkg/action/testdata/charts/corrupted-compressed-chart.tgz deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/action/testdata/charts/decompressedchart/Chart.yaml b/pkg/action/testdata/charts/decompressedchart/Chart.yaml deleted file mode 100644 index 92ba4d88f..000000000 --- a/pkg/action/testdata/charts/decompressedchart/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: decompressedchart -version: 0.1.0 diff --git a/pkg/action/testdata/charts/decompressedchart/values.yaml b/pkg/action/testdata/charts/decompressedchart/values.yaml deleted file mode 100644 index a940d1fd9..000000000 --- a/pkg/action/testdata/charts/decompressedchart/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for decompressedchart. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. - name: my-decompressed-chart diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml deleted file mode 100644 index e33c97e8c..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -name: multiplecharts-lint-chart-1 -version: "1" -icon: "" \ No newline at end of file diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml deleted file mode 100644 index 88ebf2468..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -metadata: - name: multicharttest-chart1-configmap -data: - dat: | - {{ .Values.config | indent 4 }} diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml deleted file mode 100644 index aafb09e4b..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml +++ /dev/null @@ -1 +0,0 @@ -config: "Test" \ No newline at end of file diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml deleted file mode 100644 index b27de2754..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -name: multiplecharts-lint-chart-2 -version: "1" -icon: "" \ No newline at end of file diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml deleted file mode 100644 index 8484bfe6a..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -metadata: - name: multicharttest-chart2-configmap -data: - {{ toYaml .Values.config | indent 4 }} diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml deleted file mode 100644 index 9139f486e..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -config: - test: "Test" \ No newline at end of file diff --git a/pkg/action/testdata/charts/pre-release-chart-0.1.0-alpha.tgz b/pkg/action/testdata/charts/pre-release-chart-0.1.0-alpha.tgz deleted file mode 100644 index 5d5770fed..000000000 Binary files a/pkg/action/testdata/charts/pre-release-chart-0.1.0-alpha.tgz and /dev/null differ diff --git a/pkg/action/testdata/output/list-compressed-deps-tgz.txt b/pkg/action/testdata/output/list-compressed-deps-tgz.txt deleted file mode 100644 index 6cc526b70..000000000 --- a/pkg/action/testdata/output/list-compressed-deps-tgz.txt +++ /dev/null @@ -1,3 +0,0 @@ -NAME VERSION REPOSITORY STATUS -mariadb 4.x.x https://kubernetes-charts.storage.googleapis.com/ unpacked - diff --git a/pkg/action/testdata/output/list-compressed-deps.txt b/pkg/action/testdata/output/list-compressed-deps.txt deleted file mode 100644 index 08597f31e..000000000 --- a/pkg/action/testdata/output/list-compressed-deps.txt +++ /dev/null @@ -1,3 +0,0 @@ -NAME VERSION REPOSITORY STATUS -mariadb 4.x.x https://charts.helm.sh/stable/ ok - diff --git a/pkg/action/testdata/output/list-missing-deps.txt b/pkg/action/testdata/output/list-missing-deps.txt deleted file mode 100644 index 03051251e..000000000 --- a/pkg/action/testdata/output/list-missing-deps.txt +++ /dev/null @@ -1,3 +0,0 @@ -NAME VERSION REPOSITORY STATUS -mariadb 4.x.x https://charts.helm.sh/stable/ missing - diff --git a/pkg/action/testdata/output/list-uncompressed-deps-tgz.txt b/pkg/action/testdata/output/list-uncompressed-deps-tgz.txt deleted file mode 100644 index 6cc526b70..000000000 --- a/pkg/action/testdata/output/list-uncompressed-deps-tgz.txt +++ /dev/null @@ -1,3 +0,0 @@ -NAME VERSION REPOSITORY STATUS -mariadb 4.x.x https://kubernetes-charts.storage.googleapis.com/ unpacked - diff --git a/pkg/action/testdata/output/list-uncompressed-deps.txt b/pkg/action/testdata/output/list-uncompressed-deps.txt deleted file mode 100644 index bc59e825c..000000000 --- a/pkg/action/testdata/output/list-uncompressed-deps.txt +++ /dev/null @@ -1,3 +0,0 @@ -NAME VERSION REPOSITORY STATUS -mariadb 4.x.x https://charts.helm.sh/stable/ unpacked - diff --git a/pkg/action/testdata/rbac.txt b/pkg/action/testdata/rbac.txt deleted file mode 100644 index 0cb15b868..000000000 --- a/pkg/action/testdata/rbac.txt +++ /dev/null @@ -1,25 +0,0 @@ ---- -# Source: hello/templates/rbac -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: schedule-agents -rules: -- apiGroups: [""] - resources: ["pods", "pods/exec", "pods/log"] - verbs: ["*"] ---- -# Source: hello/templates/rbac -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: schedule-agents - namespace: spaced -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: schedule-agents -subjects: -- kind: ServiceAccount - name: schedule-agents - namespace: spaced diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go deleted file mode 100644 index 9dcbf19b0..000000000 --- a/pkg/action/uninstall.go +++ /dev/null @@ -1,226 +0,0 @@ -/* -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 action - -import ( - "strings" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// Uninstall is the action for uninstalling releases. -// -// It provides the implementation of 'helm uninstall'. -type Uninstall struct { - cfg *Configuration - - DisableHooks bool - DryRun bool - KeepHistory bool - Wait bool - Timeout time.Duration - Description string -} - -// NewUninstall creates a new Uninstall object with the given configuration. -func NewUninstall(cfg *Configuration) *Uninstall { - return &Uninstall{ - cfg: cfg, - } -} - -// Run uninstalls the given release. -func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) { - if err := u.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - if u.DryRun { - // In the dry run case, just see if the release exists - r, err := u.cfg.releaseContent(name, 0) - if err != nil { - return &release.UninstallReleaseResponse{}, err - } - return &release.UninstallReleaseResponse{Release: r}, nil - } - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("uninstall: Release name is invalid: %s", name) - } - - rels, err := u.cfg.Releases.History(name) - if err != nil { - return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name) - } - if len(rels) < 1 { - return nil, errMissingRelease - } - - releaseutil.SortByRevision(rels) - rel := rels[len(rels)-1] - - // TODO: Are there any cases where we want to force a delete even if it's - // already marked deleted? - if rel.Info.Status == release.StatusUninstalled { - if !u.KeepHistory { - if err := u.purgeReleases(rels...); err != nil { - return nil, errors.Wrap(err, "uninstall: Failed to purge the release") - } - return &release.UninstallReleaseResponse{Release: rel}, nil - } - return nil, errors.Errorf("the release named %q is already deleted", name) - } - - u.cfg.Log("uninstall: Deleting %s", name) - rel.Info.Status = release.StatusUninstalling - rel.Info.Deleted = helmtime.Now() - rel.Info.Description = "Deletion in progress (or silently failed)" - res := &release.UninstallReleaseResponse{Release: rel} - - if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { - return res, err - } - } else { - u.cfg.Log("delete hooks disabled for %s", name) - } - - // From here on out, the release is currently considered to be in StatusUninstalling - // state. - if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) - } - - deletedResources, kept, errs := u.deleteRelease(rel) - if errs != nil { - u.cfg.Log("uninstall: Failed to delete release: %s", errs) - return nil, errors.Errorf("failed to delete release: %s", name) - } - - if kept != "" { - kept = "These resources were kept due to the resource policy:\n" + kept - } - res.Info = kept - - if u.Wait { - if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok { - if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil { - errs = append(errs, err) - } - } - } - - if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { - errs = append(errs, err) - } - } - - rel.Info.Status = release.StatusUninstalled - if len(u.Description) > 0 { - rel.Info.Description = u.Description - } else { - rel.Info.Description = "Uninstallation complete" - } - - if !u.KeepHistory { - u.cfg.Log("purge requested for %s", name) - err := u.purgeReleases(rels...) - if err != nil { - errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release")) - } - - // Return the errors that occurred while deleting the release, if any - if len(errs) > 0 { - return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) - } - - return res, nil - } - - if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) - } - - if len(errs) > 0 { - return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) - } - return res, nil -} - -func (u *Uninstall) purgeReleases(rels ...*release.Release) error { - for _, rel := range rels { - if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil { - return err - } - } - return nil -} - -func joinErrors(errs []error) string { - es := make([]string, 0, len(errs)) - for _, e := range errs { - es = append(es, e.Error()) - } - return strings.Join(es, "; ") -} - -// deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process -func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) { - var errs []error - caps, err := u.cfg.getCapabilities() - if err != nil { - return nil, rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")} - } - - manifests := releaseutil.SplitManifests(rel.Manifest) - _, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder) - if err != nil { - // We could instead just delete everything in no particular order. - // FIXME: One way to delete at this point would be to try a label-based - // deletion. The problem with this is that we could get a false positive - // and delete something that was not legitimately part of this release. - return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")} - } - - filesToKeep, filesToDelete := filterManifestsToKeep(files) - var kept string - for _, f := range filesToKeep { - kept += "[" + f.Head.Kind + "] " + f.Head.Metadata.Name + "\n" - } - - var builder strings.Builder - for _, file := range filesToDelete { - builder.WriteString("\n---\n" + file.Content) - } - - resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) - if err != nil { - return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} - } - if len(resources) > 0 { - _, errs = u.cfg.KubeClient.Delete(resources) - } - return resources, kept, errs -} diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go deleted file mode 100644 index 9cc75520b..000000000 --- a/pkg/action/uninstall_test.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -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 action - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" -) - -func uninstallAction(t *testing.T) *Uninstall { - config := actionConfigFixture(t) - unAction := NewUninstall(config) - return unAction -} - -func TestUninstallRelease_deleteRelease(t *testing.T) { - is := assert.New(t) - - unAction := uninstallAction(t) - unAction.DisableHooks = true - unAction.DryRun = false - unAction.KeepHistory = true - - rel := releaseStub() - rel.Name = "keep-secret" - rel.Manifest = `{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "secret", - "annotations": { - "helm.sh/resource-policy": "keep" - } - }, - "type": "Opaque", - "data": { - "password": "password" - } - }` - unAction.cfg.Releases.Create(rel) - res, err := unAction.Run(rel.Name) - is.NoError(err) - expected := `These resources were kept due to the resource policy: -[Secret] secret -` - is.Contains(res.Info, expected) -} - -func TestUninstallRelease_Wait(t *testing.T) { - is := assert.New(t) - - unAction := uninstallAction(t) - unAction.DisableHooks = true - unAction.DryRun = false - unAction.Wait = true - - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Manifest = `{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "secret" - }, - "type": "Opaque", - "data": { - "password": "password" - } - }` - unAction.cfg.Releases.Create(rel) - failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("U timed out") - unAction.cfg.KubeClient = failer - res, err := unAction.Run(rel.Name) - is.Error(err) - is.Contains(err.Error(), "U timed out") - is.Equal(res.Release.Info.Status, release.StatusUninstalled) -} diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go deleted file mode 100644 index 690397d4a..000000000 --- a/pkg/action/upgrade.go +++ /dev/null @@ -1,574 +0,0 @@ -/* -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 action - -import ( - "bytes" - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/resource" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" -) - -// Upgrade is the action for upgrading releases. -// -// It provides the implementation of 'helm upgrade'. -type Upgrade struct { - cfg *Configuration - - ChartPathOptions - - // Install is a purely informative flag that indicates whether this upgrade was done in "install" mode. - // - // Applications may use this to determine whether this Upgrade operation was done as part of a - // pure upgrade (Upgrade.Install == false) or as part of an install-or-upgrade operation - // (Upgrade.Install == true). - // - // Setting this to `true` will NOT cause `Upgrade` to perform an install if the release does not exist. - // That process must be handled by creating an Install action directly. See cmd/upgrade.go for an - // example of how this flag is used. - Install bool - // Devel indicates that the operation is done in devel mode. - Devel bool - // Namespace is the namespace in which this operation should be performed. - Namespace string - // SkipCRDs skips installing CRDs when install flag is enabled during upgrade - SkipCRDs bool - // Timeout is the timeout for this operation - Timeout time.Duration - // Wait determines whether the wait operation should be performed after the upgrade is requested. - Wait bool - // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. - WaitForJobs bool - // DisableHooks disables hook processing if set to true. - DisableHooks bool - // DryRun controls whether the operation is prepared, but not executed. - // If `true`, the upgrade is prepared but not performed. - DryRun bool - // Force will, if set to `true`, ignore certain warnings and perform the upgrade anyway. - // - // This should be used with caution. - Force bool - // ResetValues will reset the values to the chart's built-ins rather than merging with existing. - ResetValues bool - // ReuseValues will re-use the user's last supplied values. - ReuseValues bool - // Recreate will (if true) recreate pods after a rollback. - Recreate bool - // MaxHistory limits the maximum number of revisions saved per release - MaxHistory int - // Atomic, if true, will roll back on failure. - Atomic bool - // CleanupOnFail will, if true, cause the upgrade to delete newly-created resources on a failed update. - CleanupOnFail bool - // SubNotes determines whether sub-notes are rendered in the chart. - SubNotes bool - // Description is the description of this operation - Description string - // PostRender is an optional post-renderer - // - // If this is non-nil, then after templates are rendered, they will be sent to the - // post renderer before sending to the Kubernetes API server. - PostRenderer postrender.PostRenderer - // DisableOpenAPIValidation controls whether OpenAPI validation is enforced. - DisableOpenAPIValidation bool - // Get missing dependencies - DependencyUpdate bool - // Lock to control raceconditions when the process receives a SIGTERM - Lock sync.Mutex -} - -type resultMessage struct { - r *release.Release - e error -} - -// NewUpgrade creates a new Upgrade object with the given configuration. -func NewUpgrade(cfg *Configuration) *Upgrade { - up := &Upgrade{ - cfg: cfg, - } - up.ChartPathOptions.registryClient = cfg.RegistryClient - - return up -} - -// Run executes the upgrade on the given release. -func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - ctx := context.Background() - return u.RunWithContext(ctx, name, chart, vals) -} - -// RunWithContext executes the upgrade on the given release with context. -func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - if err := u.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - // Make sure if Atomic is set, that wait is set as well. This makes it so - // the user doesn't have to specify both - u.Wait = u.Wait || u.Atomic - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("release name is invalid: %s", name) - } - u.cfg.Log("preparing upgrade for %s", name) - currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) - if err != nil { - return nil, err - } - - u.cfg.Releases.MaxHistory = u.MaxHistory - - u.cfg.Log("performing update for %s", name) - res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease) - if err != nil { - return res, err - } - - if !u.DryRun { - u.cfg.Log("updating status for upgraded release for %s", name) - if err := u.cfg.Releases.Update(upgradedRelease); err != nil { - return res, err - } - } - - return res, nil -} - -// prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) { - if chart == nil { - return nil, nil, errMissingChart - } - - // finds the last non-deleted release with the given name - lastRelease, err := u.cfg.Releases.Last(name) - if err != nil { - // to keep existing behavior of returning the "%q has no deployed releases" error when an existing release does not exist - if errors.Is(err, driver.ErrReleaseNotFound) { - return nil, nil, driver.NewErrNoDeployedReleases(name) - } - return nil, nil, err - } - - // Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock. - if lastRelease.Info.Status.IsPending() { - return nil, nil, errPending - } - - var currentRelease *release.Release - if lastRelease.Info.Status == release.StatusDeployed { - // no need to retrieve the last deployed release from storage as the last release is deployed - currentRelease = lastRelease - } else { - // finds the deployed release with the given name - currentRelease, err = u.cfg.Releases.Deployed(name) - if err != nil { - if errors.Is(err, driver.ErrNoDeployedReleases) && - (lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded) { - currentRelease = lastRelease - } else { - return nil, nil, err - } - } - } - - // determine if values will be reused - vals, err = u.reuseValues(chart, currentRelease, vals) - if err != nil { - return nil, nil, err - } - - if err := chartutil.ProcessDependencies(chart, vals); err != nil { - return nil, nil, err - } - - // Increment revision count. This is passed to templates, and also stored on - // the release object. - revision := lastRelease.Version + 1 - - options := chartutil.ReleaseOptions{ - Name: name, - Namespace: currentRelease.Namespace, - Revision: revision, - IsUpgrade: true, - } - - caps, err := u.cfg.getCapabilities() - if err != nil { - return nil, nil, err - } - valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps) - if err != nil { - return nil, nil, err - } - - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) - if err != nil { - return nil, nil, err - } - - // Store an upgraded release. - upgradedRelease := &release.Release{ - Name: name, - Namespace: currentRelease.Namespace, - Chart: chart, - Config: vals, - Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: Timestamper(), - Status: release.StatusPendingUpgrade, - Description: "Preparing upgrade", // This should be overwritten later. - }, - Version: revision, - Manifest: manifestDoc.String(), - Hooks: hooks, - } - - if len(notesTxt) > 0 { - upgradedRelease.Info.Notes = notesTxt - } - err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) - return currentRelease, upgradedRelease, err -} - -func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedRelease *release.Release) (*release.Release, error) { - current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false) - if err != nil { - // Checking for removed Kubernetes API error so can provide a more informative error message to the user - // Ref: https://github.com/helm/helm/issues/7219 - if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") { - return upgradedRelease, errors.Wrap(err, "current release manifest contains removed kubernetes api(s) for this "+ - "kubernetes version and it is therefore unable to build the kubernetes "+ - "objects for performing the diff. error from kubernetes") - } - return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") - } - target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) - if err != nil { - return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") - } - - // It is safe to use force only on target because these are resources currently rendered by the chart. - err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace, true)) - if err != nil { - return upgradedRelease, err - } - - // Do a basic diff using gvk + name to figure out what new resources are being created so we can validate they don't already exist - existingResources := make(map[string]bool) - for _, r := range current { - existingResources[objectKey(r)] = true - } - - var toBeCreated kube.ResourceList - for _, r := range target { - if !existingResources[objectKey(r)] { - toBeCreated = append(toBeCreated, r) - } - } - - toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace) - if err != nil { - return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update") - } - - toBeUpdated.Visit(func(r *resource.Info, err error) error { - if err != nil { - return err - } - current.Append(r) - return nil - }) - - if u.DryRun { - u.cfg.Log("dry run for %s", upgradedRelease.Name) - if len(u.Description) > 0 { - upgradedRelease.Info.Description = u.Description - } else { - upgradedRelease.Info.Description = "Dry run complete" - } - return upgradedRelease, nil - } - - u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name) - if err := u.cfg.Releases.Create(upgradedRelease); err != nil { - return nil, err - } - rChan := make(chan resultMessage) - ctxChan := make(chan resultMessage) - doneChan := make(chan interface{}) - defer close(doneChan) - go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease) - go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease) - select { - case result := <-rChan: - return result.r, result.e - case result := <-ctxChan: - return result.r, result.e - } -} - -// Function used to lock the Mutex, this is important for the case when the atomic flag is set. -// In that case the upgrade will finish before the rollback is finished so it is necessary to wait for the rollback to finish. -// The rollback will be trigger by the function failRelease -func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Release, created kube.ResourceList, err error) { - u.Lock.Lock() - if err != nil { - rel, err = u.failRelease(rel, created, err) - } - c <- resultMessage{r: rel, e: err} - u.Lock.Unlock() -} - -// Setup listener for SIGINT and SIGTERM -func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) { - select { - case <-ctx.Done(): - err := ctx.Err() - - // when the atomic flag is set the ongoing release finish first and doesn't give time for the rollback happens. - u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err) - case <-done: - return - } -} -func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release) { - // pre-upgrade hooks - - if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { - u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) - return - } - } else { - u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) - } - - results, err := u.cfg.KubeClient.Update(current, target, u.Force) - if err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - - if u.Recreate { - // NOTE: Because this is not critical for a release to succeed, we just - // log if an error occurs and continue onward. If we ever introduce log - // levels, we should make these error level logs so users are notified - // that they'll need to go do the cleanup on their own - if err := recreate(u.cfg, results.Updated); err != nil { - u.cfg.Log(err.Error()) - } - } - - if u.Wait { - u.cfg.Log( - "waiting for release %s resources (created: %d updated: %d deleted: %d)", - upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted)) - if u.WaitForJobs { - if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - } else { - if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - } - } - - // post-upgrade hooks - if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) - return - } - } - - originalRelease.Info.Status = release.StatusSuperseded - u.cfg.recordRelease(originalRelease) - - upgradedRelease.Info.Status = release.StatusDeployed - if len(u.Description) > 0 { - upgradedRelease.Info.Description = u.Description - } else { - upgradedRelease.Info.Description = "Upgrade complete" - } - u.reportToPerformUpgrade(c, upgradedRelease, nil, nil) -} - -func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { - msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) - u.cfg.Log("warning: %s", msg) - - rel.Info.Status = release.StatusFailed - rel.Info.Description = msg - u.cfg.recordRelease(rel) - if u.CleanupOnFail && len(created) > 0 { - u.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(created)) - _, errs := u.cfg.KubeClient.Delete(created) - if errs != nil { - var errorList []string - for _, e := range errs { - errorList = append(errorList, e.Error()) - } - return rel, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original upgrade error: %s", err) - } - u.cfg.Log("Resource cleanup complete") - } - if u.Atomic { - u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release") - - // As a protection, get the last successful release before rollback. - // If there are no successful releases, bail out - hist := NewHistory(u.cfg) - fullHistory, herr := hist.Run(rel.Name) - if herr != nil { - return rel, errors.Wrapf(herr, "an error occurred while finding last successful release. original upgrade error: %s", err) - } - - // There isn't a way to tell if a previous release was successful, but - // generally failed releases do not get superseded unless the next - // release is successful, so this should be relatively safe - filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool { - return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed - }).Filter(fullHistory) - if len(filteredHistory) == 0 { - return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error") - } - - releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision) - - rollin := NewRollback(u.cfg) - rollin.Version = filteredHistory[0].Version - rollin.Wait = true - rollin.WaitForJobs = u.WaitForJobs - rollin.DisableHooks = u.DisableHooks - rollin.Recreate = u.Recreate - rollin.Force = u.Force - rollin.Timeout = u.Timeout - if rollErr := rollin.Run(rel.Name); rollErr != nil { - return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err) - } - return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name) - } - - return rel, err -} - -// reuseValues copies values from the current release to a new release if the -// new release does not have any values. -// -// If the request already has values, or if there are no values in the current -// release, this does nothing. -// -// This is skipped if the u.ResetValues flag is set, in which case the -// request values are not altered. -func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { - if u.ResetValues { - // If ResetValues is set, we completely ignore current.Config. - u.cfg.Log("resetting values to the chart's original version") - return newVals, nil - } - - // If the ReuseValues flag is set, we always copy the old values over the new config's values. - if u.ReuseValues { - u.cfg.Log("reusing the old release's values") - - // We have to regenerate the old coalesced values: - oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) - if err != nil { - return nil, errors.Wrap(err, "failed to rebuild old values") - } - - newVals = chartutil.CoalesceTables(newVals, current.Config) - - chart.Values = oldVals - - return newVals, nil - } - - if len(newVals) == 0 && len(current.Config) > 0 { - u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) - newVals = current.Config - } - return newVals, nil -} - -func validateManifest(c kube.Interface, manifest []byte, openAPIValidation bool) error { - _, err := c.Build(bytes.NewReader(manifest), openAPIValidation) - return err -} - -// recreate captures all the logic for recreating pods for both upgrade and -// rollback. If we end up refactoring rollback to use upgrade, this can just be -// made an unexported method on the upgrade action. -func recreate(cfg *Configuration, resources kube.ResourceList) error { - for _, res := range resources { - versioned := kube.AsVersioned(res) - selector, err := kube.SelectorsForObject(versioned) - if err != nil { - // If no selector is returned, it means this object is - // definitely not a pod, so continue onward - continue - } - - client, err := cfg.KubernetesClientSet() - if err != nil { - return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name) - } - - pods, err := client.CoreV1().Pods(res.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: selector.String(), - }) - if err != nil { - return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name) - } - - // Restart pods - for _, pod := range pods.Items { - // Delete each pod for get them restarted with changed spec. - if err := client.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, *metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { - return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name) - } - } - } - return nil -} - -func objectKey(r *resource.Info) string { - gvk := r.Object.GetObjectKind().GroupVersionKind() - return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) -} diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go deleted file mode 100644 index 62922b373..000000000 --- a/pkg/action/upgrade_test.go +++ /dev/null @@ -1,390 +0,0 @@ -/* -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 action - -import ( - "context" - "fmt" - "testing" - "time" - - "helm.sh/helm/v3/pkg/chart" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -func upgradeAction(t *testing.T) *Upgrade { - config := actionConfigFixture(t) - upAction := NewUpgrade(config) - upAction.Namespace = "spaced" - - return upAction -} - -func TestUpgradeRelease_Success(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "previous-release" - rel.Info.Status = release.StatusDeployed - req.NoError(upAction.cfg.Releases.Create(rel)) - - upAction.Wait = true - vals := map[string]interface{}{} - - ctx, done := context.WithCancel(context.Background()) - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) - done() - req.NoError(err) - is.Equal(res.Info.Status, release.StatusDeployed) - - // Detecting previous bug where context termination after successful release - // caused release to fail. - time.Sleep(time.Millisecond * 100) - lastRelease, err := upAction.cfg.Releases.Last(rel.Name) - req.NoError(err) - is.Equal(lastRelease.Info.Status, release.StatusDeployed) -} - -func TestUpgradeRelease_Wait(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - upAction.cfg.KubeClient = failer - upAction.Wait = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestUpgradeRelease_WaitForJobs(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - upAction.cfg.KubeClient = failer - upAction.Wait = true - upAction.WaitForJobs = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestUpgradeRelease_CleanupOnFail(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - failer.DeleteError = fmt.Errorf("I tried to delete nil") - upAction.cfg.KubeClient = failer - upAction.Wait = true - upAction.CleanupOnFail = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.NotContains(err.Error(), "unable to cleanup resources") - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestUpgradeRelease_Atomic(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - t.Run("atomic rollback succeeds", func(t *testing.T) { - upAction := upgradeAction(t) - - rel := releaseStub() - rel.Name = "nuketown" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - // We can't make Update error because then the rollback won't work - failer.WatchUntilReadyError = fmt.Errorf("arming key removed") - upAction.cfg.KubeClient = failer - upAction.Atomic = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(err.Error(), "arming key removed") - is.Contains(err.Error(), "atomic") - - // Now make sure it is actually upgraded - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3) - is.NoError(err) - // Should have rolled back to the previous - is.Equal(updatedRes.Info.Status, release.StatusDeployed) - }) - - t.Run("atomic uninstall fails", func(t *testing.T) { - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "fallout" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.UpdateError = fmt.Errorf("update fail") - upAction.cfg.KubeClient = failer - upAction.Atomic = true - vals := map[string]interface{}{} - - _, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(err.Error(), "update fail") - is.Contains(err.Error(), "an error occurred while rolling back the release") - }) -} - -func TestUpgradeRelease_ReuseValues(t *testing.T) { - is := assert.New(t) - - t.Run("reuse values should work with values", func(t *testing.T) { - upAction := upgradeAction(t) - - existingValues := map[string]interface{}{ - "name": "value", - "maxHeapSize": "128m", - "replicas": 2, - } - newValues := map[string]interface{}{ - "name": "newValue", - "maxHeapSize": "512m", - "cpu": "12m", - } - expectedValues := map[string]interface{}{ - "name": "newValue", - "maxHeapSize": "512m", - "cpu": "12m", - "replicas": 2, - } - - rel := releaseStub() - rel.Name = "nuketown" - rel.Info.Status = release.StatusDeployed - rel.Config = existingValues - - err := upAction.cfg.Releases.Create(rel) - is.NoError(err) - - upAction.ReuseValues = true - // setting newValues and upgrading - res, err := upAction.Run(rel.Name, buildChart(), newValues) - is.NoError(err) - - // Now make sure it is actually upgraded - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) - - if updatedRes == nil { - is.Fail("Updated Release is nil") - return - } - is.Equal(release.StatusDeployed, updatedRes.Info.Status) - is.Equal(expectedValues, updatedRes.Config) - }) - - t.Run("reuse values should not install disabled charts", func(t *testing.T) { - upAction := upgradeAction(t) - chartDefaultValues := map[string]interface{}{ - "subchart": map[string]interface{}{ - "enabled": true, - }, - } - dependency := chart.Dependency{ - Name: "subchart", - Version: "0.1.0", - Repository: "http://some-repo.com", - Condition: "subchart.enabled", - } - sampleChart := buildChart( - withName("sample"), - withValues(chartDefaultValues), - withMetadataDependency(dependency), - ) - now := helmtime.Now() - existingValues := map[string]interface{}{ - "subchart": map[string]interface{}{ - "enabled": false, - }, - } - rel := &release.Release{ - Name: "nuketown", - Info: &release.Info{ - FirstDeployed: now, - LastDeployed: now, - Status: release.StatusDeployed, - Description: "Named Release Stub", - }, - Chart: sampleChart, - Config: existingValues, - Version: 1, - } - err := upAction.cfg.Releases.Create(rel) - is.NoError(err) - - upAction.ReuseValues = true - sampleChartWithSubChart := buildChart( - withName(sampleChart.Name()), - withValues(sampleChart.Values), - withDependency(withName("subchart")), - withMetadataDependency(dependency), - ) - // reusing values and upgrading - res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{}) - is.NoError(err) - - // Now get the upgraded release - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) - - if updatedRes == nil { - is.Fail("Updated Release is nil") - return - } - is.Equal(release.StatusDeployed, updatedRes.Info.Status) - is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies") - - expectedValues := map[string]interface{}{ - "subchart": map[string]interface{}{ - "enabled": false, - }, - } - is.Equal(expectedValues, updatedRes.Config) - }) -} - -func TestUpgradeRelease_Pending(t *testing.T) { - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - rel2 := releaseStub() - rel2.Name = "come-fail-away" - rel2.Info.Status = release.StatusPendingUpgrade - rel2.Version = 2 - upAction.cfg.Releases.Create(rel2) - - vals := map[string]interface{}{} - - _, err := upAction.Run(rel.Name, buildChart(), vals) - req.Contains(err.Error(), "progress", err) -} - -func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { - - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "interrupted-release" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 10 * time.Second - upAction.cfg.KubeClient = failer - upAction.Wait = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) - - req.Error(err) - is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled") - is.Equal(res.Info.Status, release.StatusFailed) - -} - -func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { - - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "interrupted-release" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 5 * time.Second - upAction.cfg.KubeClient = failer - upAction.Atomic = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) - - req.Error(err) - is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled") - - // Now make sure it is actually upgraded - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3) - is.NoError(err) - // Should have rolled back to the previous - is.Equal(updatedRes.Info.Status, release.StatusDeployed) - -} diff --git a/pkg/action/validate.go b/pkg/action/validate.go deleted file mode 100644 index 73eb1937b..000000000 --- a/pkg/action/validate.go +++ /dev/null @@ -1,184 +0,0 @@ -/* -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 action - -import ( - "fmt" - - "github.com/pkg/errors" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/resource" - - "helm.sh/helm/v3/pkg/kube" -) - -var accessor = meta.NewAccessor() - -const ( - appManagedByLabel = "app.kubernetes.io/managed-by" - appManagedByHelm = "Helm" - helmReleaseNameAnnotation = "meta.helm.sh/release-name" - helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace" -) - -func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) { - var requireUpdate kube.ResourceList - - err := resources.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err - } - - helper := resource.NewHelper(info.Client, info.Mapping) - existing, err := helper.Get(info.Namespace, info.Name) - if err != nil { - if apierrors.IsNotFound(err) { - return nil - } - return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info)) - } - - // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. - if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) - } - - requireUpdate.Append(info) - return nil - }) - - return requireUpdate, err -} - -func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { - lbls, err := accessor.Labels(obj) - if err != nil { - return err - } - annos, err := accessor.Annotations(obj) - if err != nil { - return err - } - - var errs []error - if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { - errs = append(errs, fmt.Errorf("label validation error: %s", err)) - } - if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) - } - if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) - } - - if len(errs) > 0 { - err := errors.New("invalid ownership metadata") - for _, e := range errs { - err = fmt.Errorf("%w; %s", err, e) - } - return err - } - - return nil -} - -func requireValue(meta map[string]string, k, v string) error { - actual, ok := meta[k] - if !ok { - return fmt.Errorf("missing key %q: must be set to %q", k, v) - } - if actual != v { - return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual) - } - return nil -} - -// setMetadataVisitor adds release tracking metadata to all resources. If force is enabled, existing -// ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an -// existing and conflicting value for the managed by label or Helm release/namespace annotations. -func setMetadataVisitor(releaseName, releaseNamespace string, force bool) resource.VisitorFunc { - return func(info *resource.Info, err error) error { - if err != nil { - return err - } - - if !force { - if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) - } - } - - if err := mergeLabels(info.Object, map[string]string{ - appManagedByLabel: appManagedByHelm, - }); err != nil { - return fmt.Errorf( - "%s labels could not be updated: %s", - resourceString(info), err, - ) - } - - if err := mergeAnnotations(info.Object, map[string]string{ - helmReleaseNameAnnotation: releaseName, - helmReleaseNamespaceAnnotation: releaseNamespace, - }); err != nil { - return fmt.Errorf( - "%s annotations could not be updated: %s", - resourceString(info), err, - ) - } - - return nil - } -} - -func resourceString(info *resource.Info) string { - _, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind() - return fmt.Sprintf( - "%s %q in namespace %q", - k, info.Name, info.Namespace, - ) -} - -func mergeLabels(obj runtime.Object, labels map[string]string) error { - current, err := accessor.Labels(obj) - if err != nil { - return err - } - return accessor.SetLabels(obj, mergeStrStrMaps(current, labels)) -} - -func mergeAnnotations(obj runtime.Object, annotations map[string]string) error { - current, err := accessor.Annotations(obj) - if err != nil { - return err - } - return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations)) -} - -// merge two maps, always taking the value on the right -func mergeStrStrMaps(current, desired map[string]string) map[string]string { - result := make(map[string]string) - for k, v := range current { - result[k] = v - } - for k, desiredVal := range desired { - result[k] = desiredVal - } - return result -} diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go deleted file mode 100644 index a9c1cb49c..000000000 --- a/pkg/action/validate_test.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -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 action - -import ( - "testing" - - "helm.sh/helm/v3/pkg/kube" - - appsv1 "k8s.io/api/apps/v1" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/resource" -) - -func newDeploymentResource(name, namespace string) *resource.Info { - return &resource.Info{ - Name: name, - Mapping: &meta.RESTMapping{ - Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"}, - GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - Object: &appsv1.Deployment{ - ObjectMeta: v1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - }, - } -} - -func TestCheckOwnership(t *testing.T) { - deployFoo := newDeploymentResource("foo", "ns-a") - - // Verify that a resource that lacks labels/annotations is not owned - err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) - - // Set managed by label and verify annotation error message - _ = accessor.SetLabels(deployFoo.Object, map[string]string{ - appManagedByLabel: appManagedByHelm, - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) - - // Set only the release name annotation and verify missing release namespace error message - _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ - helmReleaseNameAnnotation: "rel-a", - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) - - // Set both release name and namespace annotations and verify no ownership errors - _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ - helmReleaseNameAnnotation: "rel-a", - helmReleaseNamespaceAnnotation: "ns-a", - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.NoError(t, err) - - // Verify ownership error for wrong release name - err = checkOwnership(deployFoo.Object, "rel-b", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) - - // Verify ownership error for wrong release namespace - err = checkOwnership(deployFoo.Object, "rel-a", "ns-b") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) - - // Verify ownership error for wrong manager label - _ = accessor.SetLabels(deployFoo.Object, map[string]string{ - appManagedByLabel: "helm", - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) -} - -func TestSetMetadataVisitor(t *testing.T) { - var ( - err error - deployFoo = newDeploymentResource("foo", "ns-a") - deployBar = newDeploymentResource("bar", "ns-a-system") - resources = kube.ResourceList{deployFoo, deployBar} - ) - - // Set release tracking metadata and verify no error - err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)) - assert.NoError(t, err) - - // Verify that release "b" cannot take ownership of "a" - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.Error(t, err) - - // Force release "b" to take ownership - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)) - assert.NoError(t, err) - - // Check that there is now no ownership error when setting metadata without force - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.NoError(t, err) - - // Add a new resource that is missing ownership metadata and verify error - resources.Append(newDeploymentResource("baz", "default")) - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.Error(t, err) - assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`) -} diff --git a/pkg/action/verify.go b/pkg/action/verify.go deleted file mode 100644 index f36239496..000000000 --- a/pkg/action/verify.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 action - -import ( - "fmt" - "strings" - - "helm.sh/helm/v3/pkg/downloader" -) - -// Verify is the action for building a given chart's Verify tree. -// -// It provides the implementation of 'helm verify'. -type Verify struct { - Keyring string - Out string -} - -// NewVerify creates a new Verify object with the given configuration. -func NewVerify() *Verify { - return &Verify{} -} - -// Run executes 'helm verify'. -func (v *Verify) Run(chartfile string) error { - var out strings.Builder - p, err := downloader.VerifyChart(chartfile, v.Keyring) - if err != nil { - return err - } - - for name := range p.SignedBy.Identities { - fmt.Fprintf(&out, "Signed by: %v\n", name) - } - fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", p.SignedBy.PrimaryKey.Fingerprint) - fmt.Fprintf(&out, "Chart Hash Verified: %s\n", p.FileHash) - - // TODO(mattfarina): The output is set as a property rather than returned - // to maintain the Go API. In Helm v4 this function should return the out - // and the property on the struct can be removed. - v.Out = out.String() - - return nil -} diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go deleted file mode 100644 index a3bed63a3..000000000 --- a/pkg/chart/chart.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -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" - "regexp" - "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" - -// aliasNameFormat defines the characters that are legal in an alias name. -var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") - -// 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") -} diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go deleted file mode 100644 index ef8cec3ad..000000000 --- a/pkg/chart/chart_test.go +++ /dev/null @@ -1,211 +0,0 @@ -/* -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 ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCRDs(t *testing.T) { - chrt := Chart{ - Files: []*File{ - { - Name: "crds/foo.yaml", - Data: []byte("hello"), - }, - { - Name: "bar.yaml", - Data: []byte("hello"), - }, - { - Name: "crds/foo/bar/baz.yaml", - Data: []byte("hello"), - }, - { - Name: "crdsfoo/bar/baz.yaml", - Data: []byte("hello"), - }, - { - Name: "crds/README.md", - Data: []byte("# hello"), - }, - }, - } - - is := assert.New(t) - crds := chrt.CRDs() - is.Equal(2, len(crds)) - is.Equal("crds/foo.yaml", crds[0].Name) - is.Equal("crds/foo/bar/baz.yaml", crds[1].Name) -} - -func TestSaveChartNoRawData(t *testing.T) { - chrt := Chart{ - Raw: []*File{ - { - Name: "fhqwhgads.yaml", - Data: []byte("Everybody to the Limit"), - }, - }, - } - - is := assert.New(t) - data, err := json.Marshal(chrt) - if err != nil { - t.Fatal(err) - } - - res := &Chart{} - if err := json.Unmarshal(data, res); err != nil { - t.Fatal(err) - } - - is.Equal([]*File(nil), res.Raw) -} - -func TestMetadata(t *testing.T) { - chrt := Chart{ - Metadata: &Metadata{ - Name: "foo.yaml", - AppVersion: "1.0.0", - APIVersion: "v2", - Version: "1.0.0", - Type: "application", - }, - } - - is := assert.New(t) - - is.Equal("foo.yaml", chrt.Name()) - is.Equal("1.0.0", chrt.AppVersion()) - is.Equal(nil, chrt.Validate()) -} - -func TestIsRoot(t *testing.T) { - chrt1 := Chart{ - parent: &Chart{ - Metadata: &Metadata{ - Name: "foo", - }, - }, - } - - chrt2 := Chart{ - Metadata: &Metadata{ - Name: "foo", - }, - } - - is := assert.New(t) - - is.Equal(false, chrt1.IsRoot()) - is.Equal(true, chrt2.IsRoot()) -} - -func TestChartPath(t *testing.T) { - chrt1 := Chart{ - parent: &Chart{ - Metadata: &Metadata{ - Name: "foo", - }, - }, - } - - chrt2 := Chart{ - Metadata: &Metadata{ - Name: "foo", - }, - } - - is := assert.New(t) - - is.Equal("foo.", chrt1.ChartPath()) - is.Equal("foo", chrt2.ChartPath()) -} - -func TestChartFullPath(t *testing.T) { - chrt1 := Chart{ - parent: &Chart{ - Metadata: &Metadata{ - Name: "foo", - }, - }, - } - - chrt2 := Chart{ - Metadata: &Metadata{ - Name: "foo", - }, - } - - is := assert.New(t) - - is.Equal("foo/charts/", chrt1.ChartFullPath()) - is.Equal("foo", chrt2.ChartFullPath()) -} - -func TestCRDObjects(t *testing.T) { - chrt := Chart{ - Files: []*File{ - { - Name: "crds/foo.yaml", - Data: []byte("hello"), - }, - { - Name: "bar.yaml", - Data: []byte("hello"), - }, - { - Name: "crds/foo/bar/baz.yaml", - Data: []byte("hello"), - }, - { - Name: "crdsfoo/bar/baz.yaml", - Data: []byte("hello"), - }, - { - Name: "crds/README.md", - Data: []byte("# hello"), - }, - }, - } - - expected := []CRD{ - { - Name: "crds/foo.yaml", - Filename: "crds/foo.yaml", - File: &File{ - Name: "crds/foo.yaml", - Data: []byte("hello"), - }, - }, - { - Name: "crds/foo/bar/baz.yaml", - Filename: "crds/foo/bar/baz.yaml", - File: &File{ - Name: "crds/foo/bar/baz.yaml", - Data: []byte("hello"), - }, - }, - } - - is := assert.New(t) - crds := chrt.CRDObjects() - is.Equal(expected, crds) -} diff --git a/pkg/chart/dependency.go b/pkg/chart/dependency.go deleted file mode 100644 index d9d4ee981..000000000 --- a/pkg/chart/dependency.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -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"` -} - -// Validate checks for common problems with the dependency datastructure in -// the chart. This check must be done at load time before the dependency's charts are -// loaded. -func (d *Dependency) Validate() error { - if d == nil { - return ValidationError("dependency cannot be an empty list") - } - d.Name = sanitizeString(d.Name) - d.Version = sanitizeString(d.Version) - d.Repository = sanitizeString(d.Repository) - d.Condition = sanitizeString(d.Condition) - for i := range d.Tags { - d.Tags[i] = sanitizeString(d.Tags[i]) - } - if d.Alias != "" && !aliasNameFormat.MatchString(d.Alias) { - return ValidationErrorf("dependency %q has disallowed characters in the alias", d.Name) - } - return nil -} - -// 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"` -} diff --git a/pkg/chart/dependency_test.go b/pkg/chart/dependency_test.go deleted file mode 100644 index 99c45b4b5..000000000 --- a/pkg/chart/dependency_test.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -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 ( - "testing" -) - -func TestValidateDependency(t *testing.T) { - dep := &Dependency{ - Name: "example", - } - for value, shouldFail := range map[string]bool{ - "abcdefghijklmenopQRSTUVWXYZ-0123456780_": false, - "-okay": false, - "_okay": false, - "- bad": true, - " bad": true, - "bad\nvalue": true, - "bad ": true, - "bad$": true, - } { - dep.Alias = value - res := dep.Validate() - if res != nil && !shouldFail { - t.Errorf("Failed on case %q", dep.Alias) - } else if res == nil && shouldFail { - t.Errorf("Expected failure for %q", dep.Alias) - } - } -} diff --git a/pkg/chart/errors.go b/pkg/chart/errors.go deleted file mode 100644 index 2fad5f370..000000000 --- a/pkg/chart/errors.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 "fmt" - -// ValidationError represents a data validation error. -type ValidationError string - -func (v ValidationError) Error() string { - return "validation: " + string(v) -} - -// ValidationErrorf takes a message and formatting options and creates a ValidationError -func ValidationErrorf(msg string, args ...interface{}) ValidationError { - return ValidationError(fmt.Sprintf(msg, args...)) -} diff --git a/pkg/chart/file.go b/pkg/chart/file.go deleted file mode 100644 index 9dd7c08d5..000000000 --- a/pkg/chart/file.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -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"` -} diff --git a/pkg/chart/loader/archive.go b/pkg/chart/loader/archive.go deleted file mode 100644 index 8b38cb89f..000000000 --- a/pkg/chart/loader/archive.go +++ /dev/null @@ -1,196 +0,0 @@ -/* -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) -} diff --git a/pkg/chart/loader/archive_test.go b/pkg/chart/loader/archive_test.go deleted file mode 100644 index 41b0af1aa..000000000 --- a/pkg/chart/loader/archive_test.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -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" - "testing" -) - -func TestLoadArchiveFiles(t *testing.T) { - tcs := []struct { - name string - generate func(w *tar.Writer) - check func(t *testing.T, files []*BufferedFile, err error) - }{ - { - name: "empty input should return no files", - generate: func(w *tar.Writer) {}, - check: func(t *testing.T, files []*BufferedFile, err error) { - if err.Error() != "no files in chart archive" { - t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) - } - }, - }, - { - name: "should ignore files with XGlobalHeader type", - generate: func(w *tar.Writer) { - // simulate the presence of a `pax_global_header` file like you would get when - // processing a GitHub release archive. - err := w.WriteHeader(&tar.Header{ - Typeflag: tar.TypeXGlobalHeader, - Name: "pax_global_header", - }) - if err != nil { - t.Fatal(err) - } - - // we need to have at least one file, otherwise we'll get the "no files in chart archive" error - err = w.WriteHeader(&tar.Header{ - Typeflag: tar.TypeReg, - Name: "dir/empty", - }) - if err != nil { - t.Fatal(err) - } - }, - check: func(t *testing.T, files []*BufferedFile, err error) { - if err != nil { - t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) - } - - if len(files) != 1 { - t.Fatalf(`expected to get one file but got [%v]`, files) - } - }, - }, - } - - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - buf := &bytes.Buffer{} - gzw := gzip.NewWriter(buf) - tw := tar.NewWriter(gzw) - - tc.generate(tw) - - _ = tw.Close() - _ = gzw.Close() - - files, err := LoadArchiveFiles(buf) - tc.check(t, files, err) - }) - } -} diff --git a/pkg/chart/loader/directory.go b/pkg/chart/loader/directory.go deleted file mode 100644 index bbe543870..000000000 --- a/pkg/chart/loader/directory.go +++ /dev/null @@ -1,120 +0,0 @@ -/* -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) -} diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go deleted file mode 100644 index 7cc8878a8..000000000 --- a/pkg/chart/loader/load.go +++ /dev/null @@ -1,200 +0,0 @@ -/* -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) - - // do not rely on assumed ordering of files in the chart and crash - // if Chart.yaml was not coming early enough to initialize metadata - for _, f := range files { - c.Raw = append(c.Raw, &chart.File{Name: f.Name, Data: f.Data}) - if 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 - } - } - } - for _, f := range files { - switch { - case f.Name == "Chart.yaml": - // already processed - continue - 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 == nil { - c.Metadata = new(chart.Metadata) - } - 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 c.Metadata == nil { - return c, errors.New("Chart.yaml file is missing") - } - - 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 -} diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go deleted file mode 100644 index a737098b4..000000000 --- a/pkg/chart/loader/load_test.go +++ /dev/null @@ -1,649 +0,0 @@ -/* -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" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "helm.sh/helm/v3/pkg/chart" -) - -func TestLoadDir(t *testing.T) { - l, err := Loader("testdata/frobnitz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyFrobnitz(t, c) - verifyChart(t, c) - verifyDependencies(t, c) - verifyDependenciesLock(t, c) -} - -func TestLoadDirWithDevNull(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("test only works on unix systems with /dev/null present") - } - - l, err := Loader("testdata/frobnitz_with_dev_null") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - if _, err := l.Load(); err == nil { - t.Errorf("packages with an irregular file (/dev/null) should not load") - } -} - -func TestLoadDirWithSymlink(t *testing.T) { - sym := filepath.Join("..", "LICENSE") - link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE") - - if err := os.Symlink(sym, link); err != nil { - t.Fatal(err) - } - - defer os.Remove(link) - - l, err := Loader("testdata/frobnitz_with_symlink") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyFrobnitz(t, c) - verifyChart(t, c) - verifyDependencies(t, c) - verifyDependenciesLock(t, c) -} - -func TestBomTestData(t *testing.T) { - testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} - for _, file := range testFiles { - data, err := ioutil.ReadFile("testdata/" + file) - if err != nil || !bytes.HasPrefix(data, utf8bom) { - t.Errorf("Test file has no BOM or is invalid: testdata/%s", file) - } - } - - archive, err := ioutil.ReadFile("testdata/frobnitz_with_bom.tgz") - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } - unzipped, err := gzip.NewReader(bytes.NewReader(archive)) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } - defer unzipped.Close() - for _, testFile := range testFiles { - data := make([]byte, 3) - err := unzipped.Reset(bytes.NewReader(archive)) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } - tr := tar.NewReader(unzipped) - for { - file, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } - if file != nil && strings.EqualFold(file.Name, testFile) { - _, err := tr.Read(data) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } else { - break - } - } - } - if !bytes.Equal(data, utf8bom) { - t.Fatalf("Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) - } - } -} - -func TestLoadDirWithUTFBOM(t *testing.T) { - l, err := Loader("testdata/frobnitz_with_bom") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyFrobnitz(t, c) - verifyChart(t, c) - verifyDependencies(t, c) - verifyDependenciesLock(t, c) - verifyBomStripped(t, c.Files) -} - -func TestLoadArchiveWithUTFBOM(t *testing.T) { - l, err := Loader("testdata/frobnitz_with_bom.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyFrobnitz(t, c) - verifyChart(t, c) - verifyDependencies(t, c) - verifyDependenciesLock(t, c) - verifyBomStripped(t, c.Files) -} - -func TestLoadV1(t *testing.T) { - l, err := Loader("testdata/frobnitz.v1") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyDependencies(t, c) - verifyDependenciesLock(t, c) -} - -func TestLoadFileV1(t *testing.T) { - l, err := Loader("testdata/frobnitz.v1.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyDependencies(t, c) - verifyDependenciesLock(t, c) -} - -func TestLoadFile(t *testing.T) { - l, err := Loader("testdata/frobnitz-1.2.3.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyFrobnitz(t, c) - verifyChart(t, c) - verifyDependencies(t, c) -} - -func TestLoadFiles_BadCases(t *testing.T) { - for _, tt := range []struct { - name string - bufferedFiles []*BufferedFile - expectError string - }{ - { - name: "These files contain only requirements.lock", - bufferedFiles: []*BufferedFile{ - { - Name: "requirements.lock", - Data: []byte(""), - }, - }, - expectError: "validation: chart.metadata.apiVersion is required"}, - } { - _, err := LoadFiles(tt.bufferedFiles) - if err == nil { - t.Fatal("expected error when load illegal files") - } - if !strings.Contains(err.Error(), tt.expectError) { - t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.name) - } - } -} - -func TestLoadFiles(t *testing.T) { - goodFiles := []*BufferedFile{ - { - Name: "Chart.yaml", - Data: []byte(`apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -`), - }, - { - Name: "values.yaml", - Data: []byte("var: some values"), - }, - { - Name: "values.schema.json", - Data: []byte("type: Values"), - }, - { - Name: "templates/deployment.yaml", - Data: []byte("some deployment"), - }, - { - Name: "templates/service.yaml", - Data: []byte("some service"), - }, - } - - c, err := LoadFiles(goodFiles) - if err != nil { - t.Errorf("Expected good files to be loaded, got %v", err) - } - - if c.Name() != "frobnitz" { - t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name()) - } - - if c.Values["var"] != "some values" { - t.Error("Expected chart values to be populated with default values") - } - - if len(c.Raw) != 5 { - t.Errorf("Expected %d files, got %d", 5, len(c.Raw)) - } - - if !bytes.Equal(c.Schema, []byte("type: Values")) { - t.Error("Expected chart schema to be populated with default values") - } - - if len(c.Templates) != 2 { - t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) - } - - if _, err = LoadFiles([]*BufferedFile{}); err == nil { - t.Fatal("Expected err to be non-nil") - } - if err.Error() != "Chart.yaml file is missing" { - t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) - } -} - -// Test the order of file loading. The Chart.yaml file needs to come first for -// later comparison checks. See https://github.com/helm/helm/pull/8948 -func TestLoadFilesOrder(t *testing.T) { - goodFiles := []*BufferedFile{ - { - Name: "requirements.yaml", - Data: []byte("dependencies:"), - }, - { - Name: "values.yaml", - Data: []byte("var: some values"), - }, - - { - Name: "templates/deployment.yaml", - Data: []byte("some deployment"), - }, - { - Name: "templates/service.yaml", - Data: []byte("some service"), - }, - { - Name: "Chart.yaml", - Data: []byte(`apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -`), - }, - } - - // Capture stderr to make sure message about Chart.yaml handle dependencies - // is not present - r, w, err := os.Pipe() - if err != nil { - t.Fatalf("Unable to create pipe: %s", err) - } - stderr := log.Writer() - log.SetOutput(w) - defer func() { - log.SetOutput(stderr) - }() - - _, err = LoadFiles(goodFiles) - if err != nil { - t.Errorf("Expected good files to be loaded, got %v", err) - } - w.Close() - - var text bytes.Buffer - io.Copy(&text, r) - if text.String() != "" { - t.Errorf("Expected no message to Stderr, got %s", text.String()) - } - -} - -// Packaging the chart on a Windows machine will produce an -// archive that has \\ as delimiters. Test that we support these archives -func TestLoadFileBackslash(t *testing.T) { - c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyChartFileAndTemplate(t, c, "frobnitz_backslash") - verifyChart(t, c) - verifyDependencies(t, c) -} - -func TestLoadV2WithReqs(t *testing.T) { - l, err := Loader("testdata/frobnitz.v2.reqs") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - verifyDependencies(t, c) - verifyDependenciesLock(t, c) -} - -func TestLoadInvalidArchive(t *testing.T) { - tmpdir := t.TempDir() - - writeTar := func(filename, internalPath string, body []byte) { - dest, err := os.Create(filename) - if err != nil { - t.Fatal(err) - } - zipper := gzip.NewWriter(dest) - tw := tar.NewWriter(zipper) - - h := &tar.Header{ - Name: internalPath, - Mode: 0755, - Size: int64(len(body)), - ModTime: time.Now(), - } - if err := tw.WriteHeader(h); err != nil { - t.Fatal(err) - } - if _, err := tw.Write(body); err != nil { - t.Fatal(err) - } - tw.Close() - zipper.Close() - dest.Close() - } - - for _, tt := range []struct { - chartname string - internal string - expectError string - }{ - {"illegal-dots.tgz", "../../malformed-helm-test", "chart illegally references parent directory"}, - {"illegal-dots2.tgz", "/foo/../../malformed-helm-test", "chart illegally references parent directory"}, - {"illegal-dots3.tgz", "/../../malformed-helm-test", "chart illegally references parent directory"}, - {"illegal-dots4.tgz", "./../../malformed-helm-test", "chart illegally references parent directory"}, - {"illegal-name.tgz", "./.", "chart illegally contains content outside the base directory"}, - {"illegal-name2.tgz", "/./.", "chart illegally contains content outside the base directory"}, - {"illegal-name3.tgz", "missing-leading-slash", "chart illegally contains content outside the base directory"}, - {"illegal-name4.tgz", "/missing-leading-slash", "Chart.yaml file is missing"}, - {"illegal-abspath.tgz", "//foo", "chart illegally contains absolute paths"}, - {"illegal-abspath2.tgz", "///foo", "chart illegally contains absolute paths"}, - {"illegal-abspath3.tgz", "\\\\foo", "chart illegally contains absolute paths"}, - {"illegal-abspath3.tgz", "\\..\\..\\foo", "chart illegally references parent directory"}, - - // Under special circumstances, this can get normalized to things that look like absolute Windows paths - {"illegal-abspath4.tgz", "\\.\\c:\\\\foo", "chart contains illegally named files"}, - {"illegal-abspath5.tgz", "/./c://foo", "chart contains illegally named files"}, - {"illegal-abspath6.tgz", "\\\\?\\Some\\windows\\magic", "chart illegally contains absolute paths"}, - } { - illegalChart := filepath.Join(tmpdir, tt.chartname) - writeTar(illegalChart, tt.internal, []byte("hello: world")) - _, err := Load(illegalChart) - if err == nil { - t.Fatal("expected error when unpacking illegal files") - } - if !strings.Contains(err.Error(), tt.expectError) { - t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) - } - } - - // Make sure that absolute path gets interpreted as relative - illegalChart := filepath.Join(tmpdir, "abs-path.tgz") - writeTar(illegalChart, "/Chart.yaml", []byte("hello: world")) - _, err := Load(illegalChart) - if err.Error() != "validation: chart.metadata.name is required" { - t.Error(err) - } - - // And just to validate that the above was not spurious - illegalChart = filepath.Join(tmpdir, "abs-path2.tgz") - writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world")) - _, err = Load(illegalChart) - if err.Error() != "Chart.yaml file is missing" { - t.Errorf("Unexpected error message: %s", err) - } - - // Finally, test that drive letter gets stripped off on Windows - illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz") - writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world")) - _, err = Load(illegalChart) - if err.Error() != "validation: chart.metadata.name is required" { - t.Error(err) - } -} - -func verifyChart(t *testing.T, c *chart.Chart) { - t.Helper() - if c.Name() == "" { - t.Fatalf("No chart metadata found on %v", c) - } - t.Logf("Verifying chart %s", c.Name()) - if len(c.Templates) != 1 { - t.Errorf("Expected 1 template, got %d", len(c.Templates)) - } - - numfiles := 6 - if len(c.Files) != numfiles { - t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) - for _, n := range c.Files { - t.Logf("\t%s", n.Name) - } - } - - if len(c.Dependencies()) != 2 { - t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies()) - for _, d := range c.Dependencies() { - t.Logf("\tSubchart: %s\n", d.Name()) - } - } - - expect := map[string]map[string]string{ - "alpine": { - "version": "0.1.0", - }, - "mariner": { - "version": "4.3.2", - }, - } - - for _, dep := range c.Dependencies() { - if dep.Metadata == nil { - t.Fatalf("expected metadata on dependency: %v", dep) - } - exp, ok := expect[dep.Name()] - if !ok { - t.Fatalf("Unknown dependency %s", dep.Name()) - } - if exp["version"] != dep.Metadata.Version { - t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) - } - } - -} - -func verifyDependencies(t *testing.T, c *chart.Chart) { - if len(c.Metadata.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) - } - tests := []*chart.Dependency{ - {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, - {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, - } - for i, tt := range tests { - d := c.Metadata.Dependencies[i] - if d.Name != tt.Name { - t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) - } - if d.Version != tt.Version { - t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) - } - if d.Repository != tt.Repository { - t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) - } - } -} - -func verifyDependenciesLock(t *testing.T, c *chart.Chart) { - if len(c.Metadata.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) - } - tests := []*chart.Dependency{ - {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, - {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, - } - for i, tt := range tests { - d := c.Metadata.Dependencies[i] - if d.Name != tt.Name { - t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) - } - if d.Version != tt.Version { - t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) - } - if d.Repository != tt.Repository { - t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) - } - } -} - -func verifyFrobnitz(t *testing.T, c *chart.Chart) { - verifyChartFileAndTemplate(t, c, "frobnitz") -} - -func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { - if c.Metadata == nil { - t.Fatal("Metadata is nil") - } - if c.Name() != name { - t.Errorf("Expected %s, got %s", name, c.Name()) - } - if len(c.Templates) != 1 { - t.Fatalf("Expected 1 template, got %d", len(c.Templates)) - } - if c.Templates[0].Name != "templates/template.tpl" { - t.Errorf("Unexpected template: %s", c.Templates[0].Name) - } - if len(c.Templates[0].Data) == 0 { - t.Error("No template data.") - } - if len(c.Files) != 6 { - t.Fatalf("Expected 6 Files, got %d", len(c.Files)) - } - if len(c.Dependencies()) != 2 { - t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies())) - } - if len(c.Metadata.Dependencies) != 2 { - t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) - } - if len(c.Lock.Dependencies) != 2 { - t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) - } - - for _, dep := range c.Dependencies() { - switch dep.Name() { - case "mariner": - case "alpine": - if len(dep.Templates) != 1 { - t.Fatalf("Expected 1 template, got %d", len(dep.Templates)) - } - if dep.Templates[0].Name != "templates/alpine-pod.yaml" { - t.Errorf("Unexpected template: %s", dep.Templates[0].Name) - } - if len(dep.Templates[0].Data) == 0 { - t.Error("No template data.") - } - if len(dep.Files) != 1 { - t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) - } - if len(dep.Dependencies()) != 2 { - t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies())) - } - default: - t.Errorf("Unexpected dependency %s", dep.Name()) - } - } -} - -func verifyBomStripped(t *testing.T, files []*chart.File) { - for _, file := range files { - if bytes.HasPrefix(file.Data, utf8bom) { - t.Errorf("Byte Order Mark still present in processed file %s", file.Name) - } - } -} diff --git a/pkg/chart/loader/testdata/LICENSE b/pkg/chart/loader/testdata/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/albatross/Chart.yaml b/pkg/chart/loader/testdata/albatross/Chart.yaml deleted file mode 100644 index eeef737ff..000000000 --- a/pkg/chart/loader/testdata/albatross/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: albatross -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/albatross/values.yaml b/pkg/chart/loader/testdata/albatross/values.yaml deleted file mode 100644 index 3121cd7ce..000000000 --- a/pkg/chart/loader/testdata/albatross/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -albatross: "true" - -global: - author: Coleridge diff --git a/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz b/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz deleted file mode 100644 index b2b76a83c..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz.v1.tgz b/pkg/chart/loader/testdata/frobnitz.v1.tgz deleted file mode 100644 index 6282f9b73..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz.v1.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz.v1/.helmignore b/pkg/chart/loader/testdata/frobnitz.v1/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock b/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml deleted file mode 100644 index 134cd1109..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue diff --git a/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/LICENSE b/pkg/chart/loader/testdata/frobnitz.v1/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/README.md b/pkg/chart/loader/testdata/frobnitz.v1/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md b/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/icon.svg b/pkg/chart/loader/testdata/frobnitz.v1/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml b/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml deleted file mode 100644 index 5eb0bc98b..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz.v1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v1/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore b/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml deleted file mode 100644 index f3ab30291..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v2 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE b/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg b/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml deleted file mode 100644 index 5eb0bc98b..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz/.helmignore b/pkg/chart/loader/testdata/frobnitz/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz/Chart.lock b/pkg/chart/loader/testdata/frobnitz/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chart/loader/testdata/frobnitz/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz/LICENSE b/pkg/chart/loader/testdata/frobnitz/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz/README.md b/pkg/chart/loader/testdata/frobnitz/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz/docs/README.md b/pkg/chart/loader/testdata/frobnitz/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz/icon.svg b/pkg/chart/loader/testdata/frobnitz/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz/values.yaml b/pkg/chart/loader/testdata/frobnitz/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz deleted file mode 100644 index a9d4c11d8..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore b/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore deleted file mode 100755 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock b/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock deleted file mode 100755 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml deleted file mode 100755 index b1dd40a5d..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -name: frobnitz_backslash -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt deleted file mode 100755 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE b/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE deleted file mode 100755 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/README.md deleted file mode 100755 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me deleted file mode 100755 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml deleted file mode 100755 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md deleted file mode 100755 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100755 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml deleted file mode 100755 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100755 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml deleted file mode 100755 index 0ac5ca6a8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service | quote }} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml deleted file mode 100755 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz deleted file mode 100755 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md deleted file mode 100755 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg b/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg deleted file mode 100755 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt deleted file mode 100755 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl deleted file mode 100755 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml deleted file mode 100755 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom.tgz deleted file mode 100644 index be0cd027d..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_bom.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore deleted file mode 100644 index 7a4b92da2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock deleted file mode 100644 index ed43b227f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml deleted file mode 100644 index 21b21f0b5..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt deleted file mode 100644 index 77c4e724a..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE b/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE deleted file mode 100644 index c27b00bf2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/README.md deleted file mode 100644 index e9c40031b..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me deleted file mode 100644 index a7e3a38b7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml deleted file mode 100644 index adb9853c6..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md deleted file mode 100644 index ea7526bee..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1ad84b346..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index f690d53c4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index f3e662a28..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml deleted file mode 100644 index 6b7cb2596..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md deleted file mode 100644 index 816c3e431..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl deleted file mode 100644 index bb29c5491..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml deleted file mode 100644 index c24ceadf9..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE b/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md b/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/null b/pkg/chart/loader/testdata/frobnitz_with_dev_null/null deleted file mode 120000 index dc1dc0cde..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/null +++ /dev/null @@ -1 +0,0 @@ -/dev/null \ No newline at end of file diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md b/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - app.kubernetes.io/name: {{.Chart.Name}} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/genfrob.sh b/pkg/chart/loader/testdata/genfrob.sh deleted file mode 100755 index 35fdd59f2..000000000 --- a/pkg/chart/loader/testdata/genfrob.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# Pack the albatross chart into the mariner chart. -echo "Packing albatross into mariner" -tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross - -echo "Packing mariner into frobnitz" -tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner -tar -zcvf frobnitz_backslash/charts/mariner-4.3.2.tgz mariner - -# Pack the frobnitz chart. -echo "Packing frobnitz" -tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz -tar --exclude=ignore/* -zcvf frobnitz_backslash-1.2.3.tgz frobnitz_backslash diff --git a/pkg/chart/loader/testdata/mariner/Chart.yaml b/pkg/chart/loader/testdata/mariner/Chart.yaml deleted file mode 100644 index 92dc4b390..000000000 --- a/pkg/chart/loader/testdata/mariner/Chart.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -name: mariner -description: A Helm chart for Kubernetes -version: 4.3.2 -home: "" -dependencies: - - name: albatross - repository: https://example.com/mariner/charts - version: "0.1.0" diff --git a/pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz b/pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz deleted file mode 100644 index 128ef82f7..000000000 Binary files a/pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz and /dev/null differ diff --git a/pkg/chart/loader/testdata/mariner/templates/placeholder.tpl b/pkg/chart/loader/testdata/mariner/templates/placeholder.tpl deleted file mode 100644 index 29c11843a..000000000 --- a/pkg/chart/loader/testdata/mariner/templates/placeholder.tpl +++ /dev/null @@ -1 +0,0 @@ -# This is a placeholder. diff --git a/pkg/chart/loader/testdata/mariner/values.yaml b/pkg/chart/loader/testdata/mariner/values.yaml deleted file mode 100644 index b0ccb0086..000000000 --- a/pkg/chart/loader/testdata/mariner/values.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Default values for . -# This is a YAML-formatted file. https://github.com/toml-lang/toml -# Declare name/value pairs to be passed into your templates. -# name: "value" - -: - test: true diff --git a/pkg/chart/metadata.go b/pkg/chart/metadata.go deleted file mode 100644 index 7d16ecd1b..000000000 --- a/pkg/chart/metadata.go +++ /dev/null @@ -1,163 +0,0 @@ -/* -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 ( - "strings" - "unicode" - - "github.com/Masterminds/semver/v3" -) - -// 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"` -} - -// Validate checks valid data and sanitizes string characters. -func (m *Maintainer) Validate() error { - if m == nil { - return ValidationError("maintainer cannot be an empty list") - } - m.Name = sanitizeString(m.Name) - m.Email = sanitizeString(m.Email) - m.URL = sanitizeString(m.URL) - return nil -} - -// Metadata for a Chart file. This models the structure of a Chart.yaml file. -type Metadata struct { - // The name of the chart. Required. - 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. Required. - 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. Required. - 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 and sanitizes string -// characters. -func (md *Metadata) Validate() error { - if md == nil { - return ValidationError("chart.metadata is required") - } - - md.Name = sanitizeString(md.Name) - md.Description = sanitizeString(md.Description) - md.Home = sanitizeString(md.Home) - md.Icon = sanitizeString(md.Icon) - md.Condition = sanitizeString(md.Condition) - md.Tags = sanitizeString(md.Tags) - md.AppVersion = sanitizeString(md.AppVersion) - md.KubeVersion = sanitizeString(md.KubeVersion) - for i := range md.Sources { - md.Sources[i] = sanitizeString(md.Sources[i]) - } - for i := range md.Keywords { - md.Keywords[i] = sanitizeString(md.Keywords[i]) - } - - 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 !isValidSemver(md.Version) { - return ValidationErrorf("chart.metadata.version %q is invalid", md.Version) - } - if !isValidChartType(md.Type) { - return ValidationError("chart.metadata.type must be application or library") - } - - for _, m := range md.Maintainers { - if err := m.Validate(); err != nil { - return err - } - } - - // Aliases need to be validated here to make sure that the alias name does - // not contain any illegal characters. - for _, dependency := range md.Dependencies { - if err := dependency.Validate(); err != nil { - return err - } - } - return nil -} - -func isValidChartType(in string) bool { - switch in { - case "", "application", "library": - return true - } - return false -} - -func isValidSemver(v string) bool { - _, err := semver.NewVersion(v) - return err == nil -} - -// sanitizeString normalize spaces and removes non-printable characters. -func sanitizeString(str string) string { - return strings.Map(func(r rune) rune { - if unicode.IsSpace(r) { - return ' ' - } - if unicode.IsPrint(r) { - return r - } - return -1 - }, str) -} diff --git a/pkg/chart/metadata_test.go b/pkg/chart/metadata_test.go deleted file mode 100644 index 98354d13f..000000000 --- a/pkg/chart/metadata_test.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -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 ( - "testing" -) - -func TestValidate(t *testing.T) { - tests := []struct { - md *Metadata - err error - }{ - { - nil, - ValidationError("chart.metadata is required"), - }, - { - &Metadata{Name: "test", Version: "1.0"}, - ValidationError("chart.metadata.apiVersion is required"), - }, - { - &Metadata{APIVersion: "v2", Version: "1.0"}, - ValidationError("chart.metadata.name is required"), - }, - { - &Metadata{Name: "test", APIVersion: "v2"}, - ValidationError("chart.metadata.version is required"), - }, - { - &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "test"}, - ValidationError("chart.metadata.type must be application or library"), - }, - { - &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "application"}, - nil, - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Dependencies: []*Dependency{ - {Name: "dependency", Alias: "legal-alias"}, - }, - }, - nil, - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Dependencies: []*Dependency{ - {Name: "bad", Alias: "illegal alias"}, - }, - }, - ValidationError("dependency \"bad\" has disallowed characters in the alias"), - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Dependencies: []*Dependency{ - nil, - }, - }, - ValidationError("dependency cannot be an empty list"), - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Maintainers: []*Maintainer{ - nil, - }, - }, - ValidationError("maintainer cannot be an empty list"), - }, - { - &Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"}, - ValidationError("chart.metadata.version \"1.2.3.4\" is invalid"), - }, - } - - for _, tt := range tests { - result := tt.md.Validate() - if result != tt.err { - t.Errorf("expected '%s', got '%s'", tt.err, result) - } - } -} - -func TestValidate_sanitize(t *testing.T) { - md := &Metadata{APIVersion: "v2", Name: "test", Version: "1.0", Description: "\adescr\u0081iption\rtest", Maintainers: []*Maintainer{{Name: "\r"}}} - if err := md.Validate(); err != nil { - t.Fatalf("unexpected error: %s", err) - } - if md.Description != "description test" { - t.Fatalf("description was not sanitized: %q", md.Description) - } - if md.Maintainers[0].Name != " " { - t.Fatal("maintainer name was not sanitized") - } -} diff --git a/pkg/chartutil/capabilities.go b/pkg/chartutil/capabilities.go deleted file mode 100644 index 5f57e11a5..000000000 --- a/pkg/chartutil/capabilities.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -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 chartutil - -import ( - "fmt" - "strconv" - - "github.com/Masterminds/semver/v3" - "k8s.io/client-go/kubernetes/scheme" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - - helmversion "helm.sh/helm/v3/internal/version" -) - -var ( - // The Kubernetes version can be set by LDFLAGS. In order to do that the value - // must be a string. - k8sVersionMajor = "1" - k8sVersionMinor = "20" - - // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). - DefaultVersionSet = allKnownVersions() - - // DefaultCapabilities is the default set of capabilities. - DefaultCapabilities = &Capabilities{ - KubeVersion: KubeVersion{ - Version: fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor), - Major: k8sVersionMajor, - Minor: k8sVersionMinor, - }, - APIVersions: DefaultVersionSet, - HelmVersion: helmversion.Get(), - } -) - -// Capabilities describes the capabilities of the Kubernetes cluster. -type Capabilities struct { - // KubeVersion is the Kubernetes version. - KubeVersion KubeVersion - // APIversions are supported Kubernetes API versions. - APIVersions VersionSet - // HelmVersion is the build information for this helm version - HelmVersion helmversion.BuildInfo -} - -func (capabilities *Capabilities) Copy() *Capabilities { - return &Capabilities{ - KubeVersion: capabilities.KubeVersion, - APIVersions: capabilities.APIVersions, - HelmVersion: capabilities.HelmVersion, - } -} - -// KubeVersion is the Kubernetes version. -type KubeVersion struct { - Version string // Kubernetes version - Major string // Kubernetes major version - Minor string // Kubernetes minor version -} - -// String implements fmt.Stringer -func (kv *KubeVersion) String() string { return kv.Version } - -// GitVersion returns the Kubernetes version string. -// -// Deprecated: use KubeVersion.Version. -func (kv *KubeVersion) GitVersion() string { return kv.Version } - -// ParseKubeVersion parses kubernetes version from string -func ParseKubeVersion(version string) (*KubeVersion, error) { - sv, err := semver.NewVersion(version) - if err != nil { - return nil, err - } - return &KubeVersion{ - Version: "v" + sv.String(), - Major: strconv.FormatUint(sv.Major(), 10), - Minor: strconv.FormatUint(sv.Minor(), 10), - }, nil -} - -// VersionSet is a set of Kubernetes API versions. -type VersionSet []string - -// Has returns true if the version string is in the set. -// -// vs.Has("apps/v1") -func (v VersionSet) Has(apiVersion string) bool { - for _, x := range v { - if x == apiVersion { - return true - } - } - return false -} - -func allKnownVersions() VersionSet { - // We should register the built in extension APIs as well so CRDs are - // supported in the default version set. This has caused problems with `helm - // template` in the past, so let's be safe - apiextensionsv1beta1.AddToScheme(scheme.Scheme) - apiextensionsv1.AddToScheme(scheme.Scheme) - - groups := scheme.Scheme.PrioritizedVersionsAllGroups() - vs := make(VersionSet, 0, len(groups)) - for _, gv := range groups { - vs = append(vs, gv.String()) - } - return vs -} diff --git a/pkg/chartutil/capabilities_test.go b/pkg/chartutil/capabilities_test.go deleted file mode 100644 index ffd8d76da..000000000 --- a/pkg/chartutil/capabilities_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -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 chartutil - -import ( - "testing" -) - -func TestVersionSet(t *testing.T) { - vs := VersionSet{"v1", "apps/v1"} - if d := len(vs); d != 2 { - t.Errorf("Expected 2 versions, got %d", d) - } - - if !vs.Has("apps/v1") { - t.Error("Expected to find apps/v1") - } - - if vs.Has("Spanish/inquisition") { - t.Error("No one expects the Spanish/inquisition") - } -} - -func TestDefaultVersionSet(t *testing.T) { - if !DefaultVersionSet.Has("v1") { - t.Error("Expected core v1 version set") - } -} - -func TestDefaultCapabilities(t *testing.T) { - kv := DefaultCapabilities.KubeVersion - if kv.String() != "v1.20.0" { - t.Errorf("Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String()) - } - if kv.Version != "v1.20.0" { - t.Errorf("Expected default KubeVersion.Version to be v1.20.0, got %q", kv.Version) - } - if kv.GitVersion() != "v1.20.0" { - t.Errorf("Expected default KubeVersion.GitVersion() to be v1.20.0, got %q", kv.Version) - } - if kv.Major != "1" { - t.Errorf("Expected default KubeVersion.Major to be 1, got %q", kv.Major) - } - if kv.Minor != "20" { - t.Errorf("Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) - } -} - -func TestDefaultCapabilitiesHelmVersion(t *testing.T) { - hv := DefaultCapabilities.HelmVersion - - if hv.Version != "v3.10" { - t.Errorf("Expected default HelmVersion to be v3.10, got %q", hv.Version) - } -} - -func TestParseKubeVersion(t *testing.T) { - kv, err := ParseKubeVersion("v1.16.0") - if err != nil { - t.Errorf("Expected v1.16.0 to parse successfully") - } - if kv.Version != "v1.16.0" { - t.Errorf("Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String()) - } - if kv.Major != "1" { - t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major) - } - if kv.Minor != "16" { - t.Errorf("Expected parsed KubeVersion.Minor to be 16, got %q", kv.Minor) - } -} diff --git a/pkg/chartutil/chartfile.go b/pkg/chartutil/chartfile.go deleted file mode 100644 index 808a902b1..000000000 --- a/pkg/chartutil/chartfile.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -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 chartutil - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. -func LoadChartfile(filename string) (*chart.Metadata, error) { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - y := new(chart.Metadata) - err = yaml.Unmarshal(b, y) - return y, err -} - -// SaveChartfile saves the given metadata as a Chart.yaml file at the given path. -// -// 'filename' should be the complete path and filename ('foo/Chart.yaml') -func SaveChartfile(filename string, cf *chart.Metadata) error { - // Pull out the dependencies of a v1 Chart, since there's no way - // to tell the serializer to skip a field for just this use case - savedDependencies := cf.Dependencies - if cf.APIVersion == chart.APIVersionV1 { - cf.Dependencies = nil - } - out, err := yaml.Marshal(cf) - if cf.APIVersion == chart.APIVersionV1 { - cf.Dependencies = savedDependencies - } - if err != nil { - return err - } - return ioutil.WriteFile(filename, out, 0644) -} - -// IsChartDir validate a chart directory. -// -// Checks for a valid Chart.yaml. -func IsChartDir(dirName string) (bool, error) { - if fi, err := os.Stat(dirName); err != nil { - return false, err - } else if !fi.IsDir() { - return false, errors.Errorf("%q is not a directory", dirName) - } - - chartYaml := filepath.Join(dirName, ChartfileName) - if _, err := os.Stat(chartYaml); os.IsNotExist(err) { - return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName) - } - - chartYamlContent, err := ioutil.ReadFile(chartYaml) - if err != nil { - return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName) - } - - chartContent := new(chart.Metadata) - if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil { - return false, err - } - if chartContent == nil { - return false, errors.Errorf("chart metadata (%s) missing", ChartfileName) - } - if chartContent.Name == "" { - return false, errors.Errorf("invalid chart (%s): name must not be empty", ChartfileName) - } - - return true, nil -} diff --git a/pkg/chartutil/chartfile_test.go b/pkg/chartutil/chartfile_test.go deleted file mode 100644 index fb5f15376..000000000 --- a/pkg/chartutil/chartfile_test.go +++ /dev/null @@ -1,121 +0,0 @@ -/* -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 chartutil - -import ( - "testing" - - "helm.sh/helm/v3/pkg/chart" -) - -const testfile = "testdata/chartfiletest.yaml" - -func TestLoadChartfile(t *testing.T) { - f, err := LoadChartfile(testfile) - if err != nil { - t.Errorf("Failed to open %s: %s", testfile, err) - return - } - verifyChartfile(t, f, "frobnitz") -} - -func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { - - if f == nil { - t.Fatal("Failed verifyChartfile because f is nil") - } - - if f.APIVersion != chart.APIVersionV1 { - t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion) - } - - if f.Name != name { - t.Errorf("Expected %s, got %s", name, f.Name) - } - - if f.Description != "This is a frobnitz." { - t.Errorf("Unexpected description %q", f.Description) - } - - if f.Version != "1.2.3" { - t.Errorf("Unexpected version %q", f.Version) - } - - if len(f.Maintainers) != 2 { - t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) - } - - if f.Maintainers[0].Name != "The Helm Team" { - t.Errorf("Unexpected maintainer name.") - } - - if f.Maintainers[1].Email != "nobody@example.com" { - t.Errorf("Unexpected maintainer email.") - } - - if len(f.Sources) != 1 { - t.Fatalf("Unexpected number of sources") - } - - if f.Sources[0] != "https://example.com/foo/bar" { - t.Errorf("Expected https://example.com/foo/bar, got %s", f.Sources) - } - - if f.Home != "http://example.com" { - t.Error("Unexpected home.") - } - - if f.Icon != "https://example.com/64x64.png" { - t.Errorf("Unexpected icon: %q", f.Icon) - } - - if len(f.Keywords) != 3 { - t.Error("Unexpected keywords") - } - - if len(f.Annotations) != 2 { - t.Fatalf("Unexpected annotations") - } - - if want, got := "extravalue", f.Annotations["extrakey"]; want != got { - t.Errorf("Want %q, but got %q", want, got) - } - - if want, got := "anothervalue", f.Annotations["anotherkey"]; want != got { - t.Errorf("Want %q, but got %q", want, got) - } - - kk := []string{"frobnitz", "sprocket", "dodad"} - for i, k := range f.Keywords { - if kk[i] != k { - t.Errorf("Expected %q, got %q", kk[i], k) - } - } -} - -func TestIsChartDir(t *testing.T) { - validChartDir, err := IsChartDir("testdata/frobnitz") - if !validChartDir { - t.Errorf("unexpected error while reading chart-directory: (%v)", err) - return - } - validChartDir, err = IsChartDir("testdata") - if validChartDir || err == nil { - t.Errorf("expected error but did not get any") - return - } -} diff --git a/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go deleted file mode 100644 index f634d6425..000000000 --- a/pkg/chartutil/coalesce.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -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 chartutil - -import ( - "fmt" - "log" - - "github.com/mitchellh/copystructure" - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" -) - -func concatPrefix(a, b string) string { - if a == "" { - return b - } - return fmt.Sprintf("%s.%s", a, b) -} - -// CoalesceValues coalesces all of the values in a chart (and its subcharts). -// -// Values are coalesced together using the following rules: -// -// - Values in a higher level chart always override values in a lower-level -// dependency chart -// - Scalar values and arrays are replaced, maps are merged -// - A chart has access to all of the variables for it, as well as all of -// the values destined for its dependencies. -func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) { - v, err := copystructure.Copy(vals) - if err != nil { - return vals, err - } - - valsCopy := v.(map[string]interface{}) - // if we have an empty map, make sure it is initialized - if valsCopy == nil { - valsCopy = make(map[string]interface{}) - } - return coalesce(log.Printf, chrt, valsCopy, "") -} - -type printFn func(format string, v ...interface{}) - -// coalesce coalesces the dest values and the chart values, giving priority to the dest values. -// -// This is a helper function for CoalesceValues. -func coalesce(printf printFn, ch *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) { - coalesceValues(printf, ch, dest, prefix) - return coalesceDeps(printf, ch, dest, prefix) -} - -// coalesceDeps coalesces the dependencies of the given chart. -func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) { - for _, subchart := range chrt.Dependencies() { - if c, ok := dest[subchart.Name()]; !ok { - // If dest doesn't already have the key, create it. - dest[subchart.Name()] = make(map[string]interface{}) - } else if !istable(c) { - return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), c) - } - if dv, ok := dest[subchart.Name()]; ok { - dvmap := dv.(map[string]interface{}) - subPrefix := concatPrefix(prefix, chrt.Metadata.Name) - - // Get globals out of dest and merge them into dvmap. - coalesceGlobals(printf, dvmap, dest, subPrefix) - - // Now coalesce the rest of the values. - var err error - dest[subchart.Name()], err = coalesce(printf, subchart, dvmap, subPrefix) - if err != nil { - return dest, err - } - } - } - return dest, nil -} - -// coalesceGlobals copies the globals out of src and merges them into dest. -// -// For convenience, returns dest. -func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string) { - var dg, sg map[string]interface{} - - if destglob, ok := dest[GlobalKey]; !ok { - dg = make(map[string]interface{}) - } else if dg, ok = destglob.(map[string]interface{}); !ok { - printf("warning: skipping globals because destination %s is not a table.", GlobalKey) - return - } - - if srcglob, ok := src[GlobalKey]; !ok { - sg = make(map[string]interface{}) - } else if sg, ok = srcglob.(map[string]interface{}); !ok { - printf("warning: skipping globals because source %s is not a table.", GlobalKey) - return - } - - // EXPERIMENTAL: In the past, we have disallowed globals to test tables. This - // reverses that decision. It may somehow be possible to introduce a loop - // here, but I haven't found a way. So for the time being, let's allow - // tables in globals. - for key, val := range sg { - if istable(val) { - vv := copyMap(val.(map[string]interface{})) - if destv, ok := dg[key]; !ok { - // Here there is no merge. We're just adding. - dg[key] = vv - } else { - if destvmap, ok := destv.(map[string]interface{}); !ok { - printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) - } else { - // Basically, we reverse order of coalesce here to merge - // top-down. - subPrefix := concatPrefix(prefix, key) - coalesceTablesFullKey(printf, vv, destvmap, subPrefix) - dg[key] = vv - } - } - } else if dv, ok := dg[key]; ok && istable(dv) { - // It's not clear if this condition can actually ever trigger. - printf("key %s is table. Skipping", key) - } else { - // TODO: Do we need to do any additional checking on the value? - dg[key] = val - } - } - dest[GlobalKey] = dg -} - -func copyMap(src map[string]interface{}) map[string]interface{} { - m := make(map[string]interface{}, len(src)) - for k, v := range src { - m[k] = v - } - return m -} - -// coalesceValues builds up a values map for a particular chart. -// -// Values in v will override the values in the chart. -func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, prefix string) { - subPrefix := concatPrefix(prefix, c.Metadata.Name) - for key, val := range c.Values { - if value, ok := v[key]; ok { - if value == nil { - // When the YAML value is null, we remove the value's key. - // This allows Helm's various sources of values (value files or --set) to - // remove incompatible keys from any previous chart, file, or set values. - delete(v, key) - } else if dest, ok := value.(map[string]interface{}); ok { - // if v[key] is a table, merge nv's val table into v[key]. - src, ok := val.(map[string]interface{}) - if !ok { - // If the original value is nil, there is nothing to coalesce, so we don't print - // the warning - if val != nil { - printf("warning: skipped value for %s.%s: Not a table.", subPrefix, key) - } - } else { - // Because v has higher precedence than nv, dest values override src - // values. - coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key)) - } - } - } else { - // If the key is not in v, copy it from nv. - v[key] = val - } - } -} - -// CoalesceTables merges a source map into a destination map. -// -// dest is considered authoritative. -func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { - return coalesceTablesFullKey(log.Printf, dst, src, "") -} - -// coalesceTablesFullKey merges a source map into a destination map. -// -// dest is considered authoritative. -func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string) map[string]interface{} { - // When --reuse-values is set but there are no modifications yet, return new values - if src == nil { - return dst - } - if dst == nil { - return src - } - // Because dest has higher precedence than src, dest values override src - // values. - for key, val := range src { - fullkey := concatPrefix(prefix, key) - if dv, ok := dst[key]; ok && dv == nil { - delete(dst, key) - } else if !ok { - dst[key] = val - } else if istable(val) { - if istable(dv) { - coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey) - } else { - printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) - } - } else if istable(dv) && val != nil { - printf("warning: destination for %s is a table. Ignoring non-table value (%v)", fullkey, val) - } - } - return dst -} diff --git a/pkg/chartutil/coalesce_test.go b/pkg/chartutil/coalesce_test.go deleted file mode 100644 index 3fe93f5ff..000000000 --- a/pkg/chartutil/coalesce_test.go +++ /dev/null @@ -1,409 +0,0 @@ -/* -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 chartutil - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/pkg/chart" -) - -// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 -var testCoalesceValuesYaml = []byte(` -top: yup -bottom: null -right: Null -left: NULL -front: ~ -back: "" -nested: - boat: null - -global: - name: Ishmael - subject: Queequeg - nested: - boat: true - -pequod: - global: - name: Stinky - harpooner: Tashtego - nested: - boat: false - sail: true - ahab: - scope: whale - boat: null - nested: - foo: true - bar: null -`) - -func withDeps(c *chart.Chart, deps ...*chart.Chart) *chart.Chart { - c.AddDependency(deps...) - return c -} - -func TestCoalesceValues(t *testing.T) { - is := assert.New(t) - - c := withDeps(&chart.Chart{ - Metadata: &chart.Metadata{Name: "moby"}, - Values: map[string]interface{}{ - "back": "exists", - "bottom": "exists", - "front": "exists", - "left": "exists", - "name": "moby", - "nested": map[string]interface{}{"boat": true}, - "override": "bad", - "right": "exists", - "scope": "moby", - "top": "nope", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l0": "moby"}, - }, - }, - }, - withDeps(&chart.Chart{ - Metadata: &chart.Metadata{Name: "pequod"}, - Values: map[string]interface{}{ - "name": "pequod", - "scope": "pequod", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "pequod"}, - }, - }, - }, - &chart.Chart{ - Metadata: &chart.Metadata{Name: "ahab"}, - Values: map[string]interface{}{ - "global": map[string]interface{}{ - "nested": map[string]interface{}{"foo": "bar"}, - "nested2": map[string]interface{}{"l2": "ahab"}, - }, - "scope": "ahab", - "name": "ahab", - "boat": true, - "nested": map[string]interface{}{"foo": false, "bar": true}, - }, - }, - ), - &chart.Chart{ - Metadata: &chart.Metadata{Name: "spouter"}, - Values: map[string]interface{}{ - "scope": "spouter", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "spouter"}, - }, - }, - }, - ) - - vals, err := ReadValues(testCoalesceValuesYaml) - if err != nil { - t.Fatal(err) - } - - // taking a copy of the values before passing it - // to CoalesceValues as argument, so that we can - // use it for asserting later - valsCopy := make(Values, len(vals)) - for key, value := range vals { - valsCopy[key] = value - } - - v, err := CoalesceValues(c, vals) - if err != nil { - t.Fatal(err) - } - j, _ := json.MarshalIndent(v, "", " ") - t.Logf("Coalesced Values: %s", string(j)) - - tests := []struct { - tpl string - expect string - }{ - {"{{.top}}", "yup"}, - {"{{.back}}", ""}, - {"{{.name}}", "moby"}, - {"{{.global.name}}", "Ishmael"}, - {"{{.global.subject}}", "Queequeg"}, - {"{{.global.harpooner}}", ""}, - {"{{.pequod.name}}", "pequod"}, - {"{{.pequod.ahab.name}}", "ahab"}, - {"{{.pequod.ahab.scope}}", "whale"}, - {"{{.pequod.ahab.nested.foo}}", "true"}, - {"{{.pequod.ahab.global.name}}", "Ishmael"}, - {"{{.pequod.ahab.global.nested.foo}}", "bar"}, - {"{{.pequod.ahab.global.subject}}", "Queequeg"}, - {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, - {"{{.pequod.global.name}}", "Ishmael"}, - {"{{.pequod.global.nested.foo}}", ""}, - {"{{.pequod.global.subject}}", "Queequeg"}, - {"{{.spouter.global.name}}", "Ishmael"}, - {"{{.spouter.global.harpooner}}", ""}, - - {"{{.global.nested.boat}}", "true"}, - {"{{.pequod.global.nested.boat}}", "true"}, - {"{{.spouter.global.nested.boat}}", "true"}, - {"{{.pequod.global.nested.sail}}", "true"}, - {"{{.spouter.global.nested.sail}}", ""}, - - {"{{.global.nested2.l0}}", "moby"}, - {"{{.global.nested2.l1}}", ""}, - {"{{.global.nested2.l2}}", ""}, - {"{{.pequod.global.nested2.l0}}", "moby"}, - {"{{.pequod.global.nested2.l1}}", "pequod"}, - {"{{.pequod.global.nested2.l2}}", ""}, - {"{{.pequod.ahab.global.nested2.l0}}", "moby"}, - {"{{.pequod.ahab.global.nested2.l1}}", "pequod"}, - {"{{.pequod.ahab.global.nested2.l2}}", "ahab"}, - {"{{.spouter.global.nested2.l0}}", "moby"}, - {"{{.spouter.global.nested2.l1}}", "spouter"}, - {"{{.spouter.global.nested2.l2}}", ""}, - } - - for _, tt := range tests { - if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect { - t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o) - } - } - - nullKeys := []string{"bottom", "right", "left", "front"} - for _, nullKey := range nullKeys { - if _, ok := v[nullKey]; ok { - t.Errorf("Expected key %q to be removed, still present", nullKey) - } - } - - if _, ok := v["nested"].(map[string]interface{})["boat"]; ok { - t.Error("Expected nested boat key to be removed, still present") - } - - subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) - if _, ok := subchart["boat"]; ok { - t.Error("Expected subchart boat key to be removed, still present") - } - - if _, ok := subchart["nested"].(map[string]interface{})["bar"]; ok { - t.Error("Expected subchart nested bar key to be removed, still present") - } - - // CoalesceValues should not mutate the passed arguments - is.Equal(valsCopy, vals) -} - -func TestCoalesceTables(t *testing.T) { - dst := map[string]interface{}{ - "name": "Ishmael", - "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", - "country": nil, - }, - "details": map[string]interface{}{ - "friends": []string{"Tashtego"}, - }, - "boat": "pequod", - "hole": nil, - } - src := map[string]interface{}{ - "occupation": "whaler", - "address": map[string]interface{}{ - "state": "MA", - "street": "234 Spouter Inn Ct.", - "country": "US", - }, - "details": "empty", - "boat": map[string]interface{}{ - "mast": true, - }, - "hole": "black", - } - - // What we expect is that anything in dst overrides anything in src, but that - // otherwise the values are coalesced. - CoalesceTables(dst, src) - - if dst["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst["name"]) - } - if dst["occupation"] != "whaler" { - t.Errorf("Unexpected occupation: %s", dst["occupation"]) - } - - addr, ok := dst["address"].(map[string]interface{}) - if !ok { - t.Fatal("Address went away.") - } - - if addr["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr["street"]) - } - - if addr["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr["city"]) - } - - if addr["state"].(string) != "MA" { - t.Errorf("Unexpected state: %v", addr["state"]) - } - - if _, ok = addr["country"]; ok { - t.Error("The country is not left out.") - } - - if det, ok := dst["details"].(map[string]interface{}); !ok { - t.Fatalf("Details is the wrong type: %v", dst["details"]) - } else if _, ok := det["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } - - if dst["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst["boat"]) - } - - if _, ok = dst["hole"]; ok { - t.Error("The hole still exists.") - } - - dst2 := map[string]interface{}{ - "name": "Ishmael", - "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", - "country": "US", - }, - "details": map[string]interface{}{ - "friends": []string{"Tashtego"}, - }, - "boat": "pequod", - "hole": "black", - } - - // What we expect is that anything in dst should have all values set, - // this happens when the --reuse-values flag is set but the chart has no modifications yet - CoalesceTables(dst2, nil) - - if dst2["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst2["name"]) - } - - addr2, ok := dst2["address"].(map[string]interface{}) - if !ok { - t.Fatal("Address went away.") - } - - if addr2["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr2["street"]) - } - - if addr2["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr2["city"]) - } - - if addr2["country"].(string) != "US" { - t.Errorf("Unexpected Country: %v", addr2["country"]) - } - - if det2, ok := dst2["details"].(map[string]interface{}); !ok { - t.Fatalf("Details is the wrong type: %v", dst2["details"]) - } else if _, ok := det2["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } - - if dst2["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst2["boat"]) - } - - if dst2["hole"].(string) != "black" { - t.Errorf("Expected hole string, got %v", dst2["boat"]) - } -} - -func TestCoalesceValuesWarnings(t *testing.T) { - - c := withDeps(&chart.Chart{ - Metadata: &chart.Metadata{Name: "level1"}, - Values: map[string]interface{}{ - "name": "moby", - }, - }, - withDeps(&chart.Chart{ - Metadata: &chart.Metadata{Name: "level2"}, - Values: map[string]interface{}{ - "name": "pequod", - }, - }, - &chart.Chart{ - Metadata: &chart.Metadata{Name: "level3"}, - Values: map[string]interface{}{ - "name": "ahab", - "boat": true, - "spear": map[string]interface{}{ - "tip": true, - "sail": map[string]interface{}{ - "cotton": true, - }, - }, - }, - }, - ), - ) - - vals := map[string]interface{}{ - "level2": map[string]interface{}{ - "level3": map[string]interface{}{ - "boat": map[string]interface{}{"mast": true}, - "spear": map[string]interface{}{ - "tip": map[string]interface{}{ - "sharp": true, - }, - "sail": true, - }, - }, - }, - } - - warnings := make([]string, 0) - printf := func(format string, v ...interface{}) { - t.Logf(format, v...) - warnings = append(warnings, fmt.Sprintf(format, v...)) - } - - _, err := coalesce(printf, c, vals, "") - if err != nil { - t.Fatal(err) - } - - t.Logf("vals: %v", vals) - assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") - assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)") - assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])") - -} - -func TestConcatPrefix(t *testing.T) { - assert.Equal(t, "b", concatPrefix("", "b")) - assert.Equal(t, "a.b", concatPrefix("a", "b")) -} diff --git a/pkg/chartutil/compatible.go b/pkg/chartutil/compatible.go deleted file mode 100644 index f4656c913..000000000 --- a/pkg/chartutil/compatible.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -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 chartutil - -import "github.com/Masterminds/semver/v3" - -// IsCompatibleRange compares a version to a constraint. -// It returns true if the version matches the constraint, and false in all other cases. -func IsCompatibleRange(constraint, ver string) bool { - sv, err := semver.NewVersion(ver) - if err != nil { - return false - } - - c, err := semver.NewConstraint(constraint) - if err != nil { - return false - } - return c.Check(sv) -} diff --git a/pkg/chartutil/compatible_test.go b/pkg/chartutil/compatible_test.go deleted file mode 100644 index df7be6161..000000000 --- a/pkg/chartutil/compatible_test.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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 version represents the current version of the project. -package chartutil - -import "testing" - -func TestIsCompatibleRange(t *testing.T) { - tests := []struct { - constraint string - ver string - expected bool - }{ - {"v2.0.0-alpha.4", "v2.0.0-alpha.4", true}, - {"v2.0.0-alpha.3", "v2.0.0-alpha.4", false}, - {"v2.0.0", "v2.0.0-alpha.4", false}, - {"v2.0.0-alpha.4", "v2.0.0", false}, - {"~v2.0.0", "v2.0.1", true}, - {"v2", "v2.0.0", true}, - {">2.0.0", "v2.1.1", true}, - {"v2.1.*", "v2.1.1", true}, - } - - for _, tt := range tests { - if IsCompatibleRange(tt.constraint, tt.ver) != tt.expected { - t.Errorf("expected constraint %s to be %v for %s", tt.constraint, tt.expected, tt.ver) - } - } -} diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go deleted file mode 100644 index 3a8f3cc5a..000000000 --- a/pkg/chartutil/create.go +++ /dev/null @@ -1,687 +0,0 @@ -/* -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 chartutil - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -// chartName is a regular expression for testing the supplied name of a chart. -// This regular expression is probably stricter than it needs to be. We can relax it -// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be -// problematic. -var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$") - -const ( - // ChartfileName is the default Chart file name. - ChartfileName = "Chart.yaml" - // ValuesfileName is the default values file name. - ValuesfileName = "values.yaml" - // SchemafileName is the default values schema file name. - SchemafileName = "values.schema.json" - // TemplatesDir is the relative directory name for templates. - TemplatesDir = "templates" - // ChartsDir is the relative directory name for charts dependencies. - ChartsDir = "charts" - // TemplatesTestsDir is the relative directory name for tests. - TemplatesTestsDir = TemplatesDir + sep + "tests" - // IgnorefileName is the name of the Helm ignore file. - IgnorefileName = ".helmignore" - // IngressFileName is the name of the example ingress file. - IngressFileName = TemplatesDir + sep + "ingress.yaml" - // DeploymentName is the name of the example deployment file. - DeploymentName = TemplatesDir + sep + "deployment.yaml" - // ServiceName is the name of the example service file. - ServiceName = TemplatesDir + sep + "service.yaml" - // ServiceAccountName is the name of the example serviceaccount file. - ServiceAccountName = TemplatesDir + sep + "serviceaccount.yaml" - // HorizontalPodAutoscalerName is the name of the example hpa file. - HorizontalPodAutoscalerName = TemplatesDir + sep + "hpa.yaml" - // NotesName is the name of the example NOTES.txt file. - NotesName = TemplatesDir + sep + "NOTES.txt" - // HelpersName is the name of the example helpers file. - HelpersName = TemplatesDir + sep + "_helpers.tpl" - // TestConnectionName is the name of the example test file. - TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml" -) - -// maxChartNameLength is lower than the limits we know of with certain file systems, -// and with certain Kubernetes fields. -const maxChartNameLength = 250 - -const sep = string(filepath.Separator) - -const defaultChartfile = `apiVersion: v2 -name: %s -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" -` - -const defaultValues = `# Default values for %s. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - repository: nginx - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - -podAnnotations: {} - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 80 - -ingress: - enabled: false - className: "" - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -nodeSelector: {} - -tolerations: [] - -affinity: {} -` - -const defaultIgnore = `# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ -` - -const defaultIngress = `{{- if .Values.ingress.enabled -}} -{{- $fullName := include ".fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include ".labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} - {{- end }} - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} -` - -const defaultDeployment = `apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include ".fullname" . }} - labels: - {{- include ".labels" . | nindent 4 }} -spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include ".selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include ".selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include ".serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: {{ .Values.service.port }} - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http - resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -` - -const defaultService = `apiVersion: v1 -kind: Service -metadata: - name: {{ include ".fullname" . }} - labels: - {{- include ".labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include ".selectorLabels" . | nindent 4 }} -` - -const defaultServiceAccount = `{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include ".serviceAccountName" . }} - labels: - {{- include ".labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} -` - -const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include ".fullname" . }} - labels: - {{- include ".labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include ".fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} -` - -const defaultNotes = `1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include ".fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include ".fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include ".fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include ".name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} -` - -const defaultHelpers = `{{/* -Expand the name of the chart. -*/}} -{{- define ".name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define ".fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define ".chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define ".labels" -}} -helm.sh/chart: {{ include ".chart" . }} -{{ include ".selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define ".selectorLabels" -}} -app.kubernetes.io/name: {{ include ".name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define ".serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include ".fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} -` - -const defaultTestConnection = `apiVersion: v1 -kind: Pod -metadata: - name: "{{ include ".fullname" . }}-test-connection" - labels: - {{- include ".labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include ".fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never -` - -// Stderr is an io.Writer to which error messages can be written -// -// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward -// compatibility. -var Stderr io.Writer = os.Stderr - -// CreateFrom creates a new chart, but scaffolds it from the src chart. -func CreateFrom(chartfile *chart.Metadata, dest, src string) error { - schart, err := loader.Load(src) - if err != nil { - return errors.Wrapf(err, "could not load %s", src) - } - - schart.Metadata = chartfile - - var updatedTemplates []*chart.File - - for _, template := range schart.Templates { - newData := transform(string(template.Data), schart.Name()) - updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData}) - } - - schart.Templates = updatedTemplates - b, err := yaml.Marshal(schart.Values) - if err != nil { - return errors.Wrap(err, "reading values file") - } - - var m map[string]interface{} - if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { - return errors.Wrap(err, "transforming values file") - } - schart.Values = m - - // SaveDir looks for the file values.yaml when saving rather than the values - // key in order to preserve the comments in the YAML. The name placeholder - // needs to be replaced on that file. - for _, f := range schart.Raw { - if f.Name == ValuesfileName { - f.Data = transform(string(f.Data), schart.Name()) - } - } - - return SaveDir(schart, dest) -} - -// Create creates a new chart in a directory. -// -// Inside of dir, this will create a directory based on the name of -// chartfile.Name. It will then write the Chart.yaml into this directory and -// create the (empty) appropriate directories. -// -// The returned string will point to the newly created directory. It will be -// an absolute path, even if the provided base directory was relative. -// -// If dir does not exist, this will return an error. -// If Chart.yaml or any directories cannot be created, this will return an -// error. In such a case, this will attempt to clean up by removing the -// new chart directory. -func Create(name, dir string) (string, error) { - - // Sanity-check the name of a chart so user doesn't create one that causes problems. - if err := validateChartName(name); err != nil { - return "", err - } - - path, err := filepath.Abs(dir) - if err != nil { - return path, err - } - - if fi, err := os.Stat(path); err != nil { - return path, err - } else if !fi.IsDir() { - return path, errors.Errorf("no such directory %s", path) - } - - cdir := filepath.Join(path, name) - if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { - return cdir, errors.Errorf("file %s already exists and is not a directory", cdir) - } - - files := []struct { - path string - content []byte - }{ - { - // Chart.yaml - path: filepath.Join(cdir, ChartfileName), - content: []byte(fmt.Sprintf(defaultChartfile, name)), - }, - { - // values.yaml - path: filepath.Join(cdir, ValuesfileName), - content: []byte(fmt.Sprintf(defaultValues, name)), - }, - { - // .helmignore - path: filepath.Join(cdir, IgnorefileName), - content: []byte(defaultIgnore), - }, - { - // ingress.yaml - path: filepath.Join(cdir, IngressFileName), - content: transform(defaultIngress, name), - }, - { - // deployment.yaml - path: filepath.Join(cdir, DeploymentName), - content: transform(defaultDeployment, name), - }, - { - // service.yaml - path: filepath.Join(cdir, ServiceName), - content: transform(defaultService, name), - }, - { - // serviceaccount.yaml - path: filepath.Join(cdir, ServiceAccountName), - content: transform(defaultServiceAccount, name), - }, - { - // hpa.yaml - path: filepath.Join(cdir, HorizontalPodAutoscalerName), - content: transform(defaultHorizontalPodAutoscaler, name), - }, - { - // NOTES.txt - path: filepath.Join(cdir, NotesName), - content: transform(defaultNotes, name), - }, - { - // _helpers.tpl - path: filepath.Join(cdir, HelpersName), - content: transform(defaultHelpers, name), - }, - { - // test-connection.yaml - path: filepath.Join(cdir, TestConnectionName), - content: transform(defaultTestConnection, name), - }, - } - - for _, file := range files { - if _, err := os.Stat(file.path); err == nil { - // There is no handle to a preferred output stream here. - fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path) - } - if err := writeFile(file.path, file.content); err != nil { - return cdir, err - } - } - // Need to add the ChartsDir explicitly as it does not contain any file OOTB - if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil { - return cdir, err - } - return cdir, nil -} - -// transform performs a string replacement of the specified source for -// a given key with the replacement string -func transform(src, replacement string) []byte { - return []byte(strings.ReplaceAll(src, "", replacement)) -} - -func writeFile(name string, content []byte) error { - if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil { - return err - } - return ioutil.WriteFile(name, content, 0644) -} - -func validateChartName(name string) error { - if name == "" || len(name) > maxChartNameLength { - return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength) - } - if !chartName.MatchString(name) { - return fmt.Errorf("chart name must match the regular expression %q", chartName.String()) - } - return nil -} diff --git a/pkg/chartutil/create_test.go b/pkg/chartutil/create_test.go deleted file mode 100644 index f123a37cd..000000000 --- a/pkg/chartutil/create_test.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -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 chartutil - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -func TestCreate(t *testing.T) { - tdir := t.TempDir() - - c, err := Create("foo", tdir) - if err != nil { - t.Fatal(err) - } - - dir := filepath.Join(tdir, "foo") - - mychart, err := loader.LoadDir(c) - if err != nil { - t.Fatalf("Failed to load newly created chart %q: %s", c, err) - } - - if mychart.Name() != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) - } - - for _, f := range []string{ - ChartfileName, - DeploymentName, - HelpersName, - IgnorefileName, - NotesName, - ServiceAccountName, - ServiceName, - TemplatesDir, - TemplatesTestsDir, - TestConnectionName, - ValuesfileName, - } { - if _, err := os.Stat(filepath.Join(dir, f)); err != nil { - t.Errorf("Expected %s file: %s", f, err) - } - } -} - -func TestCreateFrom(t *testing.T) { - tdir := t.TempDir() - - cf := &chart.Metadata{ - APIVersion: chart.APIVersionV1, - Name: "foo", - Version: "0.1.0", - } - srcdir := "./testdata/frobnitz/charts/mariner" - - if err := CreateFrom(cf, tdir, srcdir); err != nil { - t.Fatal(err) - } - - dir := filepath.Join(tdir, "foo") - c := filepath.Join(tdir, cf.Name) - mychart, err := loader.LoadDir(c) - if err != nil { - t.Fatalf("Failed to load newly created chart %q: %s", c, err) - } - - if mychart.Name() != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) - } - - for _, f := range []string{ - ChartfileName, - ValuesfileName, - filepath.Join(TemplatesDir, "placeholder.tpl"), - } { - if _, err := os.Stat(filepath.Join(dir, f)); err != nil { - t.Errorf("Expected %s file: %s", f, err) - } - - // Check each file to make sure has been replaced - b, err := ioutil.ReadFile(filepath.Join(dir, f)) - if err != nil { - t.Errorf("Unable to read file %s: %s", f, err) - } - if bytes.Contains(b, []byte("")) { - t.Errorf("File %s contains ", f) - } - } -} - -// TestCreate_Overwrite is a regression test for making sure that files are overwritten. -func TestCreate_Overwrite(t *testing.T) { - tdir := t.TempDir() - - var errlog bytes.Buffer - - if _, err := Create("foo", tdir); err != nil { - t.Fatal(err) - } - - dir := filepath.Join(tdir, "foo") - - tplname := filepath.Join(dir, "templates/hpa.yaml") - writeFile(tplname, []byte("FOO")) - - // Now re-run the create - Stderr = &errlog - if _, err := Create("foo", tdir); err != nil { - t.Fatal(err) - } - - data, err := ioutil.ReadFile(tplname) - if err != nil { - t.Fatal(err) - } - - if string(data) == "FOO" { - t.Fatal("File that should have been modified was not.") - } - - if errlog.Len() == 0 { - t.Errorf("Expected warnings about overwriting files.") - } -} - -func TestValidateChartName(t *testing.T) { - for name, shouldPass := range map[string]bool{ - "": false, - "abcdefghijklmnopqrstuvwxyz-_.": true, - "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": true, - "$hello": false, - "Hellô": false, - "he%%o": false, - "he\nllo": false, - - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "abcdefghijklmnopqrstuvwxyz-_." + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false, - } { - if err := validateChartName(name); (err != nil) == shouldPass { - t.Errorf("test for %q failed", name) - } - } -} diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go deleted file mode 100644 index e01b95bf7..000000000 --- a/pkg/chartutil/dependencies.go +++ /dev/null @@ -1,285 +0,0 @@ -/* -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 chartutil - -import ( - "log" - "strings" - - "helm.sh/helm/v3/pkg/chart" -) - -// ProcessDependencies checks through this chart's dependencies, processing accordingly. -func ProcessDependencies(c *chart.Chart, v Values) error { - if err := processDependencyEnabled(c, v, ""); err != nil { - return err - } - return processDependencyImportValues(c) -} - -// processDependencyConditions disables charts based on condition path value in values -func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) { - if reqs == nil { - return - } - for _, r := range reqs { - for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") { - if len(c) > 0 { - // retrieve value - vv, err := cvals.PathValue(cpath + c) - if err == nil { - // if not bool, warn - if bv, ok := vv.(bool); ok { - r.Enabled = bv - break - } else { - log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) - } - } else if _, ok := err.(ErrNoValue); !ok { - // this is a real error - log.Printf("Warning: PathValue returned error %v", err) - } - } - } - } -} - -// processDependencyTags disables charts based on tags in values -func processDependencyTags(reqs []*chart.Dependency, cvals Values) { - if reqs == nil { - return - } - vt, err := cvals.Table("tags") - if err != nil { - return - } - for _, r := range reqs { - var hasTrue, hasFalse bool - for _, k := range r.Tags { - if b, ok := vt[k]; ok { - // if not bool, warn - if bv, ok := b.(bool); ok { - if bv { - hasTrue = true - } else { - hasFalse = true - } - } else { - log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name) - } - } - } - if !hasTrue && hasFalse { - r.Enabled = false - } else if hasTrue || !hasTrue && !hasFalse { - r.Enabled = true - } - } -} - -func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart { - for _, c := range charts { - if c == nil { - continue - } - if c.Name() != dep.Name { - continue - } - if !IsCompatibleRange(dep.Version, c.Metadata.Version) { - continue - } - - out := *c - md := *c.Metadata - out.Metadata = &md - - if dep.Alias != "" { - md.Name = dep.Alias - } - return &out - } - return nil -} - -// processDependencyEnabled removes disabled charts from dependencies -func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { - if c.Metadata.Dependencies == nil { - return nil - } - - var chartDependencies []*chart.Chart - // If any dependency is not a part of Chart.yaml - // then this should be added to chartDependencies. - // However, if the dependency is already specified in Chart.yaml - // we should not add it, as it would be anyways processed from Chart.yaml - -Loop: - for _, existing := range c.Dependencies() { - for _, req := range c.Metadata.Dependencies { - if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) { - continue Loop - } - } - chartDependencies = append(chartDependencies, existing) - } - - for _, req := range c.Metadata.Dependencies { - if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil { - chartDependencies = append(chartDependencies, chartDependency) - } - if req.Alias != "" { - req.Name = req.Alias - } - } - c.SetDependencies(chartDependencies...) - - // set all to true - for _, lr := range c.Metadata.Dependencies { - lr.Enabled = true - } - cvals, err := CoalesceValues(c, v) - if err != nil { - return err - } - // flag dependencies as enabled/disabled - processDependencyTags(c.Metadata.Dependencies, cvals) - processDependencyConditions(c.Metadata.Dependencies, cvals, path) - // make a map of charts to remove - rm := map[string]struct{}{} - for _, r := range c.Metadata.Dependencies { - if !r.Enabled { - // remove disabled chart - rm[r.Name] = struct{}{} - } - } - // don't keep disabled charts in new slice - cd := []*chart.Chart{} - copy(cd, c.Dependencies()[:0]) - for _, n := range c.Dependencies() { - if _, ok := rm[n.Metadata.Name]; !ok { - cd = append(cd, n) - } - } - // don't keep disabled charts in metadata - cdMetadata := []*chart.Dependency{} - copy(cdMetadata, c.Metadata.Dependencies[:0]) - for _, n := range c.Metadata.Dependencies { - if _, ok := rm[n.Name]; !ok { - cdMetadata = append(cdMetadata, n) - } - } - - // recursively call self to process sub dependencies - for _, t := range cd { - subpath := path + t.Metadata.Name + "." - if err := processDependencyEnabled(t, cvals, subpath); err != nil { - return err - } - } - // set the correct dependencies in metadata - c.Metadata.Dependencies = nil - c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...) - c.SetDependencies(cd...) - - return nil -} - -// pathToMap creates a nested map given a YAML path in dot notation. -func pathToMap(path string, data map[string]interface{}) map[string]interface{} { - if path == "." { - return data - } - return set(parsePath(path), data) -} - -func set(path []string, data map[string]interface{}) map[string]interface{} { - if len(path) == 0 { - return nil - } - cur := data - for i := len(path) - 1; i >= 0; i-- { - cur = map[string]interface{}{path[i]: cur} - } - return cur -} - -// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. -func processImportValues(c *chart.Chart) error { - if c.Metadata.Dependencies == nil { - return nil - } - // combine chart values and empty config to get Values - cvals, err := CoalesceValues(c, nil) - if err != nil { - return err - } - b := make(map[string]interface{}) - // import values from each dependency if specified in import-values - for _, r := range c.Metadata.Dependencies { - var outiv []interface{} - for _, riv := range r.ImportValues { - switch iv := riv.(type) { - case map[string]interface{}: - child := iv["child"].(string) - parent := iv["parent"].(string) - - outiv = append(outiv, map[string]string{ - "child": child, - "parent": parent, - }) - - // get child table - vv, err := cvals.Table(r.Name + "." + child) - if err != nil { - log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err) - continue - } - // create value map from child to be merged into parent - b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap())) - case string: - child := "exports." + iv - outiv = append(outiv, map[string]string{ - "child": child, - "parent": ".", - }) - vm, err := cvals.Table(r.Name + "." + child) - if err != nil { - log.Printf("Warning: ImportValues missing table: %v", err) - continue - } - b = CoalesceTables(b, vm.AsMap()) - } - } - // set our formatted import values - r.ImportValues = outiv - } - - // set the new values - c.Values = CoalesceTables(cvals, b) - - return nil -} - -// processDependencyImportValues imports specified chart values from child to parent. -func processDependencyImportValues(c *chart.Chart) error { - for _, d := range c.Dependencies() { - // recurse - if err := processDependencyImportValues(d); err != nil { - return err - } - } - return processImportValues(c) -} diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chartutil/dependencies_test.go deleted file mode 100644 index 7f5e74956..000000000 --- a/pkg/chartutil/dependencies_test.go +++ /dev/null @@ -1,457 +0,0 @@ -/* -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 chartutil - -import ( - "os" - "path/filepath" - "sort" - "strconv" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -func loadChart(t *testing.T, path string) *chart.Chart { - t.Helper() - c, err := loader.Load(path) - if err != nil { - t.Fatalf("failed to load testdata: %s", err) - } - return c -} - -func TestLoadDependency(t *testing.T) { - tests := []*chart.Dependency{ - {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, - {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, - } - - check := func(deps []*chart.Dependency) { - if len(deps) != 2 { - t.Errorf("expected 2 dependencies, got %d", len(deps)) - } - for i, tt := range tests { - if deps[i].Name != tt.Name { - t.Errorf("expected dependency named %q, got %q", tt.Name, deps[i].Name) - } - if deps[i].Version != tt.Version { - t.Errorf("expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) - } - if deps[i].Repository != tt.Repository { - t.Errorf("expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository) - } - } - } - c := loadChart(t, "testdata/frobnitz") - check(c.Metadata.Dependencies) - check(c.Lock.Dependencies) -} - -func TestDependencyEnabled(t *testing.T) { - type M = map[string]interface{} - tests := []struct { - name string - v M - e []string // expected charts including duplicates in alphanumeric order - }{{ - "tags with no effect", - M{"tags": M{"nothinguseful": false}}, - []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb"}, - }, { - "tags disabling a group", - M{"tags": M{"front-end": false}}, - []string{"parentchart"}, - }, { - "tags disabling a group and enabling a different group", - M{"tags": M{"front-end": false, "back-end": true}}, - []string{"parentchart", "parentchart.subchart2", "parentchart.subchart2.subchartb", "parentchart.subchart2.subchartc"}, - }, { - "tags disabling only children, children still enabled since tag front-end=true in values.yaml", - M{"tags": M{"subcharta": false, "subchartb": false}}, - []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb"}, - }, { - "tags disabling all parents/children with additional tag re-enabling a parent", - M{"tags": M{"front-end": false, "subchart1": true, "back-end": false}}, - []string{"parentchart", "parentchart.subchart1"}, - }, { - "conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml", - M{"subchart1": M{"enabled": true}, "subchart2": M{"enabled": true}}, - []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb", "parentchart.subchart2"}, - }, { - "conditions disabling the parent charts, effectively disabling children", - M{"subchart1": M{"enabled": false}, "subchart2": M{"enabled": false}}, - []string{"parentchart"}, - }, { - "conditions a child using the second condition path of child's condition", - M{"subchart1": M{"subcharta": M{"enabled": false}}}, - []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subchartb"}, - }, { - "tags enabling a parent/child group with condition disabling one child", - M{"subchart2": M{"subchartc": M{"enabled": false}}, "tags": M{"back-end": true}}, - []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb", "parentchart.subchart2", "parentchart.subchart2.subchartb"}, - }, { - "tags will not enable a child if parent is explicitly disabled with condition", - M{"subchart1": M{"enabled": false}, "tags": M{"front-end": true}}, - []string{"parentchart"}, - }, { - "subcharts with alias also respect conditions", - M{"subchart1": M{"enabled": false}, "subchart2alias": M{"enabled": true, "subchartb": M{"enabled": true}}}, - []string{"parentchart", "parentchart.subchart2alias", "parentchart.subchart2alias.subchartb"}, - }} - - for _, tc := range tests { - c := loadChart(t, "testdata/subpop") - t.Run(tc.name, func(t *testing.T) { - if err := processDependencyEnabled(c, tc.v, ""); err != nil { - t.Fatalf("error processing enabled dependencies %v", err) - } - - names := extractChartNames(c) - if len(names) != len(tc.e) { - t.Fatalf("slice lengths do not match got %v, expected %v", len(names), len(tc.e)) - } - for i := range names { - if names[i] != tc.e[i] { - t.Fatalf("slice values do not match got %v, expected %v", names, tc.e) - } - } - }) - } -} - -// extractCharts recursively searches chart dependencies returning all charts found -func extractChartNames(c *chart.Chart) []string { - var out []string - var fn func(c *chart.Chart) - fn = func(c *chart.Chart) { - out = append(out, c.ChartPath()) - for _, d := range c.Dependencies() { - fn(d) - } - } - fn(c) - sort.Strings(out) - return out -} - -func TestProcessDependencyImportValues(t *testing.T) { - c := loadChart(t, "testdata/subpop") - - e := make(map[string]string) - - e["imported-chart1.SC1bool"] = "true" - e["imported-chart1.SC1float"] = "3.14" - e["imported-chart1.SC1int"] = "100" - e["imported-chart1.SC1string"] = "dollywood" - e["imported-chart1.SC1extra1"] = "11" - e["imported-chart1.SPextra1"] = "helm rocks" - e["imported-chart1.SC1extra1"] = "11" - - e["imported-chartA.SCAbool"] = "false" - e["imported-chartA.SCAfloat"] = "3.1" - e["imported-chartA.SCAint"] = "55" - e["imported-chartA.SCAstring"] = "jabba" - e["imported-chartA.SPextra3"] = "1.337" - e["imported-chartA.SC1extra2"] = "1.337" - e["imported-chartA.SCAnested1.SCAnested2"] = "true" - - e["imported-chartA-B.SCAbool"] = "false" - e["imported-chartA-B.SCAfloat"] = "3.1" - e["imported-chartA-B.SCAint"] = "55" - e["imported-chartA-B.SCAstring"] = "jabba" - - e["imported-chartA-B.SCBbool"] = "true" - e["imported-chartA-B.SCBfloat"] = "7.77" - e["imported-chartA-B.SCBint"] = "33" - e["imported-chartA-B.SCBstring"] = "boba" - e["imported-chartA-B.SPextra5"] = "k8s" - e["imported-chartA-B.SC1extra5"] = "tiller" - - e["overridden-chart1.SC1bool"] = "false" - e["overridden-chart1.SC1float"] = "3.141592" - e["overridden-chart1.SC1int"] = "99" - e["overridden-chart1.SC1string"] = "pollywog" - e["overridden-chart1.SPextra2"] = "42" - - e["overridden-chartA.SCAbool"] = "true" - e["overridden-chartA.SCAfloat"] = "41.3" - e["overridden-chartA.SCAint"] = "808" - e["overridden-chartA.SCAstring"] = "jabberwocky" - e["overridden-chartA.SPextra4"] = "true" - - e["overridden-chartA-B.SCAbool"] = "true" - e["overridden-chartA-B.SCAfloat"] = "41.3" - e["overridden-chartA-B.SCAint"] = "808" - e["overridden-chartA-B.SCAstring"] = "jabberwocky" - e["overridden-chartA-B.SCBbool"] = "false" - e["overridden-chartA-B.SCBfloat"] = "1.99" - e["overridden-chartA-B.SCBint"] = "77" - e["overridden-chartA-B.SCBstring"] = "jango" - e["overridden-chartA-B.SPextra6"] = "111" - e["overridden-chartA-B.SCAextra1"] = "23" - e["overridden-chartA-B.SCBextra1"] = "13" - e["overridden-chartA-B.SC1extra6"] = "77" - - // `exports` style - e["SCBexported1B"] = "1965" - e["SC1extra7"] = "true" - e["SCBexported2A"] = "blaster" - e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" - - if err := processDependencyImportValues(c); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } - cc := Values(c.Values) - for kk, vv := range e { - pv, err := cc.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } - - switch pv := pv.(type) { - case float64: - if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { - t.Errorf("failed to match imported float value %v with expected %v", s, vv) - } - case bool: - if b := strconv.FormatBool(pv); b != vv { - t.Errorf("failed to match imported bool value %v with expected %v", b, vv) - } - default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q", pv, vv) - } - } - } -} - -func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { - c := loadChart(t, "testdata/three-level-dependent-chart/umbrella") - - e := make(map[string]string) - - e["app1.service.port"] = "3456" - e["app2.service.port"] = "8080" - - if err := processDependencyImportValues(c); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } - cc := Values(c.Values) - for kk, vv := range e { - pv, err := cc.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } - - switch pv := pv.(type) { - case float64: - if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { - t.Errorf("failed to match imported float value %v with expected %v", s, vv) - } - default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q", pv, vv) - } - } - } -} - -func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { - c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") - nameOverride := "parent-chart-prod" - - if err := processDependencyImportValues(c); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 1 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Metadata.Dependencies) != 1 { - t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) - } - - prodDependencyValues := c.Dependencies()[0].Values - if prodDependencyValues["nameOverride"] != nameOverride { - t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) - } -} - -func TestGetAliasDependency(t *testing.T) { - c := loadChart(t, "testdata/frobnitz") - req := c.Metadata.Dependencies - - if len(req) == 0 { - t.Fatalf("there are no dependencies to test") - } - - // Success case - aliasChart := getAliasDependency(c.Dependencies(), req[0]) - if aliasChart == nil { - t.Fatalf("failed to get dependency chart for alias %s", req[0].Name) - } - if req[0].Alias != "" { - if aliasChart.Name() != req[0].Alias { - t.Fatalf("dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name()) - } - } else if aliasChart.Name() != req[0].Name { - t.Fatalf("dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name()) - } - - if req[0].Version != "" { - if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatalf("dependency chart version is not in the compatible range") - } - } - - // Failure case - req[0].Name = "something-else" - if aliasChart := getAliasDependency(c.Dependencies(), req[0]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Name()) - } - - req[0].Version = "something else which is not in the compatible range" - if IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatalf("dependency chart version which is not in the compatible range should cause a failure other than a success ") - } -} - -func TestDependentChartAliases(t *testing.T) { - c := loadChart(t, "testdata/dependent-chart-alias") - req := c.Metadata.Dependencies - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 3 { - t.Fatal("expected alias dependencies to be added") - } - - if len(c.Dependencies()) != len(c.Metadata.Dependencies) { - t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) - } - - aliasChart := getAliasDependency(c.Dependencies(), req[2]) - - if aliasChart == nil { - t.Fatalf("failed to get dependency chart for alias %s", req[2].Name) - } - if req[2].Alias != "" { - if aliasChart.Name() != req[2].Alias { - t.Fatalf("dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name()) - } - } else if aliasChart.Name() != req[2].Name { - t.Fatalf("dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name()) - } - - req[2].Name = "dummy-name" - if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Name()) - } - -} - -func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { - c := loadChart(t, "testdata/dependent-chart-no-requirements-yaml") - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } -} - -func TestDependentChartWithSubChartsHelmignore(t *testing.T) { - // FIXME what does this test? - loadChart(t, "testdata/dependent-chart-helmignore") -} - -func TestDependentChartsWithSubChartsSymlink(t *testing.T) { - joonix := filepath.Join("testdata", "joonix") - if err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")); err != nil { - t.Fatal(err) - } - defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz")) - c := loadChart(t, joonix) - - if c.Name() != "joonix" { - t.Fatalf("unexpected chart name: %s", c.Name()) - } - if n := len(c.Dependencies()); n != 1 { - t.Fatalf("expected 1 dependency for this chart, but got %d", n) - } -} - -func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) { - c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml") - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Dependencies()) != len(c.Metadata.Dependencies) { - t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) - } -} - -func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) { - c := loadChart(t, "testdata/dependent-chart-with-mixed-requirements-yaml") - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Metadata.Dependencies) != 1 { - t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) - } -} diff --git a/pkg/chartutil/doc.go b/pkg/chartutil/doc.go deleted file mode 100644 index 8f06bcc9a..000000000 --- a/pkg/chartutil/doc.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -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 chartutil contains tools for working with charts. - -Charts are described in the chart package (pkg/chart). -This package provides utilities for serializing and deserializing charts. - -A chart can be represented on the file system in one of two ways: - - - As a directory that contains a Chart.yaml file and other chart things. - - As a tarred gzipped file containing a directory that then contains a - Chart.yaml file. - -This package provides utilities for working with those file formats. - -The preferred way of loading a chart is using 'loader.Load`: - - chart, err := loader.Load(filename) - -This will attempt to discover whether the file at 'filename' is a directory or -a chart archive. It will then load accordingly. - -For accepting raw compressed tar file data from an io.Reader, the -'loader.LoadArchive()' will read in the data, uncompress it, and unpack it -into a Chart. - -When creating charts in memory, use the 'helm.sh/helm/pkg/chart' -package directly. -*/ -package chartutil // import "helm.sh/helm/v3/pkg/chartutil" diff --git a/pkg/chartutil/errors.go b/pkg/chartutil/errors.go deleted file mode 100644 index fcdcc27ea..000000000 --- a/pkg/chartutil/errors.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 chartutil - -import ( - "fmt" -) - -// ErrNoTable indicates that a chart does not have a matching table. -type ErrNoTable struct { - Key string -} - -func (e ErrNoTable) Error() string { return fmt.Sprintf("%q is not a table", e.Key) } - -// ErrNoValue indicates that Values does not contain a key with a value -type ErrNoValue struct { - Key string -} - -func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) } diff --git a/pkg/chartutil/errors_test.go b/pkg/chartutil/errors_test.go deleted file mode 100644 index 3f63e3733..000000000 --- a/pkg/chartutil/errors_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 chartutil - -import ( - "testing" -) - -func TestErrorNoTableDoesNotPanic(t *testing.T) { - x := "empty" - - y := ErrNoTable{x} - - t.Logf("error is: %s", y) -} - -func TestErrorNoValueDoesNotPanic(t *testing.T) { - x := "empty" - - y := ErrNoValue{x} - - t.Logf("error is: %s", y) -} diff --git a/pkg/chartutil/expand.go b/pkg/chartutil/expand.go deleted file mode 100644 index 6ad09e417..000000000 --- a/pkg/chartutil/expand.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -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 chartutil - -import ( - "io" - "io/ioutil" - "os" - "path/filepath" - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -// Expand uncompresses and extracts a chart into the specified directory. -func Expand(dir string, r io.Reader) error { - files, err := loader.LoadArchiveFiles(r) - if err != nil { - return err - } - - // Get the name of the chart - var chartName string - for _, file := range files { - if file.Name == "Chart.yaml" { - ch := &chart.Metadata{} - if err := yaml.Unmarshal(file.Data, ch); err != nil { - return errors.Wrap(err, "cannot load Chart.yaml") - } - chartName = ch.Name - } - } - if chartName == "" { - return errors.New("chart name not specified") - } - - // Find the base directory - chartdir, err := securejoin.SecureJoin(dir, chartName) - if err != nil { - return err - } - - // Copy all files verbatim. We don't parse these files because parsing can remove - // comments. - for _, file := range files { - outpath, err := securejoin.SecureJoin(chartdir, file.Name) - if err != nil { - return err - } - - // Make sure the necessary subdirs get created. - basedir := filepath.Dir(outpath) - if err := os.MkdirAll(basedir, 0755); err != nil { - return err - } - - if err := ioutil.WriteFile(outpath, file.Data, 0644); err != nil { - return err - } - } - - return nil -} - -// ExpandFile expands the src file into the dest directory. -func ExpandFile(dest, src string) error { - h, err := os.Open(src) - if err != nil { - return err - } - defer h.Close() - return Expand(dest, h) -} diff --git a/pkg/chartutil/expand_test.go b/pkg/chartutil/expand_test.go deleted file mode 100644 index f31a3d290..000000000 --- a/pkg/chartutil/expand_test.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -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 chartutil - -import ( - "os" - "path/filepath" - "testing" -) - -func TestExpand(t *testing.T) { - dest := t.TempDir() - - reader, err := os.Open("testdata/frobnitz-1.2.3.tgz") - if err != nil { - t.Fatal(err) - } - - if err := Expand(dest, reader); err != nil { - t.Fatal(err) - } - - expectedChartPath := filepath.Join(dest, "frobnitz") - fi, err := os.Stat(expectedChartPath) - if err != nil { - t.Fatal(err) - } - if !fi.IsDir() { - t.Fatalf("expected a chart directory at %s", expectedChartPath) - } - - dir, err := os.Open(expectedChartPath) - if err != nil { - t.Fatal(err) - } - - fis, err := dir.Readdir(0) - if err != nil { - t.Fatal(err) - } - - expectLen := 11 - if len(fis) != expectLen { - t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) - } - - for _, fi := range fis { - expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) - if err != nil { - t.Fatal(err) - } - // os.Stat can return different values for directories, based on the OS - // for Linux, for example, os.Stat alwaty returns the size of the directory - // (value-4096) regardless of the size of the contents of the directory - mode := expect.Mode() - if !mode.IsDir() { - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) - } - } - } -} - -func TestExpandFile(t *testing.T) { - dest := t.TempDir() - - if err := ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"); err != nil { - t.Fatal(err) - } - - expectedChartPath := filepath.Join(dest, "frobnitz") - fi, err := os.Stat(expectedChartPath) - if err != nil { - t.Fatal(err) - } - if !fi.IsDir() { - t.Fatalf("expected a chart directory at %s", expectedChartPath) - } - - dir, err := os.Open(expectedChartPath) - if err != nil { - t.Fatal(err) - } - - fis, err := dir.Readdir(0) - if err != nil { - t.Fatal(err) - } - - expectLen := 11 - if len(fis) != expectLen { - t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) - } - - for _, fi := range fis { - expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) - if err != nil { - t.Fatal(err) - } - // os.Stat can return different values for directories, based on the OS - // for Linux, for example, os.Stat alwaty returns the size of the directory - // (value-4096) regardless of the size of the contents of the directory - mode := expect.Mode() - if !mode.IsDir() { - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) - } - } - } -} diff --git a/pkg/chartutil/jsonschema.go b/pkg/chartutil/jsonschema.go deleted file mode 100644 index 753dc98c1..000000000 --- a/pkg/chartutil/jsonschema.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -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 chartutil - -import ( - "bytes" - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/xeipuuv/gojsonschema" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// ValidateAgainstSchema checks that values does not violate the structure laid out in schema -func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error { - var sb strings.Builder - if chrt.Schema != nil { - err := ValidateAgainstSingleSchema(values, chrt.Schema) - if err != nil { - sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) - sb.WriteString(err.Error()) - } - } - - // For each dependency, recursively call this function with the coalesced values - for _, subchart := range chrt.Dependencies() { - subchartValues := values[subchart.Name()].(map[string]interface{}) - if err := ValidateAgainstSchema(subchart, subchartValues); err != nil { - sb.WriteString(err.Error()) - } - } - - if sb.Len() > 0 { - return errors.New(sb.String()) - } - - return nil -} - -// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema -func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) error { - valuesData, err := yaml.Marshal(values) - if err != nil { - return err - } - valuesJSON, err := yaml.YAMLToJSON(valuesData) - if err != nil { - return err - } - if bytes.Equal(valuesJSON, []byte("null")) { - valuesJSON = []byte("{}") - } - schemaLoader := gojsonschema.NewBytesLoader(schemaJSON) - valuesLoader := gojsonschema.NewBytesLoader(valuesJSON) - - result, err := gojsonschema.Validate(schemaLoader, valuesLoader) - if err != nil { - return err - } - - if !result.Valid() { - var sb strings.Builder - for _, desc := range result.Errors() { - sb.WriteString(fmt.Sprintf("- %s\n", desc)) - } - return errors.New(sb.String()) - } - - return nil -} diff --git a/pkg/chartutil/jsonschema_test.go b/pkg/chartutil/jsonschema_test.go deleted file mode 100644 index a0acd5a7f..000000000 --- a/pkg/chartutil/jsonschema_test.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -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 chartutil - -import ( - "io/ioutil" - "testing" - - "helm.sh/helm/v3/pkg/chart" -) - -func TestValidateAgainstSingleSchema(t *testing.T) { - values, err := ReadValuesFile("./testdata/test-values.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - schema, err := ioutil.ReadFile("./testdata/test-values.schema.json") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - - if err := ValidateAgainstSingleSchema(values, schema); err != nil { - t.Errorf("Error validating Values against Schema: %s", err) - } -} - -func TestValidateAgainstSingleSchemaNegative(t *testing.T) { - values, err := ReadValuesFile("./testdata/test-values-negative.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - schema, err := ioutil.ReadFile("./testdata/test-values.schema.json") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - - var errString string - if err := ValidateAgainstSingleSchema(values, schema); err == nil { - t.Fatalf("Expected an error, but got nil") - } else { - errString = err.Error() - } - - expectedErrString := `- (root): employmentInfo is required -- age: Must be greater than or equal to 0 -` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } -} - -const subchartSchema = `{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Values", - "type": "object", - "properties": { - "age": { - "description": "Age", - "minimum": 0, - "type": "integer" - } - }, - "required": [ - "age" - ] -} -` - -func TestValidateAgainstSchema(t *testing.T) { - subchartJSON := []byte(subchartSchema) - subchart := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "subchart", - }, - Schema: subchartJSON, - } - chrt := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "chrt", - }, - } - chrt.AddDependency(subchart) - - vals := map[string]interface{}{ - "name": "John", - "subchart": map[string]interface{}{ - "age": 25, - }, - } - - if err := ValidateAgainstSchema(chrt, vals); err != nil { - t.Errorf("Error validating Values against Schema: %s", err) - } -} - -func TestValidateAgainstSchemaNegative(t *testing.T) { - subchartJSON := []byte(subchartSchema) - subchart := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "subchart", - }, - Schema: subchartJSON, - } - chrt := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "chrt", - }, - } - chrt.AddDependency(subchart) - - vals := map[string]interface{}{ - "name": "John", - "subchart": map[string]interface{}{}, - } - - var errString string - if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatalf("Expected an error, but got nil") - } else { - errString = err.Error() - } - - expectedErrString := `subchart: -- (root): age is required -` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } -} diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go deleted file mode 100644 index 2ce4eddaf..000000000 --- a/pkg/chartutil/save.go +++ /dev/null @@ -1,244 +0,0 @@ -/* -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 chartutil - -import ( - "archive/tar" - "compress/gzip" - "encoding/json" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") - -// SaveDir saves a chart as files in a directory. -// -// This takes the chart name, and creates a new subdirectory inside of the given dest -// directory, writing the chart's contents to that subdirectory. -func SaveDir(c *chart.Chart, dest string) error { - // Create the chart directory - outdir := filepath.Join(dest, c.Name()) - if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { - return errors.Errorf("file %s already exists and is not a directory", outdir) - } - if err := os.MkdirAll(outdir, 0755); err != nil { - return err - } - - // Save the chart file. - if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil { - return err - } - - // Save values.yaml - for _, f := range c.Raw { - if f.Name == ValuesfileName { - vf := filepath.Join(outdir, ValuesfileName) - if err := writeFile(vf, f.Data); err != nil { - return err - } - } - } - - // Save values.schema.json if it exists - if c.Schema != nil { - filename := filepath.Join(outdir, SchemafileName) - if err := writeFile(filename, c.Schema); err != nil { - return err - } - } - - // Save templates and files - for _, o := range [][]*chart.File{c.Templates, c.Files} { - for _, f := range o { - n := filepath.Join(outdir, f.Name) - if err := writeFile(n, f.Data); err != nil { - return err - } - } - } - - // Save dependencies - base := filepath.Join(outdir, ChartsDir) - for _, dep := range c.Dependencies() { - // Here, we write each dependency as a tar file. - if _, err := Save(dep, base); err != nil { - return errors.Wrapf(err, "saving %s", dep.ChartFullPath()) - } - } - return nil -} - -// Save creates an archived chart to the given directory. -// -// This takes an existing chart and a destination directory. -// -// If the directory is /foo, and the chart is named bar, with version 1.0.0, this -// will generate /foo/bar-1.0.0.tgz. -// -// This returns the absolute path to the chart archive file. -func Save(c *chart.Chart, outDir string) (string, error) { - if err := c.Validate(); err != nil { - return "", errors.Wrap(err, "chart validation") - } - - filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version) - filename = filepath.Join(outDir, filename) - dir := filepath.Dir(filename) - if stat, err := os.Stat(dir); err != nil { - if os.IsNotExist(err) { - if err2 := os.MkdirAll(dir, 0755); err2 != nil { - return "", err2 - } - } else { - return "", errors.Wrapf(err, "stat %s", dir) - } - } else if !stat.IsDir() { - return "", errors.Errorf("is not a directory: %s", dir) - } - - f, err := os.Create(filename) - if err != nil { - return "", err - } - - // Wrap in gzip writer - zipper := gzip.NewWriter(f) - zipper.Header.Extra = headerBytes - zipper.Header.Comment = "Helm" - - // Wrap in tar writer - twriter := tar.NewWriter(zipper) - rollback := false - defer func() { - twriter.Close() - zipper.Close() - f.Close() - if rollback { - os.Remove(filename) - } - }() - - if err := writeTarContents(twriter, c, ""); err != nil { - rollback = true - return filename, err - } - return filename, nil -} - -func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { - base := filepath.Join(prefix, c.Name()) - - // Pull out the dependencies of a v1 Chart, since there's no way - // to tell the serializer to skip a field for just this use case - savedDependencies := c.Metadata.Dependencies - if c.Metadata.APIVersion == chart.APIVersionV1 { - c.Metadata.Dependencies = nil - } - // Save Chart.yaml - cdata, err := yaml.Marshal(c.Metadata) - if c.Metadata.APIVersion == chart.APIVersionV1 { - c.Metadata.Dependencies = savedDependencies - } - if err != nil { - return err - } - if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata); err != nil { - return err - } - - // Save Chart.lock - // TODO: remove the APIVersion check when APIVersionV1 is not used anymore - if c.Metadata.APIVersion == chart.APIVersionV2 { - if c.Lock != nil { - ldata, err := yaml.Marshal(c.Lock) - if err != nil { - return err - } - if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil { - return err - } - } - } - - // Save values.yaml - for _, f := range c.Raw { - if f.Name == ValuesfileName { - if err := writeToTar(out, filepath.Join(base, ValuesfileName), f.Data); err != nil { - return err - } - } - } - - // Save values.schema.json if it exists - if c.Schema != nil { - if !json.Valid(c.Schema) { - return errors.New("Invalid JSON in " + SchemafileName) - } - if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema); err != nil { - return err - } - } - - // Save templates - for _, f := range c.Templates { - n := filepath.Join(base, f.Name) - if err := writeToTar(out, n, f.Data); err != nil { - return err - } - } - - // Save files - for _, f := range c.Files { - n := filepath.Join(base, f.Name) - if err := writeToTar(out, n, f.Data); err != nil { - return err - } - } - - // Save dependencies - for _, dep := range c.Dependencies() { - if err := writeTarContents(out, dep, filepath.Join(base, ChartsDir)); err != nil { - return err - } - } - return nil -} - -// writeToTar writes a single file to a tar archive. -func writeToTar(out *tar.Writer, name string, body []byte) error { - // TODO: Do we need to create dummy parent directory names if none exist? - h := &tar.Header{ - Name: filepath.ToSlash(name), - Mode: 0644, - Size: int64(len(body)), - ModTime: time.Now(), - } - if err := out.WriteHeader(h); err != nil { - return err - } - _, err := out.Write(body) - return err -} diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go deleted file mode 100644 index 6914cd200..000000000 --- a/pkg/chartutil/save_test.go +++ /dev/null @@ -1,237 +0,0 @@ -/* -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 chartutil - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "io" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -func TestSave(t *testing.T) { - tmp := ensure.TempDir(t) - defer os.RemoveAll(tmp) - - for _, dest := range []string{tmp, path.Join(tmp, "newdir")} { - t.Run("outDir="+dest, func(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: chart.APIVersionV1, - Name: "ahab", - Version: "1.2.3", - }, - Lock: &chart.Lock{ - Digest: "testdigest", - }, - Files: []*chart.File{ - {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, - }, - Schema: []byte("{\n \"title\": \"Values\"\n}"), - } - chartWithInvalidJSON := withSchema(*c, []byte("{")) - - where, err := Save(c, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } - if !strings.HasPrefix(where, dest) { - t.Fatalf("Expected %q to start with %q", where, dest) - } - if !strings.HasSuffix(where, ".tgz") { - t.Fatalf("Expected %q to end with .tgz", where) - } - - c2, err := loader.LoadFile(where) - if err != nil { - t.Fatal(err) - } - if c2.Name() != c.Name() { - t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - } - if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { - t.Fatal("Files data did not match") - } - if c2.Lock != nil { - t.Fatal("Expected v1 chart archive not to contain Chart.lock file") - } - - if !bytes.Equal(c.Schema, c2.Schema) { - indentation := 4 - formattedExpected := Indent(indentation, string(c.Schema)) - formattedActual := Indent(indentation, string(c2.Schema)) - t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) - } - if _, err := Save(&chartWithInvalidJSON, dest); err == nil { - t.Fatalf("Invalid JSON was not caught while saving chart") - } - - c.Metadata.APIVersion = chart.APIVersionV2 - where, err = Save(c, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } - c2, err = loader.LoadFile(where) - if err != nil { - t.Fatal(err) - } - if c2.Lock == nil { - t.Fatal("Expected v2 chart archive to contain a Chart.lock file") - } - if c2.Lock.Digest != c.Lock.Digest { - t.Fatal("Chart.lock data did not match") - } - }) - } -} - -// Creates a copy with a different schema; does not modify anything. -func withSchema(chart chart.Chart, schema []byte) chart.Chart { - chart.Schema = schema - return chart -} - -func Indent(n int, text string) string { - startOfLine := regexp.MustCompile(`(?m)^`) - indentation := strings.Repeat(" ", n) - return startOfLine.ReplaceAllLiteralString(text, indentation) -} - -func TestSavePreservesTimestamps(t *testing.T) { - // Test executes so quickly that if we don't subtract a second, the - // check will fail because `initialCreateTime` will be identical to the - // written timestamp for the files. - initialCreateTime := time.Now().Add(-1 * time.Second) - - tmp := t.TempDir() - - c := &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: chart.APIVersionV1, - Name: "ahab", - Version: "1.2.3", - }, - Values: map[string]interface{}{ - "imageName": "testimage", - "imageId": 42, - }, - Files: []*chart.File{ - {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, - }, - Schema: []byte("{\n \"title\": \"Values\"\n}"), - } - - where, err := Save(c, tmp) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } - - allHeaders, err := retrieveAllHeadersFromTar(where) - if err != nil { - t.Fatalf("Failed to parse tar: %v", err) - } - - for _, header := range allHeaders { - if header.ModTime.Before(initialCreateTime) { - t.Fatalf("File timestamp not preserved: %v", header.ModTime) - } - } -} - -// We could refactor `load.go` to use this `retrieveAllHeadersFromTar` function -// as well, so we are not duplicating components of the code which iterate -// through the tar. -func retrieveAllHeadersFromTar(path string) ([]*tar.Header, error) { - raw, err := os.Open(path) - if err != nil { - return nil, err - } - defer raw.Close() - - unzipped, err := gzip.NewReader(raw) - if err != nil { - return nil, err - } - defer unzipped.Close() - - tr := tar.NewReader(unzipped) - headers := []*tar.Header{} - for { - hd, err := tr.Next() - if err == io.EOF { - break - } - - if err != nil { - return nil, err - } - - headers = append(headers, hd) - } - - return headers, nil -} - -func TestSaveDir(t *testing.T) { - tmp := t.TempDir() - - c := &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: chart.APIVersionV1, - Name: "ahab", - Version: "1.2.3", - }, - Files: []*chart.File{ - {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, - }, - Templates: []*chart.File{ - {Name: filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml"), Data: []byte("abc: {{ .Values.abc }}")}, - }, - } - - if err := SaveDir(c, tmp); err != nil { - t.Fatalf("Failed to save: %s", err) - } - - c2, err := loader.LoadDir(tmp + "/ahab") - if err != nil { - t.Fatal(err) - } - - if c2.Name() != c.Name() { - t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - } - - if len(c2.Templates) != 1 || c2.Templates[0].Name != filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml") { - t.Fatal("Templates data did not match") - } - - if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { - t.Fatal("Files data did not match") - } -} diff --git a/pkg/chartutil/testdata/chartfiletest.yaml b/pkg/chartutil/testdata/chartfiletest.yaml deleted file mode 100644 index 134cd1109..000000000 --- a/pkg/chartutil/testdata/chartfiletest.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue diff --git a/pkg/chartutil/testdata/coleridge.yaml b/pkg/chartutil/testdata/coleridge.yaml deleted file mode 100644 index b6579628b..000000000 --- a/pkg/chartutil/testdata/coleridge.yaml +++ /dev/null @@ -1,12 +0,0 @@ -poet: "Coleridge" -title: "Rime of the Ancient Mariner" -stanza: ["at", "length", "did", "cross", "an", "Albatross"] - -mariner: - with: "crossbow" - shot: "ALBATROSS" - -water: - water: - where: "everywhere" - nor: "any drop to drink" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/.helmignore b/pkg/chartutil/testdata/dependent-chart-alias/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock b/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml deleted file mode 100644 index 751a3aa67..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts - alias: mariners2 - - name: mariner - version: "4.3.2" - repository: https://example.com/charts - alias: mariners1 diff --git a/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/LICENSE b/pkg/chartutil/testdata/dependent-chart-alias/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/README.md b/pkg/chartutil/testdata/dependent-chart-alias/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md b/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/icon.svg b/pkg/chartutil/testdata/dependent-chart-alias/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-alias/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore b/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore deleted file mode 100644 index 8a71bc82e..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore +++ /dev/null @@ -1,2 +0,0 @@ -ignore/ -.* diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml deleted file mode 100644 index 7c071c27b..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml deleted file mode 100644 index 7c071c27b..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml deleted file mode 100644 index fe7a99681..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml deleted file mode 100644 index 7fc39e28d..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b0..000000000 Binary files a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/frobnitz-1.2.3.tgz b/pkg/chartutil/testdata/frobnitz-1.2.3.tgz deleted file mode 100644 index 8731dce02..000000000 Binary files a/pkg/chartutil/testdata/frobnitz-1.2.3.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/frobnitz/.helmignore b/pkg/chartutil/testdata/frobnitz/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/frobnitz/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/frobnitz/Chart.lock b/pkg/chartutil/testdata/frobnitz/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chartutil/testdata/frobnitz/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts -digest: invalid diff --git a/pkg/chartutil/testdata/frobnitz/Chart.yaml b/pkg/chartutil/testdata/frobnitz/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chartutil/testdata/frobnitz/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -name: frobnitz -description: This is a frobnitz. -version: "1.2.3" -keywords: - - frobnitz - - sprocket - - dodad -maintainers: - - name: The Helm Team - email: helm@example.com - - name: Someone Else - email: nobody@example.com -sources: - - https://example.com/foo/bar -home: http://example.com -icon: https://example.com/64x64.png -annotations: - extrakey: extravalue - anotherkey: anothervalue -dependencies: - - name: alpine - version: "0.1.0" - repository: https://example.com/charts - - name: mariner - version: "4.3.2" - repository: https://example.com/charts diff --git a/pkg/chartutil/testdata/frobnitz/INSTALL.txt b/pkg/chartutil/testdata/frobnitz/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/frobnitz/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/frobnitz/LICENSE b/pkg/chartutil/testdata/frobnitz/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/frobnitz/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/frobnitz/README.md b/pkg/chartutil/testdata/frobnitz/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/frobnitz/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Frobnitz - -This is an example chart. - -## Usage - -This is an example. It has no usage. - -## Development - -For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/frobnitz/charts/_ignore_me b/pkg/chartutil/testdata/frobnitz/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md b/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.toml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for mast1. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name = "value" diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051..000000000 Binary files a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml deleted file mode 100644 index 92dc4b390..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -name: mariner -description: A Helm chart for Kubernetes -version: 4.3.2 -home: "" -dependencies: - - name: albatross - repository: https://example.com/mariner/charts - version: "0.1.0" diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml deleted file mode 100644 index b5188fde0..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: albatross -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml deleted file mode 100644 index 3121cd7ce..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -albatross: "true" - -global: - author: Coleridge diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl b/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl deleted file mode 100644 index 29c11843a..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl +++ /dev/null @@ -1 +0,0 @@ -# This is a placeholder. diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml deleted file mode 100644 index b0ccb0086..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Default values for . -# This is a YAML-formatted file. https://github.com/toml-lang/toml -# Declare name/value pairs to be passed into your templates. -# name: "value" - -: - test: true diff --git a/pkg/chartutil/testdata/frobnitz/docs/README.md b/pkg/chartutil/testdata/frobnitz/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/frobnitz/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/frobnitz/icon.svg b/pkg/chartutil/testdata/frobnitz/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/frobnitz/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/frobnitz/ignore/me.txt b/pkg/chartutil/testdata/frobnitz/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/frobnitz/templates/template.tpl b/pkg/chartutil/testdata/frobnitz/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/frobnitz/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/frobnitz/values.yaml b/pkg/chartutil/testdata/frobnitz/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/frobnitz/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz deleted file mode 100644 index 692965951..000000000 Binary files a/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/genfrob.sh b/pkg/chartutil/testdata/genfrob.sh deleted file mode 100755 index 35fdd59f2..000000000 --- a/pkg/chartutil/testdata/genfrob.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# Pack the albatross chart into the mariner chart. -echo "Packing albatross into mariner" -tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross - -echo "Packing mariner into frobnitz" -tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner -tar -zcvf frobnitz_backslash/charts/mariner-4.3.2.tgz mariner - -# Pack the frobnitz chart. -echo "Packing frobnitz" -tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz -tar --exclude=ignore/* -zcvf frobnitz_backslash-1.2.3.tgz frobnitz_backslash diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock deleted file mode 100644 index b2f17fb39..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: -- name: dev - repository: file://envs/dev - version: v0.1.0 -- name: prod - repository: file://envs/prod - version: v0.1.0 -digest: sha256:9403fc24f6cf9d6055820126cf7633b4bd1fed3c77e4880c674059f536346182 -generated: "2020-02-03T10:38:51.180474+01:00" diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml deleted file mode 100644 index 24b26d9e5..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v2 -name: parent-chart -version: v0.1.0 -appVersion: v0.1.0 -dependencies: - - name: dev - repository: "file://envs/dev" - version: ">= 0.0.1" - condition: dev.enabled,global.dev.enabled - tags: - - dev - import-values: - - data - - - name: prod - repository: "file://envs/prod" - version: ">= 0.0.1" - condition: prod.enabled,global.prod.enabled - tags: - - prod - import-values: - - data \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz deleted file mode 100644 index d28e1621c..000000000 Binary files a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz deleted file mode 100644 index a0c5aa84b..000000000 Binary files a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz and /dev/null differ diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml deleted file mode 100644 index 80a52f538..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v2 -name: dev -version: v0.1.0 -appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml deleted file mode 100644 index 38f03484d..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Dev values parent-chart -nameOverride: parent-chart-dev -exports: - data: - resources: - autoscaler: - minReplicas: 1 - maxReplicas: 3 - targetCPUUtilizationPercentage: 80 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml deleted file mode 100644 index bda4be458..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v2 -name: prod -version: v0.1.0 -appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml deleted file mode 100644 index 10cc756b2..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Prod values parent-chart -nameOverride: parent-chart-prod -exports: - data: - resources: - autoscaler: - minReplicas: 2 - maxReplicas: 5 - targetCPUUtilizationPercentage: 90 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml deleted file mode 100644 index 976e5a8f1..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml +++ /dev/null @@ -1,16 +0,0 @@ -################################################################################################### -# parent-chart horizontal pod autoscaler -################################################################################################### -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ .Release.Name }}-autoscaler - namespace: {{ .Release.Namespace }} -spec: - scaleTargetRef: - apiVersion: apps/v1beta1 - kind: Deployment - name: {{ .Release.Name }} - minReplicas: {{ required "A valid .Values.resources.autoscaler.minReplicas entry required!" .Values.resources.autoscaler.minReplicas }} - maxReplicas: {{ required "A valid .Values.resources.autoscaler.maxReplicas entry required!" .Values.resources.autoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ required "A valid .Values.resources.autoscaler.targetCPUUtilizationPercentage!" .Values.resources.autoscaler.targetCPUUtilizationPercentage }} \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml deleted file mode 100644 index b812f0a33..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Default values for parent-chart. -nameOverride: parent-chart -tags: - dev: false - prod: true -resources: - autoscaler: - minReplicas: 0 - maxReplicas: 0 - targetCPUUtilizationPercentage: 99 \ No newline at end of file diff --git a/pkg/chartutil/testdata/joonix/Chart.yaml b/pkg/chartutil/testdata/joonix/Chart.yaml deleted file mode 100644 index c3464c56e..000000000 --- a/pkg/chartutil/testdata/joonix/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: joonix -version: 1.2.3 diff --git a/pkg/chartutil/testdata/joonix/charts/.gitkeep b/pkg/chartutil/testdata/joonix/charts/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/subpop/Chart.yaml b/pkg/chartutil/testdata/subpop/Chart.yaml deleted file mode 100644 index 27118672a..000000000 --- a/pkg/chartutil/testdata/subpop/Chart.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: parentchart -version: 0.1.0 -dependencies: - - name: subchart1 - repository: http://localhost:10191 - version: 0.1.0 - condition: subchart1.enabled - tags: - - front-end - - subchart1 - import-values: - - child: SC1data - parent: imported-chart1 - - child: SC1data - parent: overridden-chart1 - - child: imported-chartA - parent: imported-chartA - - child: imported-chartA-B - parent: imported-chartA-B - - child: overridden-chartA-B - parent: overridden-chartA-B - - child: SCBexported1A - parent: . - - SCBexported2 - - SC1exported1 - - - name: subchart2 - repository: http://localhost:10191 - version: 0.1.0 - condition: subchart2.enabled - tags: - - back-end - - subchart2 - - - name: subchart2 - alias: subchart2alias - repository: http://localhost:10191 - version: 0.1.0 - condition: subchart2alias.enabled diff --git a/pkg/chartutil/testdata/subpop/README.md b/pkg/chartutil/testdata/subpop/README.md deleted file mode 100644 index e43fbfe9c..000000000 --- a/pkg/chartutil/testdata/subpop/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Subpop - -This chart is for testing the processing of enabled/disabled charts -via conditions and tags. - -Currently there are three levels: - -```` -parent --1 tags: front-end, subchart1 ---A tags: front-end, subchartA ---B tags: front-end, subchartB --2 tags: back-end, subchart2 ---B tags: back-end, subchartB ---C tags: back-end, subchartC -```` - -Tags and conditions are currently in requirements.yaml files. \ No newline at end of file diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml deleted file mode 100644 index 9d8c03ee1..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchart1 -version: 0.1.0 -dependencies: - - name: subcharta - repository: http://localhost:10191 - version: 0.1.0 - condition: subcharta.enabled - tags: - - front-end - - subcharta - import-values: - - child: SCAdata - parent: imported-chartA - - child: SCAdata - parent: overridden-chartA - - child: SCAdata - parent: imported-chartA-B - - - name: subchartb - repository: http://localhost:10191 - version: 0.1.0 - condition: subchartb.enabled - import-values: - - child: SCBdata - parent: imported-chartB - - child: SCBdata - parent: imported-chartA-B - - child: exports.SCBexported2 - parent: exports.SCBexported2 - - SCBexported1 - - tags: - - front-end - - subchartb diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml deleted file mode 100644 index be3edcefb..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subcharta -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Chart.Name }} - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml deleted file mode 100644 index f0381ae6a..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -# subchartA -service: - name: apache - type: ClusterIP - externalPort: 80 - internalPort: 80 -SCAdata: - SCAbool: false - SCAfloat: 3.1 - SCAint: 55 - SCAstring: "jabba" - SCAnested1: - SCAnested2: true - diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml deleted file mode 100644 index c3c6bbaf0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchartb -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Chart.Name }} - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml deleted file mode 100644 index 774fdd75c..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 - -SCBdata: - SCBbool: true - SCBfloat: 7.77 - SCBint: 33 - SCBstring: "boba" - -exports: - SCBexported1: - SCBexported1A: - SCBexported1B: 1965 - - SCBexported2: - SCBexported2A: "blaster" - -global: - kolla: - nova: - api: - all: - port: 8774 - metadata: - all: - port: 8775 - - - diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml deleted file mode 100644 index fca77fd4b..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: testCRDs -spec: - group: testCRDGroups - names: - kind: TestCRD - listKind: TestCRDList - plural: TestCRDs - shortNames: - - tc - singular: authconfig diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt deleted file mode 100644 index 4bdf443f6..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -Sample notes for {{ .Chart.Name }} \ No newline at end of file diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml deleted file mode 100644 index fee94dced..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Chart.Name }} - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - app.kubernetes.io/instance: "{{ .Release.Name }}" - kube-version/major: "{{ .Capabilities.KubeVersion.Major }}" - kube-version/minor: "{{ .Capabilities.KubeVersion.Minor }}" - kube-version/version: "v{{ .Capabilities.KubeVersion.Major }}.{{ .Capabilities.KubeVersion.Minor }}.0" -{{- if .Capabilities.APIVersions.Has "helm.k8s.io/test" }} - kube-api-version/test: v1 -{{- end }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml deleted file mode 100644 index 91b954e5f..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ .Chart.Name }}-role -rules: -- resources: ["*"] - verbs: ["get","list","watch"] diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml deleted file mode 100644 index 5d193f1a6..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ .Chart.Name }}-binding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ .Chart.Name }}-role -subjects: -- kind: ServiceAccount - name: {{ .Chart.Name }}-sa - namespace: default diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml deleted file mode 100644 index 7126c7d89..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ .Chart.Name }}-sa diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml deleted file mode 100644 index a974e316a..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -# subchart1 -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 - - -SC1data: - SC1bool: true - SC1float: 3.14 - SC1int: 100 - SC1string: "dollywood" - SC1extra1: 11 - -imported-chartA: - SC1extra2: 1.337 - -overridden-chartA: - SCAbool: true - SCAfloat: 3.14 - SCAint: 100 - SCAstring: "jabbathehut" - SC1extra3: true - -imported-chartA-B: - SC1extra5: "tiller" - -overridden-chartA-B: - SCAbool: true - SCAfloat: 3.33 - SCAint: 555 - SCAstring: "wormwood" - SCAextra1: 23 - - SCBbool: true - SCBfloat: 0.25 - SCBint: 98 - SCBstring: "murkwood" - SCBextra1: 13 - - SC1extra6: 77 - -SCBexported1A: - SC1extra7: true - -exports: - SC1exported1: - global: - SC1exported2: - all: - SC1exported3: "SC1expstr" \ No newline at end of file diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml deleted file mode 100644 index f936528a7..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchart2 -version: 0.1.0 -dependencies: - - name: subchartb - repository: http://localhost:10191 - version: 0.1.0 - condition: subchartb.enabled - tags: - - back-end - - subchartb - - name: subchartc - repository: http://localhost:10191 - version: 0.1.0 - condition: subchartc.enabled - tags: - - back-end - - subchartc diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml deleted file mode 100644 index c3c6bbaf0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchartb -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml deleted file mode 100644 index 3f168bdbf..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: subchart2-{{ .Chart.Name }} - labels: - helm.sh/hart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: subchart2-{{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml deleted file mode 100644 index 5e5b21065..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi - diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml deleted file mode 100644 index dcc45c088..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchartc -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Chart.Name }} - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml deleted file mode 100644 index 5e5b21065..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi - diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Chart.Name }} - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml deleted file mode 100644 index 5e5b21065..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi - diff --git a/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml b/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml deleted file mode 100644 index bbb0941c3..000000000 --- a/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: parentchart -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml b/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Chart.Name }} - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/pkg/chartutil/testdata/subpop/noreqs/values.yaml b/pkg/chartutil/testdata/subpop/noreqs/values.yaml deleted file mode 100644 index 4ed3b7ad3..000000000 --- a/pkg/chartutil/testdata/subpop/noreqs/values.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Default values for subchart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi - - -# switch-like -tags: - front-end: true - back-end: false diff --git a/pkg/chartutil/testdata/subpop/values.yaml b/pkg/chartutil/testdata/subpop/values.yaml deleted file mode 100644 index d611d6a89..000000000 --- a/pkg/chartutil/testdata/subpop/values.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# parent/values.yaml - -imported-chart1: - SPextra1: "helm rocks" - -overridden-chart1: - SC1bool: false - SC1float: 3.141592 - SC1int: 99 - SC1string: "pollywog" - SPextra2: 42 - - -imported-chartA: - SPextra3: 1.337 - -overridden-chartA: - SCAbool: true - SCAfloat: 41.3 - SCAint: 808 - SCAstring: "jabberwocky" - SPextra4: true - -imported-chartA-B: - SPextra5: "k8s" - -overridden-chartA-B: - SCAbool: true - SCAfloat: 41.3 - SCAint: 808 - SCAstring: "jabberwocky" - SCBbool: false - SCBfloat: 1.99 - SCBint: 77 - SCBstring: "jango" - SPextra6: 111 - -tags: - front-end: true - back-end: false - -subchart2alias: - enabled: false diff --git a/pkg/chartutil/testdata/test-values-negative.yaml b/pkg/chartutil/testdata/test-values-negative.yaml deleted file mode 100644 index 5a1250bff..000000000 --- a/pkg/chartutil/testdata/test-values-negative.yaml +++ /dev/null @@ -1,14 +0,0 @@ -firstname: John -lastname: Doe -age: -5 -likesCoffee: true -addresses: - - city: Springfield - street: Main - number: 12345 - - city: New York - street: Broadway - number: 67890 -phoneNumbers: - - "(888) 888-8888" - - "(555) 555-5555" diff --git a/pkg/chartutil/testdata/test-values.schema.json b/pkg/chartutil/testdata/test-values.schema.json deleted file mode 100644 index 4df89bbe8..000000000 --- a/pkg/chartutil/testdata/test-values.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "addresses": { - "description": "List of addresses", - "items": { - "properties": { - "city": { - "type": "string" - }, - "number": { - "type": "number" - }, - "street": { - "type": "string" - } - }, - "type": "object" - }, - "type": "array" - }, - "age": { - "description": "Age", - "minimum": 0, - "type": "integer" - }, - "employmentInfo": { - "properties": { - "salary": { - "minimum": 0, - "type": "number" - }, - "title": { - "type": "string" - } - }, - "required": [ - "salary" - ], - "type": "object" - }, - "firstname": { - "description": "First name", - "type": "string" - }, - "lastname": { - "type": "string" - }, - "likesCoffee": { - "type": "boolean" - }, - "phoneNumbers": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "firstname", - "lastname", - "addresses", - "employmentInfo" - ], - "title": "Values", - "type": "object" -} diff --git a/pkg/chartutil/testdata/test-values.yaml b/pkg/chartutil/testdata/test-values.yaml deleted file mode 100644 index 042dea664..000000000 --- a/pkg/chartutil/testdata/test-values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -firstname: John -lastname: Doe -age: 25 -likesCoffee: true -employmentInfo: - title: Software Developer - salary: 100000 -addresses: - - city: Springfield - street: Main - number: 12345 - - city: New York - street: Broadway - number: 67890 -phoneNumbers: - - "(888) 888-8888" - - "(555) 555-5555" diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/README.md b/pkg/chartutil/testdata/three-level-dependent-chart/README.md deleted file mode 100644 index a5fed642d..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Three Level Dependent Chart - -This chart is for testing the processing of multi-level dependencies. - -Consists of the following charts: - -- Library Chart -- App Chart (Uses Library Chart as dependecy, 2x: app1/app2) -- Umbrella Chart (Has all the app charts as dependencies) - -The precendence is as follows: `library < app < umbrella` - -Catches two use-cases: - -- app overwriting library (app2) -- umbrella overwriting app and library (app1) diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml deleted file mode 100644 index 7552e07cd..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v2 -name: umbrella -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 - -dependencies: -- name: app1 - version: 0.1.0 - condition: app1.enabled -- name: app2 - version: 0.1.0 - condition: app2.enabled diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml deleted file mode 100644 index 388245e31..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v2 -name: app1 -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 - -dependencies: -- name: library - version: 0.1.0 - import-values: - - defaults diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml deleted file mode 100644 index f2f8a90d9..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v2 -name: library -description: A Helm chart for Kubernetes -type: library -version: 0.1.0 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml deleted file mode 100644 index 3fd398b53..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Service -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml deleted file mode 100644 index 0c08b6cd2..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml +++ /dev/null @@ -1,5 +0,0 @@ -exports: - defaults: - service: - type: ClusterIP - port: 9090 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml deleted file mode 100644 index 8ed8ddf1f..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml +++ /dev/null @@ -1 +0,0 @@ -{{- include "library.service" . }} diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml deleted file mode 100644 index 3728aa930..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml +++ /dev/null @@ -1,3 +0,0 @@ -service: - type: ClusterIP - port: 1234 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml deleted file mode 100644 index fea2768c7..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v2 -name: app2 -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 - -dependencies: -- name: library - version: 0.1.0 - import-values: - - defaults diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml deleted file mode 100644 index f2f8a90d9..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v2 -name: library -description: A Helm chart for Kubernetes -type: library -version: 0.1.0 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml deleted file mode 100644 index 3fd398b53..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Service -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml deleted file mode 100644 index 0c08b6cd2..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml +++ /dev/null @@ -1,5 +0,0 @@ -exports: - defaults: - service: - type: ClusterIP - port: 9090 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml deleted file mode 100644 index 8ed8ddf1f..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml +++ /dev/null @@ -1 +0,0 @@ -{{- include "library.service" . }} diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml deleted file mode 100644 index 98bd6d24b..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml +++ /dev/null @@ -1,3 +0,0 @@ -service: - type: ClusterIP - port: 8080 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml deleted file mode 100644 index 94ee31855..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml +++ /dev/null @@ -1,8 +0,0 @@ -app1: - enabled: true - service: - type: ClusterIP - port: 3456 - -app2: - enabled: true diff --git a/pkg/chartutil/validate_name.go b/pkg/chartutil/validate_name.go deleted file mode 100644 index 05c090cb6..000000000 --- a/pkg/chartutil/validate_name.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -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 chartutil - -import ( - "fmt" - "regexp" - - "github.com/pkg/errors" -) - -// validName is a regular expression for resource names. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - -var ( - // errMissingName indicates that a release (name) was not provided. - errMissingName = errors.New("no name provided") - - // errInvalidName indicates that an invalid release name was provided - errInvalidName = fmt.Errorf( - "invalid release name, must match regex %s and the length must not be longer than 53", - validName.String()) - - // errInvalidKubernetesName indicates that the name does not meet the Kubernetes - // restrictions on metadata names. - errInvalidKubernetesName = fmt.Errorf( - "invalid metadata name, must match regex %s and the length must not be longer than 253", - validName.String()) -) - -const ( - // According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names) - // some resource names have a max length of 63 characters while others have a max - // length of 253 characters. As we cannot be sure the resources used in a chart, we - // therefore need to limit it to 63 chars and reserve 10 chars for additional part to name - // of the resource. The reason is that chart maintainers can use release name as part of - // the resource name (and some additional chars). - maxReleaseNameLen = 53 - // maxMetadataNameLen is the maximum length Kubernetes allows for any name. - maxMetadataNameLen = 253 -) - -// ValidateReleaseName performs checks for an entry for a Helm release name -// -// For Helm to allow a name, it must be below a certain character count (53) and also match -// a regular expression. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -func ValidateReleaseName(name string) error { - // This case is preserved for backwards compatibility - if name == "" { - return errMissingName - - } - if len(name) > maxReleaseNameLen || !validName.MatchString(name) { - return errInvalidName - } - return nil -} - -// ValidateMetadataName validates the name field of a Kubernetes metadata object. -// -// Empty strings, strings longer than 253 chars, or strings that don't match the regexp -// will fail. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// Deprecated: remove in Helm 4. Name validation now uses rules defined in -// pkg/lint/rules.validateMetadataNameFunc() -func ValidateMetadataName(name string) error { - if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { - return errInvalidKubernetesName - } - return nil -} diff --git a/pkg/chartutil/validate_name_test.go b/pkg/chartutil/validate_name_test.go deleted file mode 100644 index 5f0792f94..000000000 --- a/pkg/chartutil/validate_name_test.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -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 chartutil - -import "testing" - -// TestValidateName is a regression test for ValidateName -// -// Kubernetes has strict naming conventions for resource names. This test represents -// those conventions. -// -// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// NOTE: At the time of this writing, the docs above say that names cannot begin with -// digits. However, `kubectl`'s regular expression explicit allows this, and -// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits. -func TestValidateReleaseName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - "a1111111111111111111111111111111111111111111111111111111111z": false, - } - for input, expectPass := range names { - if err := ValidateReleaseName(input); (err == nil) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) - } - } -} - -func TestValidateMetadataName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - "a1111111111111111111111111111111111111111111111111111111111z": true, - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z": false, - } - for input, expectPass := range names { - if err := ValidateMetadataName(input); (err == nil) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) - } - } -} diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go deleted file mode 100644 index 97bf44217..000000000 --- a/pkg/chartutil/values.go +++ /dev/null @@ -1,212 +0,0 @@ -/* -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 chartutil - -import ( - "fmt" - "io" - "io/ioutil" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// GlobalKey is the name of the Values key that is used for storing global vars. -const GlobalKey = "global" - -// Values represents a collection of chart values. -type Values map[string]interface{} - -// YAML encodes the Values into a YAML string. -func (v Values) YAML() (string, error) { - b, err := yaml.Marshal(v) - return string(b), err -} - -// Table gets a table (YAML subsection) from a Values object. -// -// The table is returned as a Values. -// -// Compound table names may be specified with dots: -// -// foo.bar -// -// The above will be evaluated as "The table bar inside the table -// foo". -// -// An ErrNoTable is returned if the table does not exist. -func (v Values) Table(name string) (Values, error) { - table := v - var err error - - for _, n := range parsePath(name) { - if table, err = tableLookup(table, n); err != nil { - break - } - } - return table, err -} - -// AsMap is a utility function for converting Values to a map[string]interface{}. -// -// It protects against nil map panics. -func (v Values) AsMap() map[string]interface{} { - if len(v) == 0 { - return map[string]interface{}{} - } - return v -} - -// Encode writes serialized Values information to the given io.Writer. -func (v Values) Encode(w io.Writer) error { - out, err := yaml.Marshal(v) - if err != nil { - return err - } - _, err = w.Write(out) - return err -} - -func tableLookup(v Values, simple string) (Values, error) { - v2, ok := v[simple] - if !ok { - return v, ErrNoTable{simple} - } - if vv, ok := v2.(map[string]interface{}); ok { - return vv, nil - } - - // This catches a case where a value is of type Values, but doesn't (for some - // reason) match the map[string]interface{}. This has been observed in the - // wild, and might be a result of a nil map of type Values. - if vv, ok := v2.(Values); ok { - return vv, nil - } - - return Values{}, ErrNoTable{simple} -} - -// ReadValues will parse YAML byte data into a Values. -func ReadValues(data []byte) (vals Values, err error) { - err = yaml.Unmarshal(data, &vals) - if len(vals) == 0 { - vals = Values{} - } - return vals, err -} - -// ReadValuesFile will parse a YAML file into a map of values. -func ReadValuesFile(filename string) (Values, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return map[string]interface{}{}, err - } - return ReadValues(data) -} - -// ReleaseOptions represents the additional release options needed -// for the composition of the final values struct -type ReleaseOptions struct { - Name string - Namespace string - Revision int - IsUpgrade bool - IsInstall bool -} - -// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files -// -// This takes both ReleaseOptions and Capabilities to merge into the render values. -func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) { - if caps == nil { - caps = DefaultCapabilities - } - top := map[string]interface{}{ - "Chart": chrt.Metadata, - "Capabilities": caps, - "Release": map[string]interface{}{ - "Name": options.Name, - "Namespace": options.Namespace, - "IsUpgrade": options.IsUpgrade, - "IsInstall": options.IsInstall, - "Revision": options.Revision, - "Service": "Helm", - }, - } - - vals, err := CoalesceValues(chrt, chrtVals) - if err != nil { - return top, err - } - - if err := ValidateAgainstSchema(chrt, vals); err != nil { - errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" - return top, fmt.Errorf(errFmt, err.Error()) - } - - top["Values"] = vals - return top, nil -} - -// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) - return ok -} - -// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path. -// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods. -// Given the following YAML data the value at path "chapter.one.title" is "Loomings". -// -// chapter: -// one: -// title: "Loomings" -func (v Values) PathValue(path string) (interface{}, error) { - if path == "" { - return nil, errors.New("YAML path cannot be empty") - } - return v.pathValue(parsePath(path)) -} - -func (v Values) pathValue(path []string) (interface{}, error) { - if len(path) == 1 { - // if exists must be root key not table - if _, ok := v[path[0]]; ok && !istable(v[path[0]]) { - return v[path[0]], nil - } - return nil, ErrNoValue{path[0]} - } - - key, path := path[len(path)-1], path[:len(path)-1] - // get our table for table path - t, err := v.Table(joinPath(path...)) - if err != nil { - return nil, ErrNoValue{key} - } - // check table for key and ensure value is not a table - if k, ok := t[key]; ok && !istable(k) { - return k, nil - } - return nil, ErrNoValue{key} -} - -func parsePath(key string) []string { return strings.Split(key, ".") } - -func joinPath(path ...string) string { return strings.Join(path, ".") } diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go deleted file mode 100644 index c95fa503a..000000000 --- a/pkg/chartutil/values_test.go +++ /dev/null @@ -1,292 +0,0 @@ -/* -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 chartutil - -import ( - "bytes" - "fmt" - "testing" - "text/template" - - "helm.sh/helm/v3/pkg/chart" -) - -func TestReadValues(t *testing.T) { - doc := `# Test YAML parse -poet: "Coleridge" -title: "Rime of the Ancient Mariner" -stanza: - - "at" - - "length" - - "did" - - cross - - an - - Albatross - -mariner: - with: "crossbow" - shot: "ALBATROSS" - -water: - water: - where: "everywhere" - nor: "any drop to drink" -` - - data, err := ReadValues([]byte(doc)) - if err != nil { - t.Fatalf("Error parsing bytes: %s", err) - } - matchValues(t, data) - - tests := []string{`poet: "Coleridge"`, "# Just a comment", ""} - - for _, tt := range tests { - data, err = ReadValues([]byte(tt)) - if err != nil { - t.Fatalf("Error parsing bytes (%s): %s", tt, err) - } - if data == nil { - t.Errorf(`YAML string "%s" gave a nil map`, tt) - } - } -} - -func TestToRenderValues(t *testing.T) { - - chartValues := map[string]interface{}{ - "name": "al Rashid", - "where": map[string]interface{}{ - "city": "Basrah", - "title": "caliph", - }, - } - - overrideValues := map[string]interface{}{ - "name": "Haroun", - "where": map[string]interface{}{ - "city": "Baghdad", - "date": "809 CE", - }, - } - - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "test"}, - Templates: []*chart.File{}, - Values: chartValues, - Files: []*chart.File{ - {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, - }, - } - c.AddDependency(&chart.Chart{ - Metadata: &chart.Metadata{Name: "where"}, - }) - - o := ReleaseOptions{ - Name: "Seven Voyages", - Namespace: "default", - Revision: 1, - IsInstall: true, - } - - res, err := ToRenderValues(c, overrideValues, o, nil) - if err != nil { - t.Fatal(err) - } - - // Ensure that the top-level values are all set. - if name := res["Chart"].(*chart.Metadata).Name; name != "test" { - t.Errorf("Expected chart name 'test', got %q", name) - } - relmap := res["Release"].(map[string]interface{}) - if name := relmap["Name"]; name.(string) != "Seven Voyages" { - t.Errorf("Expected release name 'Seven Voyages', got %q", name) - } - if namespace := relmap["Namespace"]; namespace.(string) != "default" { - t.Errorf("Expected namespace 'default', got %q", namespace) - } - if revision := relmap["Revision"]; revision.(int) != 1 { - t.Errorf("Expected revision '1', got %d", revision) - } - if relmap["IsUpgrade"].(bool) { - t.Error("Expected upgrade to be false.") - } - if !relmap["IsInstall"].(bool) { - t.Errorf("Expected install to be true.") - } - if !res["Capabilities"].(*Capabilities).APIVersions.Has("v1") { - t.Error("Expected Capabilities to have v1 as an API") - } - if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" { - t.Error("Expected Capabilities to have a Kube version") - } - - vals := res["Values"].(Values) - if vals["name"] != "Haroun" { - t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) - } - where := vals["where"].(map[string]interface{}) - expects := map[string]string{ - "city": "Baghdad", - "date": "809 CE", - "title": "caliph", - } - for field, expect := range expects { - if got := where[field]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, where) - } - } -} - -func TestReadValuesFile(t *testing.T) { - data, err := ReadValuesFile("./testdata/coleridge.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - matchValues(t, data) -} - -func ExampleValues() { - doc := ` -title: "Moby Dick" -chapter: - one: - title: "Loomings" - two: - title: "The Carpet-Bag" - three: - title: "The Spouter Inn" -` - d, err := ReadValues([]byte(doc)) - if err != nil { - panic(err) - } - ch1, err := d.Table("chapter.one") - if err != nil { - panic("could not find chapter one") - } - fmt.Print(ch1["title"]) - // Output: - // Loomings -} - -func TestTable(t *testing.T) { - doc := ` -title: "Moby Dick" -chapter: - one: - title: "Loomings" - two: - title: "The Carpet-Bag" - three: - title: "The Spouter Inn" -` - d, err := ReadValues([]byte(doc)) - if err != nil { - t.Fatalf("Failed to parse the White Whale: %s", err) - } - - if _, err := d.Table("title"); err == nil { - t.Fatalf("Title is not a table.") - } - - if _, err := d.Table("chapter"); err != nil { - t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) - } - - if v, err := d.Table("chapter.one"); err != nil { - t.Errorf("Failed to get chapter.one: %s", err) - } else if v["title"] != "Loomings" { - t.Errorf("Unexpected title: %s", v["title"]) - } - - if _, err := d.Table("chapter.three"); err != nil { - t.Errorf("Chapter three is missing: %s\n%v", err, d) - } - - if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { - t.Errorf("I think you mean 'Epilogue'") - } -} - -func matchValues(t *testing.T, data map[string]interface{}) { - if data["poet"] != "Coleridge" { - t.Errorf("Unexpected poet: %s", data["poet"]) - } - - if o, err := ttpl("{{len .stanza}}", data); err != nil { - t.Errorf("len stanza: %s", err) - } else if o != "6" { - t.Errorf("Expected 6, got %s", o) - } - - if o, err := ttpl("{{.mariner.shot}}", data); err != nil { - t.Errorf(".mariner.shot: %s", err) - } else if o != "ALBATROSS" { - t.Errorf("Expected that mariner shot ALBATROSS") - } - - if o, err := ttpl("{{.water.water.where}}", data); err != nil { - t.Errorf(".water.water.where: %s", err) - } else if o != "everywhere" { - t.Errorf("Expected water water everywhere") - } -} - -func ttpl(tpl string, v map[string]interface{}) (string, error) { - var b bytes.Buffer - tt := template.Must(template.New("t").Parse(tpl)) - err := tt.Execute(&b, v) - return b.String(), err -} - -func TestPathValue(t *testing.T) { - doc := ` -title: "Moby Dick" -chapter: - one: - title: "Loomings" - two: - title: "The Carpet-Bag" - three: - title: "The Spouter Inn" -` - d, err := ReadValues([]byte(doc)) - if err != nil { - t.Fatalf("Failed to parse the White Whale: %s", err) - } - - if v, err := d.PathValue("chapter.one.title"); err != nil { - t.Errorf("Got error instead of title: %s\n%v", err, d) - } else if v != "Loomings" { - t.Errorf("No error but got wrong value for title: %s\n%v", err, d) - } - if _, err := d.PathValue("chapter.one.doesnotexist"); err == nil { - t.Errorf("Non-existent key should return error: %s\n%v", err, d) - } - if _, err := d.PathValue("chapter.doesnotexist.one"); err == nil { - t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d) - } - if _, err := d.PathValue(""); err == nil { - t.Error("Asking for the value from an empty path should yield an error") - } - if v, err := d.PathValue("title"); err == nil { - if v != "Moby Dick" { - t.Errorf("Failed to return values for root key title") - } - } -} diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go deleted file mode 100644 index ac3093629..000000000 --- a/pkg/cli/environment.go +++ /dev/null @@ -1,230 +0,0 @@ -/* -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" - "strings" - - "github.com/spf13/pflag" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/pkg/helmpath" -) - -// defaultMaxHistory sets the maximum number of releases to 0: unlimited -const defaultMaxHistory = 10 - -// defaultBurstLimit sets the default client-side throttling limit -const defaultBurstLimit = 100 - -// 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 - // Username to impersonate for the operation - KubeAsUser string - // Groups to impersonate for the operation, multiple groups parsed from a comma delimited list - KubeAsGroups []string - // Kubernetes API Server Endpoint for authentication - KubeAPIServer string - // Custom certificate authority file. - KubeCaFile string - // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. - // This makes the HTTPS connections insecure - KubeInsecureSkipTLSVerify bool - // KubeTLSServerName overrides the name to use for server certificate validation. - // If it is not provided, the hostname used to contact the server is used - KubeTLSServerName 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 - // MaxHistory is the max release history maintained. - MaxHistory int - // BurstLimit is the default client-side throttling limit. - BurstLimit int -} - -func New() *EnvSettings { - env := &EnvSettings{ - namespace: os.Getenv("HELM_NAMESPACE"), - MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), - KubeContext: os.Getenv("HELM_KUBECONTEXT"), - KubeToken: os.Getenv("HELM_KUBETOKEN"), - KubeAsUser: os.Getenv("HELM_KUBEASUSER"), - KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), - KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), - KubeCaFile: os.Getenv("HELM_KUBECAFILE"), - KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), - KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), - PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), - RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), - RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), - RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), - BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), - } - 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, - CAFile: &env.KubeCaFile, - KubeConfig: &env.KubeConfig, - Impersonate: &env.KubeAsUser, - Insecure: &env.KubeInsecureSkipTLSVerify, - TLSServerName: &env.KubeTLSServerName, - ImpersonateGroup: &env.KubeAsGroups, - WrapConfigFn: func(config *rest.Config) *rest.Config { - config.Burst = env.BurstLimit - return config - }, - } - 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.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") - fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") - fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") - fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") - fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") - fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") - 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") - fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") -} - -func envOr(name, def string) string { - if v, ok := os.LookupEnv(name); ok { - return v - } - return def -} - -func envBoolOr(name string, def bool) bool { - if name == "" { - return def - } - envVal := envOr(name, strconv.FormatBool(def)) - ret, err := strconv.ParseBool(envVal) - if err != nil { - return def - } - return ret -} - -func envIntOr(name string, def int) int { - if name == "" { - return def - } - envVal := envOr(name, strconv.Itoa(def)) - ret, err := strconv.Atoi(envVal) - if err != nil { - return def - } - return ret -} - -func envCSV(name string) (ls []string) { - trimmed := strings.Trim(os.Getenv(name), ", ") - if trimmed != "" { - ls = strings.Split(trimmed, ",") - } - return -} - -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(), - "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), - "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), - - // broken, these are populated from helm flags and not kubeconfig. - "HELM_KUBECONTEXT": s.KubeContext, - "HELM_KUBETOKEN": s.KubeToken, - "HELM_KUBEASUSER": s.KubeAsUser, - "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), - "HELM_KUBEAPISERVER": s.KubeAPIServer, - "HELM_KUBECAFILE": s.KubeCaFile, - "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), - "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, - } - 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" -} - -// SetNamespace sets the namespace in the configuration -func (s *EnvSettings) SetNamespace(namespace string) { - s.namespace = namespace -} - -// RESTClientGetter gets the kubeconfig from EnvSettings -func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { - return s.config -} diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go deleted file mode 100644 index dbf056e3a..000000000 --- a/pkg/cli/environment_test.go +++ /dev/null @@ -1,248 +0,0 @@ -/* -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 - -import ( - "os" - "reflect" - "strings" - "testing" - - "github.com/spf13/pflag" -) - -func TestSetNamespace(t *testing.T) { - settings := New() - - if settings.namespace != "" { - t.Errorf("Expected empty namespace, got %s", settings.namespace) - } - - settings.SetNamespace("testns") - if settings.namespace != "testns" { - t.Errorf("Expected namespace testns, got %s", settings.namespace) - } - -} - -func TestEnvSettings(t *testing.T) { - tests := []struct { - name string - - // input - args string - envvars map[string]string - - // expected values - ns, kcontext string - debug bool - maxhistory int - kubeAsUser string - kubeAsGroups []string - kubeCaFile string - kubeInsecure bool - kubeTLSServer string - burstLimit int - }{ - { - name: "defaults", - ns: "default", - maxhistory: defaultMaxHistory, - burstLimit: defaultBurstLimit, - }, - { - name: "with flags set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - ns: "myns", - debug: true, - maxhistory: defaultMaxHistory, - burstLimit: 100, - kubeAsUser: "poro", - kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, - kubeCaFile: "/tmp/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, - }, - { - name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"}, - ns: "yourns", - maxhistory: 5, - burstLimit: 150, - debug: true, - kubeAsUser: "pikachu", - kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, - kubeCaFile: "/tmp/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, - }, - { - name: "with flags and envvars set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"}, - ns: "myns", - debug: true, - maxhistory: 5, - burstLimit: 175, - kubeAsUser: "poro", - kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, - kubeCaFile: "/my/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer resetEnv()() - - for k, v := range tt.envvars { - os.Setenv(k, v) - } - - flags := pflag.NewFlagSet("testing", pflag.ContinueOnError) - - settings := New() - settings.AddFlags(flags) - flags.Parse(strings.Split(tt.args, " ")) - - if settings.Debug != tt.debug { - t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug) - } - if settings.Namespace() != tt.ns { - t.Errorf("expected namespace %q, got %q", tt.ns, settings.Namespace()) - } - if settings.KubeContext != tt.kcontext { - t.Errorf("expected kube-context %q, got %q", tt.kcontext, settings.KubeContext) - } - if settings.MaxHistory != tt.maxhistory { - t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory) - } - if tt.kubeAsUser != settings.KubeAsUser { - t.Errorf("expected kAsUser %q, got %q", tt.kubeAsUser, settings.KubeAsUser) - } - if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) { - t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups)) - } - if tt.kubeCaFile != settings.KubeCaFile { - t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile) - } - if tt.burstLimit != settings.BurstLimit { - t.Errorf("expected BurstLimit %d, got %d", tt.burstLimit, settings.BurstLimit) - } - if tt.kubeInsecure != settings.KubeInsecureSkipTLSVerify { - t.Errorf("expected kubeInsecure %t, got %t", tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify) - } - if tt.kubeTLSServer != settings.KubeTLSServerName { - t.Errorf("expected kubeTLSServer %q, got %q", tt.kubeTLSServer, settings.KubeTLSServerName) - } - }) - } -} - -func TestEnvOrBool(t *testing.T) { - const envName = "TEST_ENV_OR_BOOL" - tests := []struct { - name string - env string - val string - def bool - expected bool - }{ - { - name: "unset with default false", - def: false, - expected: false, - }, - { - name: "unset with default true", - def: true, - expected: true, - }, - { - name: "blank env with default false", - env: envName, - def: false, - expected: false, - }, - { - name: "blank env with default true", - env: envName, - def: true, - expected: true, - }, - { - name: "env true with default false", - env: envName, - val: "true", - def: false, - expected: true, - }, - { - name: "env false with default true", - env: envName, - val: "false", - def: true, - expected: false, - }, - { - name: "env fails parsing with default true", - env: envName, - val: "NOT_A_BOOL", - def: true, - expected: true, - }, - { - name: "env fails parsing with default false", - env: envName, - val: "NOT_A_BOOL", - def: false, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.env != "" { - t.Cleanup(func() { - os.Unsetenv(tt.env) - }) - os.Setenv(tt.env, tt.val) - } - actual := envBoolOr(tt.env, tt.def) - if actual != tt.expected { - t.Errorf("expected result %t, got %t", tt.expected, actual) - } - }) - } -} - -func resetEnv() func() { - origEnv := os.Environ() - - // ensure any local envvars do not hose us - for e := range New().EnvVars() { - os.Unsetenv(e) - } - - return func() { - for _, pair := range origEnv { - kv := strings.SplitN(pair, "=", 2) - os.Setenv(kv[0], kv[1]) - } - } -} diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go deleted file mode 100644 index a46c977ad..000000000 --- a/pkg/cli/output/output.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -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 output - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/gosuri/uitable" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" -) - -// Format is a type for capturing supported output formats -type Format string - -const ( - Table Format = "table" - JSON Format = "json" - YAML Format = "yaml" -) - -// Formats returns a list of the string representation of the supported formats -func Formats() []string { - return []string{Table.String(), JSON.String(), YAML.String()} -} - -// FormatsWithDesc returns a list of the string representation of the supported formats -// including a description -func FormatsWithDesc() map[string]string { - return map[string]string{ - Table.String(): "Output result in human-readable format", - JSON.String(): "Output result in JSON format", - YAML.String(): "Output result in YAML format", - } -} - -// ErrInvalidFormatType is returned when an unsupported format type is used -var ErrInvalidFormatType = fmt.Errorf("invalid format type") - -// String returns the string representation of the Format -func (o Format) String() string { - return string(o) -} - -// Write the output in the given format to the io.Writer. Unsupported formats -// will return an error -func (o Format) Write(out io.Writer, w Writer) error { - switch o { - case Table: - return w.WriteTable(out) - case JSON: - return w.WriteJSON(out) - case YAML: - return w.WriteYAML(out) - } - return ErrInvalidFormatType -} - -// ParseFormat takes a raw string and returns the matching Format. -// If the format does not exists, ErrInvalidFormatType is returned -func ParseFormat(s string) (out Format, err error) { - switch s { - case Table.String(): - out, err = Table, nil - case JSON.String(): - out, err = JSON, nil - case YAML.String(): - out, err = YAML, nil - default: - out, err = "", ErrInvalidFormatType - } - return -} - -// Writer is an interface that any type can implement to write supported formats -type Writer interface { - // WriteTable will write tabular output into the given io.Writer, returning - // an error if any occur - WriteTable(out io.Writer) error - // WriteJSON will write JSON formatted output into the given io.Writer, - // returning an error if any occur - WriteJSON(out io.Writer) error - // WriteYAML will write YAML formatted output into the given io.Writer, - // returning an error if any occur - WriteYAML(out io.Writer) error -} - -// EncodeJSON is a helper function to decorate any error message with a bit more -// context and avoid writing the same code over and over for printers. -func EncodeJSON(out io.Writer, obj interface{}) error { - enc := json.NewEncoder(out) - err := enc.Encode(obj) - if err != nil { - return errors.Wrap(err, "unable to write JSON output") - } - return nil -} - -// EncodeYAML is a helper function to decorate any error message with a bit more -// context and avoid writing the same code over and over for printers -func EncodeYAML(out io.Writer, obj interface{}) error { - raw, err := yaml.Marshal(obj) - if err != nil { - return errors.Wrap(err, "unable to write YAML output") - } - - _, err = out.Write(raw) - if err != nil { - return errors.Wrap(err, "unable to write YAML output") - } - return nil -} - -// EncodeTable is a helper function to decorate any error message with a bit -// more context and avoid writing the same code over and over for printers -func EncodeTable(out io.Writer, table *uitable.Table) error { - raw := table.Bytes() - raw = append(raw, []byte("\n")...) - _, err := out.Write(raw) - if err != nil { - return errors.Wrap(err, "unable to write table output") - } - return nil -} diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go deleted file mode 100644 index b895211d5..000000000 --- a/pkg/cli/values/options.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -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 values - -import ( - "io/ioutil" - "net/url" - "os" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/strvals" -) - -type Options struct { - ValueFiles []string - StringValues []string - Values []string - FileValues []string - JSONValues []string -} - -// MergeValues merges values from files specified via -f/--values and directly -// via --set, --set-string, or --set-file, marshaling them to YAML -func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range opts.ValueFiles { - currentMap := map[string]interface{}{} - - bytes, err := readFile(filePath, p) - if err != nil { - return nil, err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return nil, errors.Wrapf(err, "failed to parse %s", filePath) - } - // Merge with the previous map - base = mergeMaps(base, currentMap) - } - - // User specified a value via --set-json - for _, value := range opts.JSONValues { - if err := strvals.ParseJSON(value, base); err != nil { - return nil, errors.Errorf("failed parsing --set-json data %s", value) - } - } - - // User specified a value via --set - for _, value := range opts.Values { - if err := strvals.ParseInto(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set data") - } - } - - // User specified a value via --set-string - for _, value := range opts.StringValues { - if err := strvals.ParseIntoString(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-string data") - } - } - - // User specified a value via --set-file - for _, value := range opts.FileValues { - reader := func(rs []rune) (interface{}, error) { - bytes, err := readFile(string(rs), p) - if err != nil { - return nil, err - } - return string(bytes), err - } - if err := strvals.ParseIntoFile(value, base, reader); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-file data") - } - } - - return base, nil -} - -func mergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) - for k, v := range a { - out[k] = v - } - for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { - if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { - out[k] = mergeMaps(bv, v) - continue - } - } - } - out[k] = v - } - return out -} - -// readFile load a file from stdin, the local directory, or a remote file with a url. -func readFile(filePath string, p getter.Providers) ([]byte, error) { - if strings.TrimSpace(filePath) == "-" { - return ioutil.ReadAll(os.Stdin) - } - u, err := url.Parse(filePath) - if err != nil { - return nil, err - } - - // FIXME: maybe someone handle other protocols like ftp. - g, err := p.ByScheme(u.Scheme) - if err != nil { - return ioutil.ReadFile(filePath) - } - data, err := g.Get(filePath, getter.WithURL(filePath)) - if err != nil { - return nil, err - } - return data.Bytes(), err -} diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go deleted file mode 100644 index 54124c0fa..000000000 --- a/pkg/cli/values/options_test.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -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 values - -import ( - "reflect" - "testing" - - "helm.sh/helm/v3/pkg/getter" -) - -func TestMergeValues(t *testing.T) { - nestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "stuff", - }, - } - anotherNestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - flatMap := map[string]interface{}{ - "foo": "bar", - "baz": "stuff", - } - anotherFlatMap := map[string]interface{}{ - "testing": "fun", - } - - testMap := mergeMaps(flatMap, nestedMap) - equal := reflect.DeepEqual(testMap, nestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) - } - - testMap = mergeMaps(nestedMap, flatMap) - equal = reflect.DeepEqual(testMap, flatMap) - if !equal { - t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) - } - - testMap = mergeMaps(nestedMap, anotherNestedMap) - equal = reflect.DeepEqual(testMap, anotherNestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) - } - - testMap = mergeMaps(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ - "testing": "fun", - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - equal = reflect.DeepEqual(testMap, expectedMap) - if !equal { - t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) - } -} - -func TestReadFile(t *testing.T) { - var p getter.Providers - filePath := "%a.txt" - _, err := readFile(filePath, p) - if err == nil { - t.Errorf("Expected error when has special strings") - } -} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go deleted file mode 100644 index c532759b5..000000000 --- a/pkg/downloader/chart_downloader.go +++ /dev/null @@ -1,425 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package downloader - -import ( - "fmt" - "io" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/fileutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// VerificationStrategy describes a strategy for determining whether to verify a chart. -type VerificationStrategy int - -const ( - // VerifyNever will skip all verification of a chart. - VerifyNever VerificationStrategy = iota - // VerifyIfPossible will attempt a verification, it will not error if verification - // data is missing. But it will not stop processing if verification fails. - VerifyIfPossible - // VerifyAlways will always attempt a verification, and will fail if the - // verification fails. - VerifyAlways - // VerifyLater will fetch verification data, but not do any verification. - // This is to accommodate the case where another step of the process will - // perform verification. - VerifyLater -) - -// ErrNoOwnerRepo indicates that a given chart URL can't be found in any repos. -var ErrNoOwnerRepo = errors.New("could not find a repo containing the given URL") - -// ChartDownloader handles downloading a chart. -// -// It is capable of performing verifications on charts as well. -type ChartDownloader struct { - // Out is the location to write warning and info messages. - Out io.Writer - // Verify indicates what verification strategy to use. - Verify VerificationStrategy - // Keyring is the keyring file used for verification. - Keyring string - // Getter collection for the operation - Getters getter.Providers - // Options provide parameters to be passed along to the Getter being initialized. - Options []getter.Option - RegistryClient *registry.Client - RepositoryConfig string - RepositoryCache string -} - -// DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. -// -// If Verify is set to VerifyNever, the verification will be nil. -// If Verify is set to VerifyIfPossible, this will return a verification (or nil on failure), and print a warning on failure. -// If Verify is set to VerifyAlways, this will return a verification or an error if the verification fails. -// If Verify is set to VerifyLater, this will download the prov file (if it exists), but not verify it. -// -// For VerifyNever and VerifyIfPossible, the Verification may be empty. -// -// Returns a string path to the location where the file was downloaded and a verification -// (if provenance was verified), or an error if something bad happened. -func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) { - u, err := c.ResolveChartVersion(ref, version) - if err != nil { - return "", nil, err - } - - g, err := c.Getters.ByScheme(u.Scheme) - if err != nil { - return "", nil, err - } - - data, err := g.Get(u.String(), c.Options...) - if err != nil { - return "", nil, err - } - - name := filepath.Base(u.Path) - if u.Scheme == registry.OCIScheme { - idx := strings.LastIndexByte(name, ':') - name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:]) - } - - destfile := filepath.Join(dest, name) - if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil { - return destfile, nil, err - } - - // If provenance is requested, verify it. - ver := &provenance.Verification{} - if c.Verify > VerifyNever { - body, err := g.Get(u.String() + ".prov") - if err != nil { - if c.Verify == VerifyAlways { - return destfile, ver, errors.Errorf("failed to fetch provenance %q", u.String()+".prov") - } - fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", ref, err) - return destfile, ver, nil - } - provfile := destfile + ".prov" - if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil { - return destfile, nil, err - } - - if c.Verify != VerifyLater { - ver, err = VerifyChart(destfile, c.Keyring) - if err != nil { - // Fail always in this case, since it means the verification step - // failed. - return destfile, ver, err - } - } - } - return destfile, ver, nil -} - -func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) { - var tag string - var err error - - // Evaluate whether an explicit version has been provided. Otherwise, determine version to use - _, errSemVer := semver.NewVersion(version) - if errSemVer == nil { - tag = version - } else { - // Retrieve list of repository tags - tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme))) - if err != nil { - return nil, err - } - if len(tags) == 0 { - return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref) - } - - // Determine if version provided - // If empty, try to get the highest available tag - // If exact version, try to find it - // If semver constraint string, try to find a match - tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version) - if err != nil { - return nil, err - } - } - - u.Path = fmt.Sprintf("%s:%s", u.Path, tag) - - return u, err -} - -// ResolveChartVersion resolves a chart reference to a URL. -// -// It returns the URL and sets the ChartDownloader's Options that can fetch -// the URL using the appropriate Getter. -// -// A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname' -// reference, or a local path. -// -// A version is a SemVer string (1.2.3-beta.1+f334a6789). -// -// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned) -// - For a chart reference -// * If version is non-empty, this will return the URL for that version -// * If version is empty, this will return the URL for the latest version -// * If no version can be found, an error is returned -func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) { - u, err := url.Parse(ref) - if err != nil { - return nil, errors.Errorf("invalid chart URL format: %s", ref) - } - - if registry.IsOCI(u.String()) { - return c.getOciURI(ref, version, u) - } - - rf, err := loadRepoConfig(c.RepositoryConfig) - if err != nil { - return u, err - } - - if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 { - // In this case, we have to find the parent repo that contains this chart - // URL. And this is an unfortunate problem, as it requires actually going - // through each repo cache file and finding a matching URL. But basically - // we want to find the repo in case we have special SSL cert config - // for that repo. - - rc, err := c.scanReposForURL(ref, rf) - if err != nil { - // If there is no special config, return the default HTTP client and - // swallow the error. - if err == ErrNoOwnerRepo { - // Make sure to add the ref URL as the URL for the getter - c.Options = append(c.Options, getter.WithURL(ref)) - return u, nil - } - return u, err - } - - // If we get here, we don't need to go through the next phase of looking - // up the URL. We have it already. So we just set the parameters and return. - c.Options = append( - c.Options, - getter.WithURL(rc.URL), - ) - if rc.CertFile != "" || rc.KeyFile != "" || rc.CAFile != "" { - c.Options = append(c.Options, getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile)) - } - if rc.Username != "" && rc.Password != "" { - c.Options = append( - c.Options, - getter.WithBasicAuth(rc.Username, rc.Password), - getter.WithPassCredentialsAll(rc.PassCredentialsAll), - ) - } - return u, nil - } - - // See if it's of the form: repo/path_to_chart - p := strings.SplitN(u.Path, "/", 2) - if len(p) < 2 { - return u, errors.Errorf("non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u) - } - - repoName := p[0] - chartName := p[1] - rc, err := pickChartRepositoryConfigByName(repoName, rf.Repositories) - - if err != nil { - return u, err - } - - // Now that we have the chart repository information we can use that URL - // to set the URL for the getter. - c.Options = append(c.Options, getter.WithURL(rc.URL)) - - r, err := repo.NewChartRepository(rc, c.Getters) - if err != nil { - return u, err - } - - if r != nil && r.Config != nil { - if r.Config.CertFile != "" || r.Config.KeyFile != "" || r.Config.CAFile != "" { - c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) - } - if r.Config.Username != "" && r.Config.Password != "" { - c.Options = append(c.Options, - getter.WithBasicAuth(r.Config.Username, r.Config.Password), - getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), - ) - } - } - - // Next, we need to load the index, and actually look up the chart. - idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) - i, err := repo.LoadIndexFile(idxFile) - if err != nil { - return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") - } - - cv, err := i.Get(chartName, version) - if err != nil { - return u, errors.Wrapf(err, "chart %q matching %s not found in %s index. (try 'helm repo update')", chartName, version, r.Config.Name) - } - - if len(cv.URLs) == 0 { - return u, errors.Errorf("chart %q has no downloadable URLs", ref) - } - - // TODO: Seems that picking first URL is not fully correct - u, err = url.Parse(cv.URLs[0]) - if err != nil { - return u, errors.Errorf("invalid chart URL format: %s", ref) - } - - // If the URL is relative (no scheme), prepend the chart repo's base URL - if !u.IsAbs() { - repoURL, err := url.Parse(rc.URL) - if err != nil { - return repoURL, err - } - q := repoURL.Query() - // We need a trailing slash for ResolveReference to work, but make sure there isn't already one - repoURL.RawPath = strings.TrimSuffix(repoURL.RawPath, "/") + "/" - repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/" - u = repoURL.ResolveReference(u) - u.RawQuery = q.Encode() - // TODO add user-agent - if _, err := getter.NewHTTPGetter(getter.WithURL(rc.URL)); err != nil { - return repoURL, err - } - return u, err - } - - // TODO add user-agent - return u, nil -} - -// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart. -// -// It assumes that a chart archive file is accompanied by a provenance file whose -// name is the archive file name plus the ".prov" extension. -func VerifyChart(path, keyring string) (*provenance.Verification, error) { - // For now, error out if it's not a tar file. - switch fi, err := os.Stat(path); { - case err != nil: - return nil, err - case fi.IsDir(): - return nil, errors.New("unpacked charts cannot be verified") - case !isTar(path): - return nil, errors.New("chart must be a tgz file") - } - - provfile := path + ".prov" - if _, err := os.Stat(provfile); err != nil { - return nil, errors.Wrapf(err, "could not load provenance file %s", provfile) - } - - sig, err := provenance.NewFromKeyring(keyring, "") - if err != nil { - return nil, errors.Wrap(err, "failed to load keyring") - } - return sig.Verify(path, provfile) -} - -// isTar tests whether the given file is a tar file. -// -// Currently, this simply checks extension, since a subsequent function will -// untar the file and validate its binary format. -func isTar(filename string) bool { - return strings.EqualFold(filepath.Ext(filename), ".tgz") -} - -func pickChartRepositoryConfigByName(name string, cfgs []*repo.Entry) (*repo.Entry, error) { - for _, rc := range cfgs { - if rc.Name == name { - if rc.URL == "" { - return nil, errors.Errorf("no URL found for repository %s", name) - } - return rc, nil - } - } - return nil, errors.Errorf("repo %s not found", name) -} - -// scanReposForURL scans all repos to find which repo contains the given URL. -// -// This will attempt to find the given URL in all of the known repositories files. -// -// If the URL is found, this will return the repo entry that contained that URL. -// -// If all of the repos are checked, but the URL is not found, an ErrNoOwnerRepo -// error is returned. -// -// Other errors may be returned when repositories cannot be loaded or searched. -// -// Technically, the fact that a URL is not found in a repo is not a failure indication. -// Charts are not required to be included in an index before they are valid. So -// be mindful of this case. -// -// The same URL can technically exist in two or more repositories. This algorithm -// will return the first one it finds. Order is determined by the order of repositories -// in the repositories.yaml file. -func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry, error) { - // FIXME: This is far from optimal. Larger installations and index files will - // incur a performance hit for this type of scanning. - for _, rc := range rf.Repositories { - r, err := repo.NewChartRepository(rc, c.Getters) - if err != nil { - return nil, err - } - - idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) - i, err := repo.LoadIndexFile(idxFile) - if err != nil { - return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") - } - - for _, entry := range i.Entries { - for _, ver := range entry { - for _, dl := range ver.URLs { - if urlutil.Equal(u, dl) { - return rc, nil - } - } - } - } - } - // This means that there is no repo file for the given URL. - return nil, ErrNoOwnerRepo -} - -func loadRepoConfig(file string) (*repo.File, error) { - r, err := repo.LoadFile(file) - if err != nil && !os.IsNotExist(errors.Cause(err)) { - return nil, err - } - return r, nil -} diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go deleted file mode 100644 index 8ff780daf..000000000 --- a/pkg/downloader/chart_downloader_test.go +++ /dev/null @@ -1,347 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package downloader - -import ( - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" -) - -const ( - repoConfig = "testdata/repositories.yaml" - repoCache = "testdata/repository" -) - -func TestResolveChartRef(t *testing.T) { - tests := []struct { - name, ref, expect, version string - fail bool - }{ - {name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"}, - {name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"}, - {name: "full URL, with authentication", ref: "http://username:password@example.com/foo-1.2.3.tgz", expect: "http://username:password@example.com/foo-1.2.3.tgz"}, - {name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"}, - {name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"}, - {name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"}, - {name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"}, - {name: "reference, testing-relative repo", ref: "testing-relative/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, - {name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, - {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, - {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, - {name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"}, - {name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true}, - {name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true}, - {name: "invalid", ref: "invalid-1.2.3", fail: true}, - {name: "not found", ref: "nosuchthing/invalid-1.2.3", fail: true}, - } - - c := ChartDownloader{ - Out: os.Stderr, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - - for _, tt := range tests { - u, err := c.ResolveChartVersion(tt.ref, tt.version) - if err != nil { - if tt.fail { - continue - } - t.Errorf("%s: failed with error %q", tt.name, err) - continue - } - if got := u.String(); got != tt.expect { - t.Errorf("%s: expected %s, got %s", tt.name, tt.expect, got) - } - } -} - -func TestResolveChartOpts(t *testing.T) { - tests := []struct { - name, ref, version string - expect []getter.Option - }{ - { - name: "repo with CA-file", - ref: "testing-ca-file/foo", - expect: []getter.Option{ - getter.WithURL("https://example.com/foo-1.2.3.tgz"), - getter.WithTLSClientConfig("cert", "key", "ca"), - }, - }, - } - - c := ChartDownloader{ - Out: os.Stderr, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - - // snapshot options - snapshotOpts := c.Options - - for _, tt := range tests { - // reset chart downloader options for each test case - c.Options = snapshotOpts - - expect, err := getter.NewHTTPGetter(tt.expect...) - if err != nil { - t.Errorf("%s: failed to setup http client: %s", tt.name, err) - continue - } - - u, err := c.ResolveChartVersion(tt.ref, tt.version) - if err != nil { - t.Errorf("%s: failed with error %s", tt.name, err) - continue - } - - got, err := getter.NewHTTPGetter( - append( - c.Options, - getter.WithURL(u.String()), - )..., - ) - if err != nil { - t.Errorf("%s: failed to create http client: %s", tt.name, err) - continue - } - - if *(got.(*getter.HTTPGetter)) != *(expect.(*getter.HTTPGetter)) { - t.Errorf("%s: expected %s, got %s", tt.name, expect, got) - } - } -} - -func TestVerifyChart(t *testing.T) { - v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/helm-test-key.pub") - if err != nil { - t.Fatal(err) - } - // The verification is tested at length in the provenance package. Here, - // we just want a quick sanity check that the v is not empty. - if len(v.FileHash) == 0 { - t.Error("Digest missing") - } -} - -func TestIsTar(t *testing.T) { - tests := map[string]bool{ - "foo.tgz": true, - "foo/bar/baz.tgz": true, - "foo-1.2.3.4.5.tgz": true, - "foo.tar.gz": false, // for our purposes - "foo.tgz.1": false, - "footgz": false, - } - - for src, expect := range tests { - if isTar(src) != expect { - t.Errorf("%q should be %t", src, expect) - } - } -} - -func TestDownloadTo(t *testing.T) { - srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/*.tgz*") - defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyAlways, - Keyring: "testdata/helm-test-key.pub", - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - Options: []getter.Option{ - getter.WithBasicAuth("username", "password"), - getter.WithPassCredentialsAll(false), - }, - } - cname := "/signtest-0.1.0.tgz" - dest := srv.Root() - where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) - if err != nil { - t.Fatal(err) - } - - if expect := filepath.Join(dest, cname); where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if v.FileHash == "" { - t.Error("File hash was empty, but verification is required.") - } - - if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { - t.Error(err) - } -} - -func TestDownloadTo_TLS(t *testing.T) { - // Set up mock server w/ tls enabled - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - srv.Stop() - if err != nil { - t.Fatal(err) - } - srv.StartTLS() - defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - - repoConfig := filepath.Join(srv.Root(), "repositories.yaml") - repoCache := srv.Root() - - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyAlways, - Keyring: "testdata/helm-test-key.pub", - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - Options: []getter.Option{}, - } - cname := "test/signtest" - dest := srv.Root() - where, v, err := c.DownloadTo(cname, "", dest) - if err != nil { - t.Fatal(err) - } - - target := filepath.Join(dest, "signtest-0.1.0.tgz") - if expect := target; where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if v.FileHash == "" { - t.Error("File hash was empty, but verification is required.") - } - - if _, err := os.Stat(target); err != nil { - t.Error(err) - } -} - -func TestDownloadTo_VerifyLater(t *testing.T) { - defer ensure.HelmHome(t)() - - dest := ensure.TempDir(t) - defer os.RemoveAll(dest) - - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyLater, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - cname := "/signtest-0.1.0.tgz" - where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) - if err != nil { - t.Fatal(err) - } - - if expect := filepath.Join(dest, cname); where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { - t.Fatal(err) - } - if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil { - t.Fatal(err) - } -} - -func TestScanReposForURL(t *testing.T) { - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyLater, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - - u := "http://example.com/alpine-0.2.0.tgz" - rf, err := repo.LoadFile(repoConfig) - if err != nil { - t.Fatal(err) - } - - entry, err := c.scanReposForURL(u, rf) - if err != nil { - t.Fatal(err) - } - - if entry.Name != "testing" { - t.Errorf("Unexpected repo %q for URL %q", entry.Name, u) - } - - // A lookup failure should produce an ErrNoOwnerRepo - u = "https://no.such.repo/foo/bar-1.23.4.tgz" - if _, err = c.scanReposForURL(u, rf); err != ErrNoOwnerRepo { - t.Fatalf("expected ErrNoOwnerRepo, got %v", err) - } -} diff --git a/pkg/downloader/doc.go b/pkg/downloader/doc.go deleted file mode 100644 index 9588a7dfe..000000000 --- a/pkg/downloader/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/*Package downloader provides a library for downloading charts. - -This package contains various tools for downloading charts from repository -servers, and then storing them in Helm-specific directory structures. This -library contains many functions that depend on a specific -filesystem layout. -*/ -package downloader diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go deleted file mode 100644 index 18b28dde1..000000000 --- a/pkg/downloader/manager.go +++ /dev/null @@ -1,898 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package downloader - -import ( - "crypto" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "log" - "net/url" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "sync" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/resolver" - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// ErrRepoNotFound indicates that chart repositories can't be found in local repo cache. -// The value of Repos is missing repos. -type ErrRepoNotFound struct { - Repos []string -} - -// Error implements the error interface. -func (e ErrRepoNotFound) Error() string { - return fmt.Sprintf("no repository definition for %s", strings.Join(e.Repos, ", ")) -} - -// Manager handles the lifecycle of fetching, resolving, and storing dependencies. -type Manager struct { - // Out is used to print warnings and notifications. - Out io.Writer - // ChartPath is the path to the unpacked base chart upon which this operates. - ChartPath string - // Verification indicates whether the chart should be verified. - Verify VerificationStrategy - // Debug is the global "--debug" flag - Debug bool - // Keyring is the key ring file. - Keyring string - // SkipUpdate indicates that the repository should not be updated first. - SkipUpdate bool - // Getter collection for the operation - Getters []getter.Provider - RegistryClient *registry.Client - RepositoryConfig string - RepositoryCache string -} - -// Build rebuilds a local charts directory from a lockfile. -// -// If the lockfile is not present, this will run a Manager.Update() -// -// If SkipUpdate is set, this will not update the repository. -func (m *Manager) Build() error { - c, err := m.loadChartDir() - if err != nil { - return err - } - - // If a lock file is found, run a build from that. Otherwise, just do - // an update. - lock := c.Lock - if lock == nil { - return m.Update() - } - - // Check that all of the repos we're dependent on actually exist. - req := c.Metadata.Dependencies - - // If using apiVersion v1, calculate the hash before resolve repo names - // because resolveRepoNames will change req if req uses repo alias - // and Helm 2 calculate the digest from the original req - // Fix for: https://github.com/helm/helm/issues/7619 - var v2Sum string - if c.Metadata.APIVersion == chart.APIVersionV1 { - v2Sum, err = resolver.HashV2Req(req) - if err != nil { - return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") - } - } - - if _, err := m.resolveRepoNames(req); err != nil { - return err - } - - if sum, err := resolver.HashReq(req, lock.Dependencies); err != nil || sum != lock.Digest { - // If lock digest differs and chart is apiVersion v1, it maybe because the lock was built - // with Helm 2 and therefore should be checked with Helm v2 hash - // Fix for: https://github.com/helm/helm/issues/7233 - if c.Metadata.APIVersion == chart.APIVersionV1 { - log.Println("warning: a valid Helm v3 hash was not found. Checking against Helm v2 hash...") - if v2Sum != lock.Digest { - return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") - } - } else { - return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies") - } - } - - // Check that all of the repos we're dependent on actually exist. - if err := m.hasAllRepos(lock.Dependencies); err != nil { - return err - } - - if !m.SkipUpdate { - // For each repo in the file, update the cached copy of that repo - if err := m.UpdateRepositories(); err != nil { - return err - } - } - - // Now we need to fetch every package here into charts/ - return m.downloadAll(lock.Dependencies) -} - -// Update updates a local charts directory. -// -// It first reads the Chart.yaml file, and then attempts to -// negotiate versions based on that. It will download the versions -// from remote chart repositories unless SkipUpdate is true. -func (m *Manager) Update() error { - c, err := m.loadChartDir() - if err != nil { - return err - } - - // If no dependencies are found, we consider this a successful - // completion. - req := c.Metadata.Dependencies - if req == nil { - return nil - } - - // Get the names of the repositories the dependencies need that Helm is - // configured to know about. - repoNames, err := m.resolveRepoNames(req) - if err != nil { - return err - } - - // For the repositories Helm is not configured to know about, ensure Helm - // has some information about them and, when possible, the index files - // locally. - // TODO(mattfarina): Repositories should be explicitly added by end users - // rather than automattic. In Helm v4 require users to add repositories. They - // should have to add them in order to make sure they are aware of the - // repositories and opt-in to any locations, for security. - repoNames, err = m.ensureMissingRepos(repoNames, req) - if err != nil { - return err - } - - // For each of the repositories Helm is configured to know about, update - // the index information locally. - if !m.SkipUpdate { - if err := m.UpdateRepositories(); err != nil { - return err - } - } - - // Now we need to find out which version of a chart best satisfies the - // dependencies in the Chart.yaml - lock, err := m.resolve(req, repoNames) - if err != nil { - return err - } - - // Now we need to fetch every package here into charts/ - if err := m.downloadAll(lock.Dependencies); err != nil { - return err - } - - // downloadAll might overwrite dependency version, recalculate lock digest - newDigest, err := resolver.HashReq(req, lock.Dependencies) - if err != nil { - return err - } - lock.Digest = newDigest - - // If the lock file hasn't changed, don't write a new one. - oldLock := c.Lock - if oldLock != nil && oldLock.Digest == lock.Digest { - return nil - } - - // Finally, we need to write the lockfile. - return writeLock(m.ChartPath, lock, c.Metadata.APIVersion == chart.APIVersionV1) -} - -func (m *Manager) loadChartDir() (*chart.Chart, error) { - if fi, err := os.Stat(m.ChartPath); err != nil { - return nil, errors.Wrapf(err, "could not find %s", m.ChartPath) - } else if !fi.IsDir() { - return nil, errors.New("only unpacked charts can be updated") - } - return loader.LoadDir(m.ChartPath) -} - -// resolve takes a list of dependencies and translates them into an exact version to download. -// -// This returns a lock file, which has all of the dependencies normalized to a specific version. -func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { - res := resolver.New(m.ChartPath, m.RepositoryCache, m.RegistryClient) - return res.Resolve(req, repoNames) -} - -// downloadAll takes a list of dependencies and downloads them into charts/ -// -// It will delete versions of the chart that exist on disk and might cause -// a conflict. -func (m *Manager) downloadAll(deps []*chart.Dependency) error { - repos, err := m.loadChartRepositories() - if err != nil { - return err - } - - destPath := filepath.Join(m.ChartPath, "charts") - tmpPath := filepath.Join(m.ChartPath, "tmpcharts") - - // Check if 'charts' directory is not actally a directory. If it does not exist, create it. - if fi, err := os.Stat(destPath); err == nil { - if !fi.IsDir() { - return errors.Errorf("%q is not a directory", destPath) - } - } else if os.IsNotExist(err) { - if err := os.MkdirAll(destPath, 0755); err != nil { - return err - } - } else { - return fmt.Errorf("unable to retrieve file info for '%s': %v", destPath, err) - } - - // Prepare tmpPath - if err := os.MkdirAll(tmpPath, 0755); err != nil { - return err - } - defer os.RemoveAll(tmpPath) - - fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps)) - var saveError error - churls := make(map[string]struct{}) - for _, dep := range deps { - // No repository means the chart is in charts directory - if dep.Repository == "" { - fmt.Fprintf(m.Out, "Dependency %s did not declare a repository. Assuming it exists in the charts directory\n", dep.Name) - // NOTE: we are only validating the local dependency conforms to the constraints. No copying to tmpPath is necessary. - chartPath := filepath.Join(destPath, dep.Name) - ch, err := loader.LoadDir(chartPath) - if err != nil { - return fmt.Errorf("unable to load chart '%s': %v", chartPath, err) - } - - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err) - } - - v, err := semver.NewVersion(ch.Metadata.Version) - if err != nil { - return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err) - } - - if !constraint.Check(v) { - saveError = fmt.Errorf("dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version) - break - } - continue - } - if strings.HasPrefix(dep.Repository, "file://") { - if m.Debug { - fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository) - } - ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath) - if err != nil { - saveError = err - break - } - dep.Version = ver - continue - } - - // Any failure to resolve/download a chart should fail: - // https://github.com/helm/helm/issues/1439 - churl, username, password, insecureskiptlsverify, passcredentialsall, caFile, certFile, keyFile, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) - if err != nil { - saveError = errors.Wrapf(err, "could not find %s", churl) - break - } - - if _, ok := churls[churl]; ok { - fmt.Fprintf(m.Out, "Already downloaded %s from repo %s\n", dep.Name, dep.Repository) - continue - } - - fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository) - - dl := ChartDownloader{ - Out: m.Out, - Verify: m.Verify, - Keyring: m.Keyring, - RepositoryConfig: m.RepositoryConfig, - RepositoryCache: m.RepositoryCache, - RegistryClient: m.RegistryClient, - Getters: m.Getters, - Options: []getter.Option{ - getter.WithBasicAuth(username, password), - getter.WithPassCredentialsAll(passcredentialsall), - getter.WithInsecureSkipVerifyTLS(insecureskiptlsverify), - getter.WithTLSClientConfig(certFile, keyFile, caFile), - }, - } - - version := "" - if registry.IsOCI(churl) { - churl, version, err = parseOCIRef(churl) - if err != nil { - return errors.Wrapf(err, "could not parse OCI reference") - } - dl.Options = append(dl.Options, - getter.WithRegistryClient(m.RegistryClient), - getter.WithTagName(version)) - } - - if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil { - saveError = errors.Wrapf(err, "could not download %s", churl) - break - } - - churls[churl] = struct{}{} - } - - // TODO: this should probably be refactored to be a []error, so we can capture and provide more information rather than "last error wins". - if saveError == nil { - // now we can move all downloaded charts to destPath and delete outdated dependencies - if err := m.safeMoveDeps(deps, tmpPath, destPath); err != nil { - return err - } - } else { - fmt.Fprintln(m.Out, "Save error occurred: ", saveError) - return saveError - } - return nil -} - -func parseOCIRef(chartRef string) (string, string, error) { - refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:[0-9]{1,5})?[^:]+):(.*)$`) - caps := refTagRegexp.FindStringSubmatch(chartRef) - if len(caps) != 4 { - return "", "", errors.Errorf("improperly formatted oci chart reference: %s", chartRef) - } - chartRef = caps[1] - tag := caps[3] - - return chartRef, tag, nil -} - -// safeMoveDep moves all dependencies in the source and moves them into dest. -// -// It does this by first matching the file name to an expected pattern, then loading -// the file to verify that it is a chart. -// -// Any charts in dest that do not exist in source are removed (barring local dependencies) -// -// Because it requires tar file introspection, it is more intensive than a basic move. -// -// This will only return errors that should stop processing entirely. Other errors -// will emit log messages or be ignored. -func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) error { - existsInSourceDirectory := map[string]bool{} - isLocalDependency := map[string]bool{} - sourceFiles, err := os.ReadDir(source) - if err != nil { - return err - } - // attempt to read destFiles; fail fast if we can't - destFiles, err := os.ReadDir(dest) - if err != nil { - return err - } - - for _, dep := range deps { - if dep.Repository == "" { - isLocalDependency[dep.Name] = true - } - } - - for _, file := range sourceFiles { - if file.IsDir() { - continue - } - filename := file.Name() - sourcefile := filepath.Join(source, filename) - destfile := filepath.Join(dest, filename) - existsInSourceDirectory[filename] = true - if _, err := loader.LoadFile(sourcefile); err != nil { - fmt.Fprintf(m.Out, "Could not verify %s for moving: %s (Skipping)", sourcefile, err) - continue - } - // NOTE: no need to delete the dest; os.Rename replaces it. - if err := fs.RenameWithFallback(sourcefile, destfile); err != nil { - fmt.Fprintf(m.Out, "Unable to move %s to charts dir %s (Skipping)", sourcefile, err) - continue - } - } - - fmt.Fprintln(m.Out, "Deleting outdated charts") - // find all files that exist in dest that do not exist in source; delete them (outdated dependencies) - for _, file := range destFiles { - if !file.IsDir() && !existsInSourceDirectory[file.Name()] { - fname := filepath.Join(dest, file.Name()) - ch, err := loader.LoadFile(fname) - if err != nil { - fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)\n", fname, err) - continue - } - // local dependency - skip - if isLocalDependency[ch.Name()] { - continue - } - if err := os.Remove(fname); err != nil { - fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err) - continue - } - } - } - - return nil -} - -// hasAllRepos ensures that all of the referenced deps are in the local repo cache. -func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return err - } - repos := rf.Repositories - - // Verify that all repositories referenced in the deps are actually known - // by Helm. - missing := []string{} -Loop: - for _, dd := range deps { - // If repo is from local path or OCI, continue - if strings.HasPrefix(dd.Repository, "file://") || registry.IsOCI(dd.Repository) { - continue - } - - if dd.Repository == "" { - continue - } - for _, repo := range repos { - if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { - continue Loop - } - } - missing = append(missing, dd.Repository) - } - if len(missing) > 0 { - return ErrRepoNotFound{missing} - } - return nil -} - -// ensureMissingRepos attempts to ensure the repository information for repos -// not managed by Helm is present. This takes in the repoNames Helm is configured -// to work with along with the chart dependencies. It will find the deps not -// in a known repo and attempt to ensure the data is present for steps like -// version resolution. -func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { - - var ru []*repo.Entry - - for _, dd := range deps { - - // If the chart is in the local charts directory no repository needs - // to be specified. - if dd.Repository == "" { - continue - } - - // When the repoName for a dependency is known we can skip ensuring - if _, ok := repoNames[dd.Name]; ok { - continue - } - - // The generated repository name, which will result in an index being - // locally cached, has a name pattern of "helm-manager-" followed by a - // sha256 of the repo name. This assumes end users will never create - // repositories with these names pointing to other repositories. Using - // this method of naming allows the existing repository pulling and - // resolution code to do most of the work. - rn, err := key(dd.Repository) - if err != nil { - return repoNames, err - } - rn = managerKeyPrefix + rn - - repoNames[dd.Name] = rn - - // Assuming the repository is generally available. For Helm managed - // access controls the repository needs to be added through the user - // managed system. This path will work for public charts, like those - // supplied by Bitnami, but not for protected charts, like corp ones - // behind a username and pass. - ri := &repo.Entry{ - Name: rn, - URL: dd.Repository, - } - ru = append(ru, ri) - } - - // Calls to UpdateRepositories (a public function) will only update - // repositories configured by the user. Here we update repos found in - // the dependencies that are not known to the user if update skipping - // is not configured. - if !m.SkipUpdate && len(ru) > 0 { - fmt.Fprintln(m.Out, "Getting updates for unmanaged Helm repositories...") - if err := m.parallelRepoUpdate(ru); err != nil { - return repoNames, err - } - } - - return repoNames, nil -} - -// resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file -// and replaces aliased repository URLs into resolved URLs in dependencies. -func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - if os.IsNotExist(err) { - return make(map[string]string), nil - } - return nil, err - } - repos := rf.Repositories - - reposMap := make(map[string]string) - - // Verify that all repositories referenced in the deps are actually known - // by Helm. - missing := []string{} - for _, dd := range deps { - // Don't map the repository, we don't need to download chart from charts directory - if dd.Repository == "" { - continue - } - // if dep chart is from local path, verify the path is valid - if strings.HasPrefix(dd.Repository, "file://") { - if _, err := resolver.GetLocalPath(dd.Repository, m.ChartPath); err != nil { - return nil, err - } - - if m.Debug { - fmt.Fprintf(m.Out, "Repository from local path: %s\n", dd.Repository) - } - reposMap[dd.Name] = dd.Repository - continue - } - - if registry.IsOCI(dd.Repository) { - reposMap[dd.Name] = dd.Repository - continue - } - - found := false - - for _, repo := range repos { - if (strings.HasPrefix(dd.Repository, "@") && strings.TrimPrefix(dd.Repository, "@") == repo.Name) || - (strings.HasPrefix(dd.Repository, "alias:") && strings.TrimPrefix(dd.Repository, "alias:") == repo.Name) { - found = true - dd.Repository = repo.URL - reposMap[dd.Name] = repo.Name - break - } else if urlutil.Equal(repo.URL, dd.Repository) { - found = true - reposMap[dd.Name] = repo.Name - break - } - } - if !found { - repository := dd.Repository - // Add if URL - _, err := url.ParseRequestURI(repository) - if err == nil { - reposMap[repository] = repository - continue - } - missing = append(missing, repository) - } - } - if len(missing) > 0 { - errorMessage := fmt.Sprintf("no repository definition for %s. Please add them via 'helm repo add'", strings.Join(missing, ", ")) - // It is common for people to try to enter "stable" as a repository instead of the actual URL. - // For this case, let's give them a suggestion. - containsNonURL := false - for _, repo := range missing { - if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") { - containsNonURL = true - } - } - if containsNonURL { - errorMessage += ` -Note that repositories must be URLs or aliases. For example, to refer to the "example" -repository, use "https://charts.example.com/" or "@example" instead of -"example". Don't forget to add the repo, too ('helm repo add').` - } - return nil, errors.New(errorMessage) - } - return reposMap, nil -} - -// UpdateRepositories updates all of the local repos to the latest. -func (m *Manager) UpdateRepositories() error { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return err - } - repos := rf.Repositories - if len(repos) > 0 { - fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...") - // This prints warnings straight to out. - if err := m.parallelRepoUpdate(repos); err != nil { - return err - } - fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") - } - return nil -} - -func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { - - var wg sync.WaitGroup - for _, c := range repos { - r, err := repo.NewChartRepository(c, m.Getters) - if err != nil { - return err - } - wg.Add(1) - go func(r *repo.ChartRepository) { - if _, err := r.DownloadIndexFile(); err != nil { - // For those dependencies that are not known to helm and using a - // generated key name we display the repo url. - if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { - fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository:\n\t%s\n", r.Config.URL, err) - } else { - fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) - } - } else { - // For those dependencies that are not known to helm and using a - // generated key name we display the repo url. - if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { - fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.URL) - } else { - fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) - } - } - wg.Done() - }(r) - } - wg.Wait() - - return nil -} - -// findChartURL searches the cache of repo data for a chart that has the name and the repoURL specified. -// -// 'name' is the name of the chart. Version is an exact semver, or an empty string. If empty, the -// newest version will be returned. -// -// repoURL is the repository to search -// -// If it finds a URL that is "relative", it will prepend the repoURL. -func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, caFile, certFile, keyFile string, err error) { - if registry.IsOCI(repoURL) { - return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil - } - - for _, cr := range repos { - - if urlutil.Equal(repoURL, cr.Config.URL) { - var entry repo.ChartVersions - entry, err = findEntryByName(name, cr) - if err != nil { - return - } - var ve *repo.ChartVersion - ve, err = findVersionedEntry(version, entry) - if err != nil { - return - } - url, err = normalizeURL(repoURL, ve.URLs[0]) - if err != nil { - return - } - username = cr.Config.Username - password = cr.Config.Password - passcredentialsall = cr.Config.PassCredentialsAll - insecureskiptlsverify = cr.Config.InsecureSkipTLSverify - caFile = cr.Config.CAFile - certFile = cr.Config.CertFile - keyFile = cr.Config.KeyFile - return - } - } - url, err = repo.FindChartInRepoURL(repoURL, name, version, certFile, keyFile, caFile, m.Getters) - if err == nil { - return url, username, password, false, false, "", "", "", err - } - err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err) - return url, username, password, false, false, "", "", "", err -} - -// findEntryByName finds an entry in the chart repository whose name matches the given name. -// -// It returns the ChartVersions for that entry. -func findEntryByName(name string, cr *repo.ChartRepository) (repo.ChartVersions, error) { - for ename, entry := range cr.IndexFile.Entries { - if ename == name { - return entry, nil - } - } - return nil, errors.New("entry not found") -} - -// findVersionedEntry takes a ChartVersions list and returns a single chart version that satisfies the version constraints. -// -// If version is empty, the first chart found is returned. -func findVersionedEntry(version string, vers repo.ChartVersions) (*repo.ChartVersion, error) { - for _, verEntry := range vers { - if len(verEntry.URLs) == 0 { - // Not a legit entry. - continue - } - - if version == "" || versionEquals(version, verEntry.Version) { - return verEntry, nil - } - } - return nil, errors.New("no matching version") -} - -func versionEquals(v1, v2 string) bool { - sv1, err := semver.NewVersion(v1) - if err != nil { - // Fallback to string comparison. - return v1 == v2 - } - sv2, err := semver.NewVersion(v2) - if err != nil { - return false - } - return sv1.Equal(sv2) -} - -func normalizeURL(baseURL, urlOrPath string) (string, error) { - u, err := url.Parse(urlOrPath) - if err != nil { - return urlOrPath, err - } - if u.IsAbs() { - return u.String(), nil - } - u2, err := url.Parse(baseURL) - if err != nil { - return urlOrPath, errors.Wrap(err, "base URL failed to parse") - } - - u2.RawPath = path.Join(u2.RawPath, urlOrPath) - u2.Path = path.Join(u2.Path, urlOrPath) - return u2.String(), nil -} - -// loadChartRepositories reads the repositories.yaml, and then builds a map of -// ChartRepositories. -// -// The key is the local name (which is only present in the repositories.yaml). -func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) { - indices := map[string]*repo.ChartRepository{} - - // Load repositories.yaml file - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return indices, errors.Wrapf(err, "failed to load %s", m.RepositoryConfig) - } - - for _, re := range rf.Repositories { - lname := re.Name - idxFile := filepath.Join(m.RepositoryCache, helmpath.CacheIndexFile(lname)) - index, err := repo.LoadIndexFile(idxFile) - if err != nil { - return indices, err - } - - // TODO: use constructor - cr := &repo.ChartRepository{ - Config: re, - IndexFile: index, - } - indices[lname] = cr - } - return indices, nil -} - -// writeLock writes a lockfile to disk -func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { - data, err := yaml.Marshal(lock) - if err != nil { - return err - } - lockfileName := "Chart.lock" - if legacyLockfile { - lockfileName = "requirements.lock" - } - dest := filepath.Join(chartpath, lockfileName) - return ioutil.WriteFile(dest, data, 0644) -} - -// archive a dep chart from local directory and save it into destPath -func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) { - if !strings.HasPrefix(repo, "file://") { - return "", errors.Errorf("wrong format: chart %s repository %s", name, repo) - } - - origPath, err := resolver.GetLocalPath(repo, chartpath) - if err != nil { - return "", err - } - - ch, err := loader.LoadDir(origPath) - if err != nil { - return "", err - } - - constraint, err := semver.NewConstraint(version) - if err != nil { - return "", errors.Wrapf(err, "dependency %s has an invalid version/constraint format", name) - } - - v, err := semver.NewVersion(ch.Metadata.Version) - if err != nil { - return "", err - } - - if constraint.Check(v) { - _, err = chartutil.Save(ch, destPath) - return ch.Metadata.Version, err - } - - return "", errors.Errorf("can't get a valid version for dependency %s", name) -} - -// The prefix to use for cache keys created by the manager for repo names -const managerKeyPrefix = "helm-manager-" - -// key is used to turn a name, such as a repository url, into a filesystem -// safe name that is unique for querying. To accomplish this a unique hash of -// the string is used. -func key(name string) (string, error) { - in := strings.NewReader(name) - hash := crypto.SHA256.New() - if _, err := io.Copy(hash, in); err != nil { - return "", nil - } - return hex.EncodeToString(hash.Sum(nil)), nil -} diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go deleted file mode 100644 index f7ab1a568..000000000 --- a/pkg/downloader/manager_test.go +++ /dev/null @@ -1,574 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package downloader - -import ( - "bytes" - "os" - "path/filepath" - "reflect" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo/repotest" -) - -func TestVersionEquals(t *testing.T) { - tests := []struct { - name, v1, v2 string - expect bool - }{ - {name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true}, - {name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true}, - {name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true}, - {name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false}, - {name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false}, - } - - for _, tt := range tests { - if versionEquals(tt.v1, tt.v2) != tt.expect { - t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect) - } - } -} - -func TestNormalizeURL(t *testing.T) { - tests := []struct { - name, base, path, expect string - }{ - {name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"}, - {name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"}, - {name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"}, - } - - for _, tt := range tests { - got, err := normalizeURL(tt.base, tt.path) - if err != nil { - t.Errorf("%s: error %s", tt.name, err) - continue - } else if got != tt.expect { - t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got) - } - } -} - -func TestFindChartURL(t *testing.T) { - var b bytes.Buffer - m := &Manager{ - Out: &b, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - } - repos, err := m.loadChartRepositories() - if err != nil { - t.Fatal(err) - } - - name := "alpine" - version := "0.1.0" - repoURL := "http://example.com/charts" - - churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } - - if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } - if insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } - - name = "tlsfoo" - version = "1.2.3" - repoURL = "https://example-https-insecureskiptlsverify.com" - - churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } - - if !insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } - if churl != "https://example.com/tlsfoo-1.2.3.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } -} - -func TestGetRepoNames(t *testing.T) { - b := bytes.NewBuffer(nil) - m := &Manager{ - Out: b, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - } - tests := []struct { - name string - req []*chart.Dependency - expect map[string]string - err bool - }{ - { - name: "no repo definition, but references a url", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "http://example.com/test"}, - }, - expect: map[string]string{"http://example.com/test": "http://example.com/test"}, - }, - { - name: "no repo definition failure -- stable repo", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "stable"}, - }, - err: true, - }, - { - name: "no repo definition failure", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "http://example.com"}, - }, - expect: map[string]string{"oedipus-rex": "testing"}, - }, - { - name: "repo from local path", - req: []*chart.Dependency{ - {Name: "local-dep", Repository: "file://./testdata/signtest"}, - }, - expect: map[string]string{"local-dep": "file://./testdata/signtest"}, - }, - { - name: "repo alias (alias:)", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "alias:testing"}, - }, - expect: map[string]string{"oedipus-rex": "testing"}, - }, - { - name: "repo alias (@)", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "@testing"}, - }, - expect: map[string]string{"oedipus-rex": "testing"}, - }, - { - name: "repo from local chart under charts path", - req: []*chart.Dependency{ - {Name: "local-subchart", Repository: ""}, - }, - expect: map[string]string{}, - }, - } - - for _, tt := range tests { - l, err := m.resolveRepoNames(tt.req) - if err != nil { - if tt.err { - continue - } - t.Fatal(err) - } - - if tt.err { - t.Fatalf("Expected error in test %q", tt.name) - } - - // m1 and m2 are the maps we want to compare - eq := reflect.DeepEqual(l, tt.expect) - if !eq { - t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name) - } - } -} - -func TestDownloadAll(t *testing.T) { - chartPath := t.TempDir() - m := &Manager{ - Out: new(bytes.Buffer), - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - ChartPath: chartPath, - } - signtest, err := loader.LoadDir(filepath.Join("testdata", "signtest")) - if err != nil { - t.Fatal(err) - } - if err := chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata")); err != nil { - t.Fatal(err) - } - - local, err := loader.LoadDir(filepath.Join("testdata", "local-subchart")) - if err != nil { - t.Fatal(err) - } - if err := chartutil.SaveDir(local, filepath.Join(chartPath, "charts")); err != nil { - t.Fatal(err) - } - - signDep := &chart.Dependency{ - Name: signtest.Name(), - Repository: "file://./testdata/signtest", - Version: signtest.Metadata.Version, - } - localDep := &chart.Dependency{ - Name: local.Name(), - Repository: "", - Version: local.Metadata.Version, - } - - // create a 'tmpcharts' directory to test #5567 - if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0755); err != nil { - t.Fatal(err) - } - if err := m.downloadAll([]*chart.Dependency{signDep, localDep}); err != nil { - t.Error(err) - } - - if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) { - t.Error(err) - } -} - -func TestUpdateBeforeBuild(t *testing.T) { - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - dir := func(p ...string) string { - return filepath.Join(append([]string{srv.Root()}, p...)...) - } - - // Save dep - d := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "dep-chart", - Version: "0.1.0", - APIVersion: "v1", - }, - } - if err := chartutil.SaveDir(d, dir()); err != nil { - t.Fatal(err) - } - // Save a chart - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "with-dependency", - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{{ - Name: d.Metadata.Name, - Version: ">=0.1.0", - Repository: "file://../dep-chart", - }}, - }, - } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } - - // Set-up a manager - b := bytes.NewBuffer(nil) - g := getter.Providers{getter.Provider{ - Schemes: []string{"http", "https"}, - New: getter.NewHTTPGetter, - }} - m := &Manager{ - ChartPath: dir(c.Metadata.Name), - Out: b, - Getters: g, - RepositoryConfig: dir("repositories.yaml"), - RepositoryCache: dir(), - } - - // Update before Build. see issue: https://github.com/helm/helm/issues/7101 - err = m.Update() - if err != nil { - t.Fatal(err) - } - - err = m.Build() - if err != nil { - t.Fatal(err) - } -} - -// TestUpdateWithNoRepo is for the case of a dependency that has no repo listed. -// This happens when the dependency is in the charts directory and does not need -// to be fetched. -func TestUpdateWithNoRepo(t *testing.T) { - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - dir := func(p ...string) string { - return filepath.Join(append([]string{srv.Root()}, p...)...) - } - - // Setup the dependent chart - d := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "dep-chart", - Version: "0.1.0", - APIVersion: "v1", - }, - } - - // Save a chart with the dependency - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "with-dependency", - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{{ - Name: d.Metadata.Name, - Version: "0.1.0", - }}, - }, - } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } - - // Save dependent chart into the parents charts directory. If the chart is - // not in the charts directory Helm will return an error that it is not - // found. - if err := chartutil.SaveDir(d, dir(c.Metadata.Name, "charts")); err != nil { - t.Fatal(err) - } - - // Set-up a manager - b := bytes.NewBuffer(nil) - g := getter.Providers{getter.Provider{ - Schemes: []string{"http", "https"}, - New: getter.NewHTTPGetter, - }} - m := &Manager{ - ChartPath: dir(c.Metadata.Name), - Out: b, - Getters: g, - RepositoryConfig: dir("repositories.yaml"), - RepositoryCache: dir(), - } - - // Test the update - err = m.Update() - if err != nil { - t.Fatal(err) - } -} - -// This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874. -// -// This function is used by below tests that ensures success of build operation -// with optional fields, alias, condition, tags, and even with ranged version. -// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default. -// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used. -func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) { - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - dir := func(p ...string) string { - return filepath.Join(append([]string{srv.Root()}, p...)...) - } - - // Set main fields if not exist - if dep.Name == "" { - dep.Name = "local-subchart" - } - if dep.Version == "" { - dep.Version = "0.1.0" - } - if dep.Repository == "" { - dep.Repository = srv.URL() - } - - // Save a chart - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: chartName, - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{&dep}, - }, - } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } - - // Set-up a manager - b := bytes.NewBuffer(nil) - g := getter.Providers{getter.Provider{ - Schemes: []string{"http", "https"}, - New: getter.NewHTTPGetter, - }} - m := &Manager{ - ChartPath: dir(chartName), - Out: b, - Getters: g, - RepositoryConfig: dir("repositories.yaml"), - RepositoryCache: dir(), - } - - // First build will update dependencies and create Chart.lock file. - err = m.Build() - if err != nil { - t.Fatal(err) - } - - // Second build should be passed. See PR #6655. - err = m.Build() - if err != nil { - t.Fatal(err) - } -} - -func TestBuild_WithoutOptionalFields(t *testing.T) { - // Dependency has main fields only (name/version/repository) - checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{}) -} - -func TestBuild_WithSemVerRange(t *testing.T) { - // Dependency version is the form of SemVer range - checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{ - Version: ">=0.1.0", - }) -} - -func TestBuild_WithAlias(t *testing.T) { - // Dependency has an alias - checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{ - Alias: "local-subchart-alias", - }) -} - -func TestBuild_WithCondition(t *testing.T) { - // Dependency has a condition - checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{ - Condition: "some.condition", - }) -} - -func TestBuild_WithTags(t *testing.T) { - // Dependency has several tags - checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{ - Tags: []string{"tag1", "tag2"}, - }) -} - -// Failing test for #6871 -func TestBuild_WithRepositoryAlias(t *testing.T) { - // Dependency repository is aliased in Chart.yaml - checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{ - Repository: "@test", - }) -} - -func TestErrRepoNotFound_Error(t *testing.T) { - type fields struct { - Repos []string - } - tests := []struct { - name string - fields fields - want string - }{ - { - name: "OK", - fields: fields{ - Repos: []string{"https://charts1.example.com", "https://charts2.example.com"}, - }, - want: "no repository definition for https://charts1.example.com, https://charts2.example.com", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrRepoNotFound{ - Repos: tt.fields.Repos, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestKey(t *testing.T) { - tests := []struct { - name string - expect string - }{ - { - name: "file:////tmp", - expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3", - }, - { - name: "https://example.com/charts", - expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d", - }, - { - name: "foo/bar/baz", - expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82", - }, - } - - for _, tt := range tests { - o, err := key(tt.name) - if err != nil { - t.Fatalf("unable to generate key for %q with error: %s", tt.name, err) - } - if o != tt.expect { - t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o) - } - } -} diff --git a/pkg/downloader/testdata/helm-test-key.pub b/pkg/downloader/testdata/helm-test-key.pub deleted file mode 100644 index 38714f25a..000000000 Binary files a/pkg/downloader/testdata/helm-test-key.pub and /dev/null differ diff --git a/pkg/downloader/testdata/helm-test-key.secret b/pkg/downloader/testdata/helm-test-key.secret deleted file mode 100644 index a966aef93..000000000 Binary files a/pkg/downloader/testdata/helm-test-key.secret and /dev/null differ diff --git a/pkg/downloader/testdata/local-subchart-0.1.0.tgz b/pkg/downloader/testdata/local-subchart-0.1.0.tgz deleted file mode 100644 index 485312105..000000000 Binary files a/pkg/downloader/testdata/local-subchart-0.1.0.tgz and /dev/null differ diff --git a/pkg/downloader/testdata/local-subchart/Chart.yaml b/pkg/downloader/testdata/local-subchart/Chart.yaml deleted file mode 100644 index 1e17203e5..000000000 --- a/pkg/downloader/testdata/local-subchart/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -description: A Helm chart for Kubernetes -name: local-subchart -version: 0.1.0 diff --git a/pkg/downloader/testdata/repositories.yaml b/pkg/downloader/testdata/repositories.yaml deleted file mode 100644 index db7a57687..000000000 --- a/pkg/downloader/testdata/repositories.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: v1 -repositories: - - name: testing - url: "http://example.com" - - name: testing-https - url: "https://example.com" - - name: testing-basicauth - url: "http://username:password@example.com" - - name: kubernetes-charts - url: "http://example.com/charts" - - name: malformed - url: "http://dl.example.com" - - name: testing-querystring - url: "http://example.com?key=value" - - name: testing-relative - url: "http://example.com/helm" - - name: testing-relative-trailing-slash - url: "http://example.com/helm/" - - name: testing-ca-file - url: "https://example.com" - certFile: "cert" - keyFile: "key" - caFile: "ca" - - name: testing-https-insecureskip-tls-verify - url: "https://example-https-insecureskiptlsverify.com" - insecure_skip_tls_verify: true - - name: encoded-url - url: "http://example.com/with%2Fslash" diff --git a/pkg/downloader/testdata/repository/encoded-url-index.yaml b/pkg/downloader/testdata/repository/encoded-url-index.yaml deleted file mode 100644 index f9ec867a5..000000000 --- a/pkg/downloader/testdata/repository/encoded-url-index.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -entries: - foobar: - - name: foobar - description: Foo Chart With Encoded URL - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - charts/foobar-4.2.1.tgz - version: 4.2.1 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/kubernetes-charts-index.yaml b/pkg/downloader/testdata/repository/kubernetes-charts-index.yaml deleted file mode 100644 index 52dcf930b..000000000 --- a/pkg/downloader/testdata/repository/kubernetes-charts-index.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: v1 -entries: - alpine: - - name: alpine - urls: - - https://charts.helm.sh/stable/alpine-0.1.0.tgz - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - home: https://helm.sh/helm - sources: - - https://github.com/helm/helm - version: 0.1.0 - description: Deploy a basic Alpine Linux pod - keywords: [] - maintainers: [] - icon: "" - apiVersion: v2 - - name: alpine - urls: - - https://charts.helm.sh/stable/alpine-0.2.0.tgz - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - home: https://helm.sh/helm - sources: - - https://github.com/helm/helm - version: 0.2.0 - description: Deploy a basic Alpine Linux pod - keywords: [] - maintainers: [] - icon: "" - apiVersion: v2 - mariadb: - - name: mariadb - urls: - - https://charts.helm.sh/stable/mariadb-0.3.0.tgz - checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56 - home: https://mariadb.org - sources: - - https://github.com/bitnami/bitnami-docker-mariadb - version: 0.3.0 - description: Chart for MariaDB - keywords: - - mariadb - - mysql - - database - - sql - maintainers: - - name: Bitnami - email: containers@bitnami.com - icon: "" - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/malformed-index.yaml b/pkg/downloader/testdata/repository/malformed-index.yaml deleted file mode 100644 index fa319abdd..000000000 --- a/pkg/downloader/testdata/repository/malformed-index.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -entries: - alpine: - - name: alpine - urls: - - alpine-1.2.3.tgz - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - home: https://helm.sh/helm - sources: - - https://github.com/helm/helm - version: 1.2.3 - description: Deploy a basic Alpine Linux pod - keywords: [] - maintainers: [] - icon: "" - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-basicauth-index.yaml b/pkg/downloader/testdata/repository/testing-basicauth-index.yaml deleted file mode 100644 index ed092ef41..000000000 --- a/pkg/downloader/testdata/repository/testing-basicauth-index.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -entries: - foo: - - name: foo - description: Foo Chart - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - http://username:password@example.com/foo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-ca-file-index.yaml b/pkg/downloader/testdata/repository/testing-ca-file-index.yaml deleted file mode 100644 index 81901efc7..000000000 --- a/pkg/downloader/testdata/repository/testing-ca-file-index.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -entries: - foo: - - name: foo - description: Foo Chart - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - https://example.com/foo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-https-index.yaml b/pkg/downloader/testdata/repository/testing-https-index.yaml deleted file mode 100644 index 81901efc7..000000000 --- a/pkg/downloader/testdata/repository/testing-https-index.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -entries: - foo: - - name: foo - description: Foo Chart - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - https://example.com/foo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-https-insecureskip-tls-verify-index.yaml b/pkg/downloader/testdata/repository/testing-https-insecureskip-tls-verify-index.yaml deleted file mode 100644 index 58f928ff4..000000000 --- a/pkg/downloader/testdata/repository/testing-https-insecureskip-tls-verify-index.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -entries: - tlsfoo: - - name: tlsfoo - description: TLS FOO Chart - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - https://example.com/tlsfoo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b7373 diff --git a/pkg/downloader/testdata/repository/testing-index.yaml b/pkg/downloader/testdata/repository/testing-index.yaml deleted file mode 100644 index f588bf1fb..000000000 --- a/pkg/downloader/testdata/repository/testing-index.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: v1 -entries: - alpine: - - name: alpine - urls: - - http://example.com/alpine-1.2.3.tgz - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - home: https://helm.sh/helm - sources: - - https://github.com/helm/helm - version: 1.2.3 - description: Deploy a basic Alpine Linux pod - keywords: [] - maintainers: [] - icon: "" - apiVersion: v2 - - name: alpine - urls: - - http://example.com/alpine-0.2.0.tgz - - https://charts.helm.sh/stable/alpine-0.2.0.tgz - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - home: https://helm.sh/helm - sources: - - https://github.com/helm/helm - version: 0.2.0 - description: Deploy a basic Alpine Linux pod - keywords: [] - maintainers: [] - icon: "" - apiVersion: v2 - foo: - - name: foo - description: Foo Chart - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - http://example.com/foo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-querystring-index.yaml b/pkg/downloader/testdata/repository/testing-querystring-index.yaml deleted file mode 100644 index fa319abdd..000000000 --- a/pkg/downloader/testdata/repository/testing-querystring-index.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -entries: - alpine: - - name: alpine - urls: - - alpine-1.2.3.tgz - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - home: https://helm.sh/helm - sources: - - https://github.com/helm/helm - version: 1.2.3 - description: Deploy a basic Alpine Linux pod - keywords: [] - maintainers: [] - icon: "" - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-relative-index.yaml b/pkg/downloader/testdata/repository/testing-relative-index.yaml deleted file mode 100644 index ba27ed257..000000000 --- a/pkg/downloader/testdata/repository/testing-relative-index.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: v1 -entries: - foo: - - name: foo - description: Foo Chart With Relative Path - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - charts/foo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 - bar: - - name: bar - description: Bar Chart With Relative Path - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - bar-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/repository/testing-relative-trailing-slash-index.yaml b/pkg/downloader/testdata/repository/testing-relative-trailing-slash-index.yaml deleted file mode 100644 index ba27ed257..000000000 --- a/pkg/downloader/testdata/repository/testing-relative-trailing-slash-index.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: v1 -entries: - foo: - - name: foo - description: Foo Chart With Relative Path - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - charts/foo-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 - bar: - - name: bar - description: Bar Chart With Relative Path - home: https://helm.sh/helm - keywords: [] - maintainers: [] - sources: - - https://github.com/helm/charts - urls: - - bar-1.2.3.tgz - version: 1.2.3 - checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d - apiVersion: v2 diff --git a/pkg/downloader/testdata/signtest-0.1.0.tgz b/pkg/downloader/testdata/signtest-0.1.0.tgz deleted file mode 100644 index c74e5b0ef..000000000 Binary files a/pkg/downloader/testdata/signtest-0.1.0.tgz and /dev/null differ diff --git a/pkg/downloader/testdata/signtest-0.1.0.tgz.prov b/pkg/downloader/testdata/signtest-0.1.0.tgz.prov deleted file mode 100644 index d325bb266..000000000 --- a/pkg/downloader/testdata/signtest-0.1.0.tgz.prov +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -apiVersion: v1 -description: A Helm chart for Kubernetes -name: signtest -version: 0.1.0 - -... -files: - signtest-0.1.0.tgz: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55 ------BEGIN PGP SIGNATURE----- - -wsBcBAEBCgAQBQJcoosfCRCEO7+YH8GHYgAA220IALAs8T8NPgkcLvHu+5109cAN -BOCNPSZDNsqLZW/2Dc9cKoBG7Jen4Qad+i5l9351kqn3D9Gm6eRfAWcjfggRobV/ -9daZ19h0nl4O1muQNAkjvdgZt8MOP3+PB3I3/Tu2QCYjI579SLUmuXlcZR5BCFPR -PJy+e3QpV2PcdeU2KZLG4tjtlrq+3QC9ZHHEJLs+BVN9d46Dwo6CxJdHJrrrAkTw -M8MhA92vbiTTPRSCZI9x5qDAwJYhoq0oxLflpuL2tIlo3qVoCsaTSURwMESEHO32 -XwYG7BaVDMELWhAorBAGBGBwWFbJ1677qQ2gd9CN0COiVhekWlFRcnn60800r84= -=k9Y9 ------END PGP SIGNATURE----- \ No newline at end of file diff --git a/pkg/downloader/testdata/signtest/.helmignore b/pkg/downloader/testdata/signtest/.helmignore deleted file mode 100644 index 435b756d8..000000000 --- a/pkg/downloader/testdata/signtest/.helmignore +++ /dev/null @@ -1,5 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -.git diff --git a/pkg/downloader/testdata/signtest/Chart.yaml b/pkg/downloader/testdata/signtest/Chart.yaml deleted file mode 100644 index f1f73723a..000000000 --- a/pkg/downloader/testdata/signtest/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: signtest -version: 0.1.0 diff --git a/pkg/downloader/testdata/signtest/alpine/Chart.yaml b/pkg/downloader/testdata/signtest/alpine/Chart.yaml deleted file mode 100644 index eec261220..000000000 --- a/pkg/downloader/testdata/signtest/alpine/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -description: Deploy a basic Alpine Linux pod -home: https://helm.sh/helm -name: alpine -sources: -- https://github.com/helm/helm -version: 0.1.0 diff --git a/pkg/downloader/testdata/signtest/alpine/README.md b/pkg/downloader/testdata/signtest/alpine/README.md deleted file mode 100644 index 28bebae07..000000000 --- a/pkg/downloader/testdata/signtest/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.yaml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml b/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{.Release.Name}}-{{.Chart.Name}} - labels: - app.kubernetes.io/managed-by: {{.Release.Service}} - chartName: {{.Chart.Name}} - chartVersion: {{.Chart.Version | quote}} -spec: - restartPolicy: {{default "Never" .restart_policy}} - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/downloader/testdata/signtest/alpine/values.yaml b/pkg/downloader/testdata/signtest/alpine/values.yaml deleted file mode 100644 index bb6c06ae4..000000000 --- a/pkg/downloader/testdata/signtest/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: my-alpine diff --git a/pkg/downloader/testdata/signtest/templates/pod.yaml b/pkg/downloader/testdata/signtest/templates/pod.yaml deleted file mode 100644 index 9b00ccaf7..000000000 --- a/pkg/downloader/testdata/signtest/templates/pod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: signtest -spec: - restartPolicy: Never - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/downloader/testdata/signtest/values.yaml b/pkg/downloader/testdata/signtest/values.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go deleted file mode 100644 index 6ff875c46..000000000 --- a/pkg/engine/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/*Package engine implements the Go text template engine as needed for Helm. - -When Helm renders templates it does so with additional functions and different -modes (e.g., strict, lint mode). This package handles the helm specific -implementation. -*/ -package engine // import "helm.sh/helm/v3/pkg/engine" diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go deleted file mode 100644 index 00494f9d7..000000000 --- a/pkg/engine/engine.go +++ /dev/null @@ -1,401 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "fmt" - "log" - "path" - "path/filepath" - "regexp" - "sort" - "strings" - "text/template" - - "github.com/pkg/errors" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" -) - -// Engine is an implementation of the Helm rendering implementation for templates. -type Engine struct { - // If strict is enabled, template rendering will fail if a template references - // a value that was not passed in. - Strict bool - // In LintMode, some 'required' template values may be missing, so don't fail - LintMode bool - // the rest config to connect to the kubernetes api - config *rest.Config -} - -// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. -// -// Render can be called repeatedly on the same engine. -// -// This will look in the chart's 'templates' data (e.g. the 'templates/' directory) -// and attempt to render the templates there using the values passed in. -// -// Values are scoped to their templates. A dependency template will not have -// access to the values set for its parent. If chart "foo" includes chart "bar", -// "bar" will not have access to the values for "foo". -// -// Values should be prepared with something like `chartutils.ReadValues`. -// -// Values are passed through the templates according to scope. If the top layer -// chart includes the chart foo, which includes the chart bar, the values map -// will be examined for a table called "foo". If "foo" is found in vals, -// that section of the values will be passed into the "foo" chart. And if that -// section contains a value named "bar", that value will be passed on to the -// bar chart during render time. -func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { - tmap := allTemplates(chrt, values) - return e.render(tmap) -} - -// Render takes a chart, optional values, and value overrides, and attempts to -// render the Go templates using the default options. -func Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { - return new(Engine).Render(chrt, values) -} - -// RenderWithClient takes a chart, optional values, and value overrides, and attempts to -// render the Go templates using the default options. This engine is client aware and so can have template -// functions that interact with the client -func RenderWithClient(chrt *chart.Chart, values chartutil.Values, config *rest.Config) (map[string]string, error) { - return Engine{ - config: config, - }.Render(chrt, values) -} - -// renderable is an object that can be rendered. -type renderable struct { - // tpl is the current template. - tpl string - // vals are the values to be supplied to the template. - vals chartutil.Values - // namespace prefix to the templates of the current chart - basePath string -} - -const warnStartDelim = "HELM_ERR_START" -const warnEndDelim = "HELM_ERR_END" -const recursionMaxNums = 1000 - -var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim) - -func warnWrap(warn string) string { - return warnStartDelim + warn + warnEndDelim -} - -// initFunMap creates the Engine's FuncMap and adds context-specific functions. -func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { - funcMap := funcMap() - includedNames := make(map[string]int) - - // Add the 'include' function here so we can close over t. - funcMap["include"] = func(name string, data interface{}) (string, error) { - var buf strings.Builder - if v, ok := includedNames[name]; ok { - if v > recursionMaxNums { - return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name) - } - includedNames[name]++ - } else { - includedNames[name] = 1 - } - err := t.ExecuteTemplate(&buf, name, data) - includedNames[name]-- - return buf.String(), err - } - - // Add the 'tpl' function here - funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { - basePath, err := vals.PathValue("Template.BasePath") - if err != nil { - return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl) - } - - templateName, err := vals.PathValue("Template.Name") - if err != nil { - return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl) - } - - templates := map[string]renderable{ - templateName.(string): { - tpl: tpl, - vals: vals, - basePath: basePath.(string), - }, - } - - result, err := e.renderWithReferences(templates, referenceTpls) - if err != nil { - return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) - } - return result[templateName.(string)], nil - } - - // Add the `required` function here so we can use lintMode - funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { - if val == nil { - if e.LintMode { - // Don't fail on missing required values when linting - log.Printf("[INFO] Missing required value: %s", warn) - return "", nil - } - return val, errors.Errorf(warnWrap(warn)) - } else if _, ok := val.(string); ok { - if val == "" { - if e.LintMode { - // Don't fail on missing required values when linting - log.Printf("[INFO] Missing required value: %s", warn) - return "", nil - } - return val, errors.Errorf(warnWrap(warn)) - } - } - return val, nil - } - - // Override sprig fail function for linting and wrapping message - funcMap["fail"] = func(msg string) (string, error) { - if e.LintMode { - // Don't fail when linting - log.Printf("[INFO] Fail: %s", msg) - return "", nil - } - return "", errors.New(warnWrap(msg)) - } - - // If we are not linting and have a cluster connection, provide a Kubernetes-backed - // implementation. - if !e.LintMode && e.config != nil { - funcMap["lookup"] = NewLookupFunction(e.config) - } - - t.Funcs(funcMap) -} - -// render takes a map of templates/values and renders them. -func (e Engine) render(tpls map[string]renderable) (map[string]string, error) { - return e.renderWithReferences(tpls, tpls) -} - -// renderWithReferences takes a map of templates/values to render, and a map of -// templates which can be referenced within them. -func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) { - // Basically, what we do here is start with an empty parent template and then - // build up a list of templates -- one for each file. Once all of the templates - // have been parsed, we loop through again and execute every template. - // - // The idea with this process is to make it possible for more complex templates - // to share common blocks, but to make the entire thing feel like a file-based - // template engine. - defer func() { - if r := recover(); r != nil { - err = errors.Errorf("rendering template failed: %v", r) - } - }() - t := template.New("gotpl") - if e.Strict { - t.Option("missingkey=error") - } else { - // Not that zero will attempt to add default values for types it knows, - // but will still emit for others. We mitigate that later. - t.Option("missingkey=zero") - } - - e.initFunMap(t, referenceTpls) - - // We want to parse the templates in a predictable order. The order favors - // higher-level (in file system) templates over deeply nested templates. - keys := sortTemplates(tpls) - referenceKeys := sortTemplates(referenceTpls) - - for _, filename := range keys { - r := tpls[filename] - if _, err := t.New(filename).Parse(r.tpl); err != nil { - return map[string]string{}, cleanupParseError(filename, err) - } - } - - // Adding the reference templates to the template context - // so they can be referenced in the tpl function - for _, filename := range referenceKeys { - if t.Lookup(filename) == nil { - r := referenceTpls[filename] - if _, err := t.New(filename).Parse(r.tpl); err != nil { - return map[string]string{}, cleanupParseError(filename, err) - } - } - } - - rendered = make(map[string]string, len(keys)) - for _, filename := range keys { - // Don't render partials. We don't care out the direct output of partials. - // They are only included from other templates. - if strings.HasPrefix(path.Base(filename), "_") { - continue - } - // At render time, add information about the template that is being rendered. - vals := tpls[filename].vals - vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath} - var buf strings.Builder - if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { - return map[string]string{}, cleanupExecError(filename, err) - } - - // Work around the issue where Go will emit "" even if Options(missing=zero) - // is set. Since missing=error will never get here, we do not need to handle - // the Strict case. - rendered[filename] = strings.ReplaceAll(buf.String(), "", "") - } - - return rendered, nil -} - -func cleanupParseError(filename string, err error) error { - tokens := strings.Split(err.Error(), ": ") - if len(tokens) == 1 { - // This might happen if a non-templating error occurs - return fmt.Errorf("parse error in (%s): %s", filename, err) - } - // The first token is "template" - // The second token is either "filename:lineno" or "filename:lineNo:columnNo" - location := tokens[1] - // The remaining tokens make up a stacktrace-like chain, ending with the relevant error - errMsg := tokens[len(tokens)-1] - return fmt.Errorf("parse error at (%s): %s", string(location), errMsg) -} - -func cleanupExecError(filename string, err error) error { - if _, isExecError := err.(template.ExecError); !isExecError { - return err - } - - tokens := strings.SplitN(err.Error(), ": ", 3) - if len(tokens) != 3 { - // This might happen if a non-templating error occurs - return fmt.Errorf("execution error in (%s): %s", filename, err) - } - - // The first token is "template" - // The second token is either "filename:lineno" or "filename:lineNo:columnNo" - location := tokens[1] - - parts := warnRegex.FindStringSubmatch(tokens[2]) - if len(parts) >= 2 { - return fmt.Errorf("execution error at (%s): %s", string(location), parts[1]) - } - - return err -} - -func sortTemplates(tpls map[string]renderable) []string { - keys := make([]string, len(tpls)) - i := 0 - for key := range tpls { - keys[i] = key - i++ - } - sort.Sort(sort.Reverse(byPathLen(keys))) - return keys -} - -type byPathLen []string - -func (p byPathLen) Len() int { return len(p) } -func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] } -func (p byPathLen) Less(i, j int) bool { - a, b := p[i], p[j] - ca, cb := strings.Count(a, "/"), strings.Count(b, "/") - if ca == cb { - return strings.Compare(a, b) == -1 - } - return ca < cb -} - -// allTemplates returns all templates for a chart and its dependencies. -// -// As it goes, it also prepares the values in a scope-sensitive manner. -func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { - templates := make(map[string]renderable) - recAllTpls(c, templates, vals) - return templates -} - -// recAllTpls recurses through the templates in a chart. -// -// As it recurses, it also sets the values to be appropriate for the template -// scope. -func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) map[string]interface{} { - subCharts := make(map[string]interface{}) - chartMetaData := struct { - chart.Metadata - IsRoot bool - }{*c.Metadata, c.IsRoot()} - - next := map[string]interface{}{ - "Chart": chartMetaData, - "Files": newFiles(c.Files), - "Release": vals["Release"], - "Capabilities": vals["Capabilities"], - "Values": make(chartutil.Values), - "Subcharts": subCharts, - } - - // If there is a {{.Values.ThisChart}} in the parent metadata, - // copy that into the {{.Values}} for this template. - if c.IsRoot() { - next["Values"] = vals["Values"] - } else if vs, err := vals.Table("Values." + c.Name()); err == nil { - next["Values"] = vs - } - - for _, child := range c.Dependencies() { - subCharts[child.Name()] = recAllTpls(child, templates, next) - } - - newParentID := c.ChartFullPath() - for _, t := range c.Templates { - if !isTemplateValid(c, t.Name) { - continue - } - templates[path.Join(newParentID, t.Name)] = renderable{ - tpl: string(t.Data), - vals: next, - basePath: path.Join(newParentID, "templates"), - } - } - - return next -} - -// isTemplateValid returns true if the template is valid for the chart type -func isTemplateValid(ch *chart.Chart, templateName string) bool { - if isLibraryChart(ch) { - return strings.HasPrefix(filepath.Base(templateName), "_") - } - return true -} - -// isLibraryChart returns true if the chart is a library chart -func isLibraryChart(c *chart.Chart) bool { - return strings.EqualFold(c.Metadata.Type, "library") -} diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go deleted file mode 100644 index 54cd21ae2..000000000 --- a/pkg/engine/engine_test.go +++ /dev/null @@ -1,832 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "fmt" - "strings" - "sync" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" -) - -func TestSortTemplates(t *testing.T) { - tpls := map[string]renderable{ - "/mychart/templates/foo.tpl": {}, - "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {}, - "/mychart/templates/bar.tpl": {}, - "/mychart/templates/charts/foo/templates/bar.tpl": {}, - "/mychart/templates/_foo.tpl": {}, - "/mychart/templates/charts/foo/templates/foo.tpl": {}, - "/mychart/templates/charts/bar/templates/foo.tpl": {}, - } - got := sortTemplates(tpls) - if len(got) != len(tpls) { - t.Fatal("Sorted results are missing templates") - } - - expect := []string{ - "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl", - "/mychart/templates/charts/foo/templates/foo.tpl", - "/mychart/templates/charts/foo/templates/bar.tpl", - "/mychart/templates/charts/bar/templates/foo.tpl", - "/mychart/templates/foo.tpl", - "/mychart/templates/bar.tpl", - "/mychart/templates/_foo.tpl", - } - for i, e := range expect { - if got[i] != e { - t.Fatalf("\n\tExp:\n%s\n\tGot:\n%s", - strings.Join(expect, "\n"), - strings.Join(got, "\n"), - ) - } - } -} - -func TestFuncMap(t *testing.T) { - fns := funcMap() - forbidden := []string{"env", "expandenv"} - for _, f := range forbidden { - if _, ok := fns[f]; ok { - t.Errorf("Forbidden function %s exists in FuncMap.", f) - } - } - - // Test for Engine-specific template functions. - expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson", "lookup"} - for _, f := range expect { - if _, ok := fns[f]; !ok { - t.Errorf("Expected add-on function %q", f) - } - } -} - -func TestRender(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "moby", - Version: "1.2.3", - }, - Templates: []*chart.File{ - {Name: "templates/test1", Data: []byte("{{.Values.outer | title }} {{.Values.inner | title}}")}, - {Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")}, - {Name: "templates/test3", Data: []byte("{{.noValue}}")}, - {Name: "templates/test4", Data: []byte("{{toJson .Values}}")}, - }, - Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"}, - } - - vals := map[string]interface{}{ - "Values": map[string]interface{}{ - "outer": "spouter", - "inner": "inn", - "global": map[string]interface{}{ - "callme": "Ishmael", - }, - }, - } - - v, err := chartutil.CoalesceValues(c, vals) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } - out, err := Render(c, v) - if err != nil { - t.Errorf("Failed to render templates: %s", err) - } - - expect := map[string]string{ - "moby/templates/test1": "Spouter Inn", - "moby/templates/test2": "ishmael", - "moby/templates/test3": "", - "moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`, - } - - for name, data := range expect { - if out[name] != data { - t.Errorf("Expected %q, got %q", data, out[name]) - } - } -} - -func TestRenderRefsOrdering(t *testing.T) { - parentChart := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "parent", - Version: "1.2.3", - }, - Templates: []*chart.File{ - {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}parent value{{- end -}}`)}, - {Name: "templates/test.yaml", Data: []byte(`{{ tpl "{{ include \"test\" . }}" . }}`)}, - }, - } - childChart := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "child", - Version: "1.2.3", - }, - Templates: []*chart.File{ - {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}child value{{- end -}}`)}, - }, - } - parentChart.AddDependency(childChart) - - expect := map[string]string{ - "parent/templates/test.yaml": "parent value", - } - - for i := 0; i < 100; i++ { - out, err := Render(parentChart, chartutil.Values{}) - if err != nil { - t.Fatalf("Failed to render templates: %s", err) - } - - for name, data := range expect { - if out[name] != data { - t.Fatalf("Expected %q, got %q (iteration %d)", data, out[name], i+1) - } - } - } -} - -func TestRenderInternals(t *testing.T) { - // Test the internals of the rendering tool. - - vals := chartutil.Values{"Name": "one", "Value": "two"} - tpls := map[string]renderable{ - "one": {tpl: `Hello {{title .Name}}`, vals: vals}, - "two": {tpl: `Goodbye {{upper .Value}}`, vals: vals}, - // Test whether a template can reliably reference another template - // without regard for ordering. - "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, - } - - out, err := new(Engine).render(tpls) - if err != nil { - t.Fatalf("Failed template rendering: %s", err) - } - - if len(out) != 3 { - t.Fatalf("Expected 3 templates, got %d", len(out)) - } - - if out["one"] != "Hello One" { - t.Errorf("Expected 'Hello One', got %q", out["one"]) - } - - if out["two"] != "Goodbye TWO" { - t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"]) - } - - if out["three"] != "Goodbye THREE" { - t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"]) - } -} - -func TestParallelRenderInternals(t *testing.T) { - // Make sure that we can use one Engine to run parallel template renders. - e := new(Engine) - var wg sync.WaitGroup - for i := 0; i < 20; i++ { - wg.Add(1) - go func(i int) { - tt := fmt.Sprintf("expect-%d", i) - tpls := map[string]renderable{ - "t": { - tpl: `{{.val}}`, - vals: map[string]interface{}{"val": tt}, - }, - } - out, err := e.render(tpls) - if err != nil { - t.Errorf("Failed to render %s: %s", tt, err) - } - if out["t"] != tt { - t.Errorf("Expected %q, got %q", tt, out["t"]) - } - wg.Done() - }(i) - } - wg.Wait() -} - -func TestParseErrors(t *testing.T) { - vals := chartutil.Values{"Values": map[string]interface{}{}} - - tplsUndefinedFunction := map[string]renderable{ - "undefined_function": {tpl: `{{foo}}`, vals: vals}, - } - _, err := new(Engine).render(tplsUndefinedFunction) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - expected := `parse error at (undefined_function:1): function "foo" not defined` - if err.Error() != expected { - t.Errorf("Expected '%s', got %q", expected, err.Error()) - } -} - -func TestExecErrors(t *testing.T) { - vals := chartutil.Values{"Values": map[string]interface{}{}} - cases := []struct { - name string - tpls map[string]renderable - expected string - }{ - { - name: "MissingRequired", - tpls: map[string]renderable{ - "missing_required": {tpl: `{{required "foo is required" .Values.foo}}`, vals: vals}, - }, - expected: `execution error at (missing_required:1:2): foo is required`, - }, - { - name: "MissingRequiredWithColons", - tpls: map[string]renderable{ - "missing_required_with_colons": {tpl: `{{required ":this: message: has many: colons:" .Values.foo}}`, vals: vals}, - }, - expected: `execution error at (missing_required_with_colons:1:2): :this: message: has many: colons:`, - }, - { - name: "Issue6044", - tpls: map[string]renderable{ - "issue6044": { - vals: vals, - tpl: `{{ $someEmptyValue := "" }} -{{ $myvar := "abc" }} -{{- required (printf "%s: something is missing" $myvar) $someEmptyValue | repeat 0 }}`, - }, - }, - expected: `execution error at (issue6044:3:4): abc: something is missing`, - }, - { - name: "MissingRequiredWithNewlines", - tpls: map[string]renderable{ - "issue9981": {tpl: `{{required "foo is required\nmore info after the break" .Values.foo}}`, vals: vals}, - }, - expected: `execution error at (issue9981:1:2): foo is required -more info after the break`, - }, - { - name: "FailWithNewlines", - tpls: map[string]renderable{ - "issue9981": {tpl: `{{fail "something is wrong\nlinebreak"}}`, vals: vals}, - }, - expected: `execution error at (issue9981:1:2): something is wrong -linebreak`, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - _, err := new(Engine).render(tt.tpls) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - if err.Error() != tt.expected { - t.Errorf("Expected %q, got %q", tt.expected, err.Error()) - } - }) - } -} - -func TestFailErrors(t *testing.T) { - vals := chartutil.Values{"Values": map[string]interface{}{}} - - failtpl := `All your base are belong to us{{ fail "This is an error" }}` - tplsFailed := map[string]renderable{ - "failtpl": {tpl: failtpl, vals: vals}, - } - _, err := new(Engine).render(tplsFailed) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - expected := `execution error at (failtpl:1:33): This is an error` - if err.Error() != expected { - t.Errorf("Expected '%s', got %q", expected, err.Error()) - } - - var e Engine - e.LintMode = true - out, err := e.render(tplsFailed) - if err != nil { - t.Fatal(err) - } - - expectStr := "All your base are belong to us" - if gotStr := out["failtpl"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } -} - -func TestAllTemplates(t *testing.T) { - ch1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "ch1"}, - Templates: []*chart.File{ - {Name: "templates/foo", Data: []byte("foo")}, - {Name: "templates/bar", Data: []byte("bar")}, - }, - } - dep1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "laboratory mice"}, - Templates: []*chart.File{ - {Name: "templates/pinky", Data: []byte("pinky")}, - {Name: "templates/brain", Data: []byte("brain")}, - }, - } - ch1.AddDependency(dep1) - - dep2 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "same thing we do every night"}, - Templates: []*chart.File{ - {Name: "templates/innermost", Data: []byte("innermost")}, - }, - } - dep1.AddDependency(dep2) - - tpls := allTemplates(ch1, chartutil.Values{}) - if len(tpls) != 5 { - t.Errorf("Expected 5 charts, got %d", len(tpls)) - } -} - -func TestChartValuesContainsIsRoot(t *testing.T) { - ch1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "parent"}, - Templates: []*chart.File{ - {Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")}, - }, - } - dep1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "child"}, - Templates: []*chart.File{ - {Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")}, - }, - } - ch1.AddDependency(dep1) - - out, err := Render(ch1, chartutil.Values{}) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } - expects := map[string]string{ - "parent/charts/child/templates/isroot": "false", - "parent/templates/isroot": "true", - } - for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } - } -} - -func TestRenderDependency(t *testing.T) { - deptpl := `{{define "myblock"}}World{{end}}` - toptpl := `Hello {{template "myblock"}}` - ch := &chart.Chart{ - Metadata: &chart.Metadata{Name: "outerchart"}, - Templates: []*chart.File{ - {Name: "templates/outer", Data: []byte(toptpl)}, - }, - } - ch.AddDependency(&chart.Chart{ - Metadata: &chart.Metadata{Name: "innerchart"}, - Templates: []*chart.File{ - {Name: "templates/inner", Data: []byte(deptpl)}, - }, - }) - - out, err := Render(ch, map[string]interface{}{}) - if err != nil { - t.Fatalf("failed to render chart: %s", err) - } - - if len(out) != 2 { - t.Errorf("Expected 2, got %d", len(out)) - } - - expect := "Hello World" - if out["outerchart/templates/outer"] != expect { - t.Errorf("Expected %q, got %q", expect, out["outer"]) - } - -} - -func TestRenderNestedValues(t *testing.T) { - innerpath := "templates/inner.tpl" - outerpath := "templates/outer.tpl" - // Ensure namespacing rules are working. - deepestpath := "templates/inner.tpl" - checkrelease := "templates/release.tpl" - // Ensure subcharts scopes are working. - subchartspath := "templates/subcharts.tpl" - - deepest := &chart.Chart{ - Metadata: &chart.Metadata{Name: "deepest"}, - Templates: []*chart.File{ - {Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, - {Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, - }, - Values: map[string]interface{}{"what": "milkshake", "where": "here"}, - } - - inner := &chart.Chart{ - Metadata: &chart.Metadata{Name: "herrick"}, - Templates: []*chart.File{ - {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, - }, - Values: map[string]interface{}{"who": "Robert", "what": "glasses"}, - } - inner.AddDependency(deepest) - - outer := &chart.Chart{ - Metadata: &chart.Metadata{Name: "top"}, - Templates: []*chart.File{ - {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, - {Name: subchartspath, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)}, - }, - Values: map[string]interface{}{ - "what": "stinkweed", - "who": "me", - "herrick": map[string]interface{}{ - "who": "time", - "what": "Sun", - }, - }, - } - outer.AddDependency(inner) - - injValues := map[string]interface{}{ - "what": "rosebuds", - "herrick": map[string]interface{}{ - "deepest": map[string]interface{}{ - "what": "flower", - "where": "Heaven", - }, - }, - "global": map[string]interface{}{ - "when": "to-day", - }, - } - - tmp, err := chartutil.CoalesceValues(outer, injValues) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } - - inject := chartutil.Values{ - "Values": tmp, - "Chart": outer.Metadata, - "Release": chartutil.Values{ - "Name": "dyin", - }, - } - - t.Logf("Calculated values: %v", inject) - - out, err := Render(outer, inject) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } - - fullouterpath := "top/" + outerpath - if out[fullouterpath] != "Gather ye rosebuds while ye may" { - t.Errorf("Unexpected outer: %q", out[fullouterpath]) - } - - fullinnerpath := "top/charts/herrick/" + innerpath - if out[fullinnerpath] != "Old time is still a-flyin'" { - t.Errorf("Unexpected inner: %q", out[fullinnerpath]) - } - - fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath - if out[fulldeepestpath] != "And this same flower that smiles to-day" { - t.Errorf("Unexpected deepest: %q", out[fulldeepestpath]) - } - - fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease - if out[fullcheckrelease] != "Tomorrow will be dyin" { - t.Errorf("Unexpected release: %q", out[fullcheckrelease]) - } - - fullchecksubcharts := "top/" + subchartspath - if out[fullchecksubcharts] != "The glorious Lamp of Heaven, the Sun" { - t.Errorf("Unexpected subcharts: %q", out[fullchecksubcharts]) - } -} - -func TestRenderBuiltinValues(t *testing.T) { - inner := &chart.Chart{ - Metadata: &chart.Metadata{Name: "Latium"}, - Templates: []*chart.File{ - {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, - {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)}, - }, - Files: []*chart.File{ - {Name: "author", Data: []byte("Virgil")}, - {Name: "book/title.txt", Data: []byte("Aeneid")}, - }, - } - - outer := &chart.Chart{ - Metadata: &chart.Metadata{Name: "Troy"}, - Templates: []*chart.File{ - {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, - {Name: "templates/Amata", Data: []byte(`{{.Subcharts.Latium.Chart.Name}} {{.Subcharts.Latium.Files.author | printf "%s"}}`)}, - }, - } - outer.AddDependency(inner) - - inject := chartutil.Values{ - "Values": "", - "Chart": outer.Metadata, - "Release": chartutil.Values{ - "Name": "Aeneid", - }, - } - - t.Logf("Calculated values: %v", outer) - - out, err := Render(outer, inject) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } - - expects := map[string]string{ - "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid", - "Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid", - "Troy/templates/Amata": "Latium Virgil", - "Troy/charts/Latium/templates/From": "Virgil Aeneid", - } - for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } - } - -} - -func TestAlterFuncMap_include(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "conrad"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, - {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, - }, - } - - // Check nested reference in include FuncMap - d := &chart.Chart{ - Metadata: &chart.Metadata{Name: "nested"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(`{{include "nested/templates/quote" . | indent 2}} dead.`)}, - {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, - }, - } - - v := chartutil.Values{ - "Values": "", - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "Mistah Kurtz", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := " Mistah Kurtz - he dead." - if got := out["conrad/templates/quote"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } - - _, err = Render(d, v) - expectErrName := "nested/templates/quote" - if err == nil { - t.Errorf("Expected err of nested reference name: %v", expectErrName) - } -} - -func TestAlterFuncMap_require(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "conan"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)}, - {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)}, - }, - } - - v := chartutil.Values{ - "Values": chartutil.Values{ - "who": "us", - "bases": 2, - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "That 90s meme", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expectStr := "All your base are belong to us" - if gotStr := out["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } - expectNum := "All 2 of them!" - if gotNum := out["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) - } - - // test required without passing in needed values with lint mode on - // verifies lint replaces required with an empty string (should not fail) - lintValues := chartutil.Values{ - "Values": chartutil.Values{ - "who": "us", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "That 90s meme", - }, - } - var e Engine - e.LintMode = true - out, err = e.Render(c, lintValues) - if err != nil { - t.Fatal(err) - } - - expectStr = "All your base are belong to us" - if gotStr := out["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } - expectNum = "All of them!" - if gotNum := out["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) - } -} - -func TestAlterFuncMap_tpl(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "TplFunction"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, - }, - } - - v := chartutil.Values{ - "Values": chartutil.Values{ - "value": "myvalue", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "Evaluate tpl Value: myvalue" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } -} - -func TestAlterFuncMap_tplfunc(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "TplFunction"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, - }, - } - - v := chartutil.Values{ - "Values": chartutil.Values{ - "value": "myvalue", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "Evaluate tpl Value: \"myvalue\"" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } -} - -func TestAlterFuncMap_tplinclude(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "TplFunction"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, - {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, - }, - } - v := chartutil.Values{ - "Values": chartutil.Values{ - "value": "myvalue", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "\"TplFunction/templates/base\"" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } - -} - -func TestRenderRecursionLimit(t *testing.T) { - // endless recursion should produce an error - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "bad"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`{{include "recursion" . }}`)}, - {Name: "templates/recursion", Data: []byte(`{{define "recursion"}}{{include "recursion" . }}{{end}}`)}, - }, - } - v := chartutil.Values{ - "Values": "", - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - expectErr := "rendering template has a nested reference name: recursion: unable to execute template" - - _, err := Render(c, v) - if err == nil || !strings.HasSuffix(err.Error(), expectErr) { - t.Errorf("Expected err with suffix: %s", expectErr) - } - - // calling the same function many times is ok - times := 4000 - phrase := "All work and no play makes Jack a dull boy" - printFunc := `{{define "overlook"}}{{printf "` + phrase + `\n"}}{{end}}` - var repeatedIncl string - for i := 0; i < times; i++ { - repeatedIncl += `{{include "overlook" . }}` - } - - d := &chart.Chart{ - Metadata: &chart.Metadata{Name: "overlook"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(repeatedIncl)}, - {Name: "templates/_function", Data: []byte(printFunc)}, - }, - } - - out, err := Render(d, v) - if err != nil { - t.Fatal(err) - } - - var expect string - for i := 0; i < times; i++ { - expect += phrase + "\n" - } - if got := out["overlook/templates/quote"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } - -} diff --git a/pkg/engine/files.go b/pkg/engine/files.go deleted file mode 100644 index d7e62da5a..000000000 --- a/pkg/engine/files.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "encoding/base64" - "path" - "strings" - - "github.com/gobwas/glob" - - "helm.sh/helm/v3/pkg/chart" -) - -// files is a map of files in a chart that can be accessed from a template. -type files map[string][]byte - -// NewFiles creates a new files from chart files. -// Given an []*chart.File (the format for files in a chart.Chart), extract a map of files. -func newFiles(from []*chart.File) files { - files := make(map[string][]byte) - for _, f := range from { - files[f.Name] = f.Data - } - return files -} - -// GetBytes gets a file by path. -// -// The returned data is raw. In a template context, this is identical to calling -// {{index .Files $path}}. -// -// This is intended to be accessed from within a template, so a missed key returns -// an empty []byte. -func (f files) GetBytes(name string) []byte { - if v, ok := f[name]; ok { - return v - } - return []byte{} -} - -// Get returns a string representation of the given file. -// -// Fetch the contents of a file as a string. It is designed to be called in a -// template. -// -// {{.Files.Get "foo"}} -func (f files) Get(name string) string { - return string(f.GetBytes(name)) -} - -// Glob takes a glob pattern and returns another files object only containing -// matched files. -// -// This is designed to be called from a template. -// -// {{ range $name, $content := .Files.Glob("foo/**") }} -// {{ $name }}: | -// {{ .Files.Get($name) | indent 4 }}{{ end }} -func (f files) Glob(pattern string) files { - g, err := glob.Compile(pattern, '/') - if err != nil { - g, _ = glob.Compile("**") - } - - nf := newFiles(nil) - for name, contents := range f { - if g.Match(name) { - nf[name] = contents - } - } - - return nf -} - -// AsConfig turns a Files group and flattens it to a YAML map suitable for -// including in the 'data' section of a Kubernetes ConfigMap definition. -// Duplicate keys will be overwritten, so be aware that your file names -// (regardless of path) should be unique. -// -// This is designed to be called from a template, and will return empty string -// (via toYAML function) if it cannot be serialized to YAML, or if the Files -// object is nil. -// -// The output will not be indented, so you will want to pipe this to the -// 'indent' template function. -// -// data: -// {{ .Files.Glob("config/**").AsConfig() | indent 4 }} -func (f files) AsConfig() string { - if f == nil { - return "" - } - - m := make(map[string]string) - - // Explicitly convert to strings, and file names - for k, v := range f { - m[path.Base(k)] = string(v) - } - - return toYAML(m) -} - -// AsSecrets returns the base64-encoded value of a Files object suitable for -// including in the 'data' section of a Kubernetes Secret definition. -// Duplicate keys will be overwritten, so be aware that your file names -// (regardless of path) should be unique. -// -// This is designed to be called from a template, and will return empty string -// (via toYAML function) if it cannot be serialized to YAML, or if the Files -// object is nil. -// -// The output will not be indented, so you will want to pipe this to the -// 'indent' template function. -// -// data: -// {{ .Files.Glob("secrets/*").AsSecrets() }} -func (f files) AsSecrets() string { - if f == nil { - return "" - } - - m := make(map[string]string) - - for k, v := range f { - m[path.Base(k)] = base64.StdEncoding.EncodeToString(v) - } - - return toYAML(m) -} - -// Lines returns each line of a named file (split by "\n") as a slice, so it can -// be ranged over in your templates. -// -// This is designed to be called from a template. -// -// {{ range .Files.Lines "foo/bar.html" }} -// {{ . }}{{ end }} -func (f files) Lines(path string) []string { - if f == nil || f[path] == nil { - return []string{} - } - - return strings.Split(string(f[path]), "\n") -} diff --git a/pkg/engine/files_test.go b/pkg/engine/files_test.go deleted file mode 100644 index 4b37724f9..000000000 --- a/pkg/engine/files_test.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -var cases = []struct { - path, data string -}{ - {"ship/captain.txt", "The Captain"}, - {"ship/stowaway.txt", "Legatt"}, - {"story/name.txt", "The Secret Sharer"}, - {"story/author.txt", "Joseph Conrad"}, - {"multiline/test.txt", "bar\nfoo"}, -} - -func getTestFiles() files { - a := make(files, len(cases)) - for _, c := range cases { - a[c.path] = []byte(c.data) - } - return a -} - -func TestNewFiles(t *testing.T) { - files := getTestFiles() - if len(files) != len(cases) { - t.Errorf("Expected len() = %d, got %d", len(cases), len(files)) - } - - for i, f := range cases { - if got := string(files.GetBytes(f.path)); got != f.data { - t.Errorf("%d: expected %q, got %q", i, f.data, got) - } - if got := files.Get(f.path); got != f.data { - t.Errorf("%d: expected %q, got %q", i, f.data, got) - } - } -} - -func TestFileGlob(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - - matched := f.Glob("story/**") - - as.Len(matched, 2, "Should be two files in glob story/**") - as.Equal("Joseph Conrad", matched.Get("story/author.txt")) -} - -func TestToConfig(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - out := f.Glob("**/captain.txt").AsConfig() - as.Equal("captain.txt: The Captain", out) - - out = f.Glob("ship/**").AsConfig() - as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt", out) -} - -func TestToSecret(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - - out := f.Glob("ship/**").AsSecrets() - as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0", out) -} - -func TestLines(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - - out := f.Lines("multiline/test.txt") - as.Len(out, 2) - - as.Equal("bar", out[0]) -} diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go deleted file mode 100644 index 92b4c3383..000000000 --- a/pkg/engine/funcs.go +++ /dev/null @@ -1,177 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "bytes" - "encoding/json" - "strings" - "text/template" - - "github.com/BurntSushi/toml" - "github.com/Masterminds/sprig/v3" - "sigs.k8s.io/yaml" -) - -// funcMap returns a mapping of all of the functions that Engine has. -// -// Because some functions are late-bound (e.g. contain context-sensitive -// data), the functions may not all perform identically outside of an Engine -// as they will inside of an Engine. -// -// Known late-bound functions: -// -// - "include" -// - "tpl" -// -// These are late-bound in Engine.Render(). The -// version included in the FuncMap is a placeholder. -// -func funcMap() template.FuncMap { - f := sprig.TxtFuncMap() - delete(f, "env") - delete(f, "expandenv") - - // Add some extra functionality - extra := template.FuncMap{ - "toToml": toTOML, - "toYaml": toYAML, - "fromYaml": fromYAML, - "fromYamlArray": fromYAMLArray, - "toJson": toJSON, - "fromJson": fromJSON, - "fromJsonArray": fromJSONArray, - - // This is a placeholder for the "include" function, which is - // late-bound to a template. By declaring it here, we preserve the - // integrity of the linter. - "include": func(string, interface{}) string { return "not implemented" }, - "tpl": func(string, interface{}) interface{} { return "not implemented" }, - "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, - // Provide a placeholder for the "lookup" function, which requires a kubernetes - // connection. - "lookup": func(string, string, string, string) (map[string]interface{}, error) { - return map[string]interface{}{}, nil - }, - } - - for k, v := range extra { - f[k] = v - } - - return f -} - -// toYAML takes an interface, marshals it to yaml, and returns a string. It will -// always return a string, even on marshal error (empty string). -// -// This is designed to be called from a template. -func toYAML(v interface{}) string { - data, err := yaml.Marshal(v) - if err != nil { - // Swallow errors inside of a template. - return "" - } - return strings.TrimSuffix(string(data), "\n") -} - -// fromYAML converts a YAML document into a map[string]interface{}. -// -// This is not a general-purpose YAML parser, and will not parse all valid -// YAML documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string into -// m["Error"] in the returned map. -func fromYAML(str string) map[string]interface{} { - m := map[string]interface{}{} - - if err := yaml.Unmarshal([]byte(str), &m); err != nil { - m["Error"] = err.Error() - } - return m -} - -// fromYAMLArray converts a YAML array into a []interface{}. -// -// This is not a general-purpose YAML parser, and will not parse all valid -// YAML documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string as -// the first and only item in the returned array. -func fromYAMLArray(str string) []interface{} { - a := []interface{}{} - - if err := yaml.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} - } - return a -} - -// toTOML takes an interface, marshals it to toml, and returns a string. It will -// always return a string, even on marshal error (empty string). -// -// This is designed to be called from a template. -func toTOML(v interface{}) string { - b := bytes.NewBuffer(nil) - e := toml.NewEncoder(b) - err := e.Encode(v) - if err != nil { - return err.Error() - } - return b.String() -} - -// toJSON takes an interface, marshals it to json, and returns a string. It will -// always return a string, even on marshal error (empty string). -// -// This is designed to be called from a template. -func toJSON(v interface{}) string { - data, err := json.Marshal(v) - if err != nil { - // Swallow errors inside of a template. - return "" - } - return string(data) -} - -// fromJSON converts a JSON document into a map[string]interface{}. -// -// This is not a general-purpose JSON parser, and will not parse all valid -// JSON documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string into -// m["Error"] in the returned map. -func fromJSON(str string) map[string]interface{} { - m := make(map[string]interface{}) - - if err := json.Unmarshal([]byte(str), &m); err != nil { - m["Error"] = err.Error() - } - return m -} - -// fromJSONArray converts a JSON array into a []interface{}. -// -// This is not a general-purpose JSON parser, and will not parse all valid -// JSON documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string as -// the first and only item in the returned array. -func fromJSONArray(str string) []interface{} { - a := []interface{}{} - - if err := json.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} - } - return a -} diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go deleted file mode 100644 index 29bc121b5..000000000 --- a/pkg/engine/funcs_test.go +++ /dev/null @@ -1,178 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "strings" - "testing" - "text/template" - - "github.com/stretchr/testify/assert" -) - -func TestFuncs(t *testing.T) { - //TODO write tests for failure cases - tests := []struct { - tpl, expect string - vars interface{} - }{{ - tpl: `{{ toYaml . }}`, - expect: `foo: bar`, - vars: map[string]interface{}{"foo": "bar"}, - }, { - tpl: `{{ toToml . }}`, - expect: "foo = \"bar\"\n", - vars: map[string]interface{}{"foo": "bar"}, - }, { - tpl: `{{ toJson . }}`, - expect: `{"foo":"bar"}`, - vars: map[string]interface{}{"foo": "bar"}, - }, { - tpl: `{{ fromYaml . }}`, - expect: "map[hello:world]", - vars: `hello: world`, - }, { - tpl: `{{ fromYamlArray . }}`, - expect: "[one 2 map[name:helm]]", - vars: "- one\n- 2\n- name: helm\n", - }, { - tpl: `{{ fromYamlArray . }}`, - expect: "[one 2 map[name:helm]]", - vars: `["one", 2, { "name": "helm" }]`, - }, { - // Regression for https://github.com/helm/helm/issues/2271 - tpl: `{{ toToml . }}`, - expect: "[mast]\n sail = \"white\"\n", - vars: map[string]map[string]string{"mast": {"sail": "white"}}, - }, { - tpl: `{{ fromYaml . }}`, - expect: "map[Error:error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go value of type map[string]interface {}]", - vars: "- one\n- two\n", - }, { - tpl: `{{ fromJson .}}`, - expect: `map[hello:world]`, - vars: `{"hello":"world"}`, - }, { - tpl: `{{ fromJson . }}`, - expect: `map[Error:json: cannot unmarshal array into Go value of type map[string]interface {}]`, - vars: `["one", "two"]`, - }, { - tpl: `{{ fromJsonArray . }}`, - expect: `[one 2 map[name:helm]]`, - vars: `["one", 2, { "name": "helm" }]`, - }, { - tpl: `{{ fromJsonArray . }}`, - expect: `[json: cannot unmarshal object into Go value of type []interface {}]`, - vars: `{"hello": "world"}`, - }, { - tpl: `{{ merge .dict (fromYaml .yaml) }}`, - expect: `map[a:map[b:c]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, - }, { - tpl: `{{ merge (fromYaml .yaml) .dict }}`, - expect: `map[a:map[b:d]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, - }, { - tpl: `{{ fromYaml . }}`, - expect: `map[Error:error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go value of type map[string]interface {}]`, - vars: `["one", "two"]`, - }, { - tpl: `{{ fromYamlArray . }}`, - expect: `[error unmarshaling JSON: while decoding JSON: json: cannot unmarshal object into Go value of type []interface {}]`, - vars: `hello: world`, - }, { - // This should never result in a network lookup. Regression for #7955 - tpl: `{{ lookup "v1" "Namespace" "" "unlikelynamespace99999999" }}`, - expect: `map[]`, - vars: `["one", "two"]`, - }} - - for _, tt := range tests { - var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) - assert.NoError(t, err) - assert.Equal(t, tt.expect, b.String(), tt.tpl) - } -} - -// This test to check a function provided by sprig is due to a change in a -// dependency of sprig. mergo in v0.3.9 changed the way it merges and only does -// public fields (i.e. those starting with a capital letter). This test, from -// sprig, fails in the new version. This is a behavior change for mergo that -// impacts sprig and Helm users. This test will help us to not update to a -// version of mergo (even accidentally) that causes a breaking change. See -// sprig changelog and notes for more details. -// Note, Go modules assume semver is never broken. So, there is no way to tell -// the tooling to not update to a minor or patch version. `go install` could -// be used to accidentally update mergo. This test and message should catch -// the problem and explain why it's happening. -func TestMerge(t *testing.T) { - dict := map[string]interface{}{ - "src2": map[string]interface{}{ - "h": 10, - "i": "i", - "j": "j", - }, - "src1": map[string]interface{}{ - "a": 1, - "b": 2, - "d": map[string]interface{}{ - "e": "four", - }, - "g": []int{6, 7}, - "i": "aye", - "j": "jay", - "k": map[string]interface{}{ - "l": false, - }, - }, - "dst": map[string]interface{}{ - "a": "one", - "c": 3, - "d": map[string]interface{}{ - "f": 5, - }, - "g": []int{8, 9}, - "i": "eye", - "k": map[string]interface{}{ - "l": true, - }, - }, - } - tpl := `{{merge .dst .src1 .src2}}` - var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) - assert.NoError(t, err) - - expected := map[string]interface{}{ - "a": "one", // key overridden - "b": 2, // merged from src1 - "c": 3, // merged from dst - "d": map[string]interface{}{ // deep merge - "e": "four", - "f": 5, - }, - "g": []int{8, 9}, // overridden - arrays are not merged - "h": 10, // merged from src2 - "i": "eye", // overridden twice - "j": "jay", // overridden and merged - "k": map[string]interface{}{ - "l": true, // overridden - }, - } - assert.Equal(t, expected, dict["dst"]) -} diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go deleted file mode 100644 index d1bf1105a..000000000 --- a/pkg/engine/lookup_func.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package engine - -import ( - "context" - "log" - "strings" - - "github.com/pkg/errors" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" -) - -type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) - -// NewLookupFunction returns a function for looking up objects in the cluster. -// -// If the resource does not exist, no error is raised. -// -// This function is considered deprecated, and will be renamed in Helm 4. It will no -// longer be a public function. -func NewLookupFunction(config *rest.Config) lookupFunc { - return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) { - var client dynamic.ResourceInterface - c, namespaced, err := getDynamicClientOnKind(apiversion, resource, config) - if err != nil { - return map[string]interface{}{}, err - } - if namespaced && namespace != "" { - client = c.Namespace(namespace) - } else { - client = c - } - if name != "" { - // this will return a single object - obj, err := client.Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - // Just return an empty interface when the object was not found. - // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil - } - return map[string]interface{}{}, err - } - return obj.UnstructuredContent(), nil - } - // this will return a list - obj, err := client.List(context.Background(), metav1.ListOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - // Just return an empty interface when the object was not found. - // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil - } - return map[string]interface{}{}, err - } - return obj.UnstructuredContent(), nil - } -} - -// getDynamicClientOnUnstructured returns a dynamic client on an Unstructured type. This client can be further namespaced. -func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { - gvk := schema.FromAPIVersionAndKind(apiversion, kind) - apiRes, err := getAPIResourceForGVK(gvk, config) - if err != nil { - log.Printf("[ERROR] unable to get apiresource from unstructured: %s , error %s", gvk.String(), err) - return nil, false, errors.Wrapf(err, "unable to get apiresource from unstructured: %s", gvk.String()) - } - gvr := schema.GroupVersionResource{ - Group: apiRes.Group, - Version: apiRes.Version, - Resource: apiRes.Name, - } - intf, err := dynamic.NewForConfig(config) - if err != nil { - log.Printf("[ERROR] unable to get dynamic client %s", err) - return nil, false, err - } - res := intf.Resource(gvr) - return res, apiRes.Namespaced, nil -} - -func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (metav1.APIResource, error) { - res := metav1.APIResource{} - discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) - if err != nil { - log.Printf("[ERROR] unable to create discovery client %s", err) - return res, err - } - resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) - if err != nil { - log.Printf("[ERROR] unable to retrieve resource list for: %s , error: %s", gvk.GroupVersion().String(), err) - return res, err - } - for _, resource := range resList.APIResources { - // if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now. - if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") { - res = resource - res.Group = gvk.Group - res.Version = gvk.Version - break - } - } - return res, nil -} diff --git a/pkg/gates/doc.go b/pkg/gates/doc.go deleted file mode 100644 index 762fdb8c6..000000000 --- a/pkg/gates/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 gates provides a general tool for working with experimental feature gates. - -This provides convenience methods where the user can determine if certain experimental features are enabled. -*/ -package gates diff --git a/pkg/gates/gates.go b/pkg/gates/gates.go deleted file mode 100644 index 69559219e..000000000 --- a/pkg/gates/gates.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -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 gates - -import ( - "fmt" - "os" -) - -// Gate is the name of the feature gate. -type Gate string - -// String returns the string representation of this feature gate. -func (g Gate) String() string { - return string(g) -} - -// IsEnabled determines whether a certain feature gate is enabled. -func (g Gate) IsEnabled() bool { - return os.Getenv(string(g)) != "" -} - -func (g Gate) Error() error { - return fmt.Errorf("this feature has been marked as experimental and is not enabled by default. Please set %s=1 in your environment to use this feature", g.String()) -} diff --git a/pkg/gates/gates_test.go b/pkg/gates/gates_test.go deleted file mode 100644 index 6bdd17ed6..000000000 --- a/pkg/gates/gates_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -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 gates - -import ( - "os" - "testing" -) - -const name string = "HELM_EXPERIMENTAL_FEATURE" - -func TestIsEnabled(t *testing.T) { - os.Unsetenv(name) - g := Gate(name) - - if g.IsEnabled() { - t.Errorf("feature gate shows as available, but the environment variable %s was not set", name) - } - - os.Setenv(name, "1") - - if !g.IsEnabled() { - t.Errorf("feature gate shows as disabled, but the environment variable %s was set", name) - } -} - -func TestError(t *testing.T) { - os.Unsetenv(name) - g := Gate(name) - - if g.Error().Error() != "this feature has been marked as experimental and is not enabled by default. Please set HELM_EXPERIMENTAL_FEATURE=1 in your environment to use this feature" { - t.Errorf("incorrect error message. Received %s", g.Error().Error()) - } -} - -func TestString(t *testing.T) { - os.Unsetenv(name) - g := Gate(name) - - if g.String() != "HELM_EXPERIMENTAL_FEATURE" { - t.Errorf("incorrect string representation. Received %s", g.String()) - } -} diff --git a/pkg/getter/doc.go b/pkg/getter/doc.go deleted file mode 100644 index c53ef1ae0..000000000 --- a/pkg/getter/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -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 diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go deleted file mode 100644 index 653b032fe..000000000 --- a/pkg/getter/getter.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -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" - "net/http" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -// 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 - unTar bool - insecureSkipVerifyTLS bool - username string - password string - passCredentialsAll bool - userAgent string - version string - registryClient *registry.Client - timeout time.Duration - transport *http.Transport -} - -// 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 - } -} - -func WithPassCredentialsAll(pass bool) Option { - return func(opts *options) { - opts.passCredentialsAll = pass - } -} - -// 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 - } -} - -func WithTagName(tagname string) Option { - return func(opts *options) { - opts.version = tagname - } -} - -func WithRegistryClient(client *registry.Client) Option { - return func(opts *options) { - opts.registryClient = client - } -} - -func WithUntar() Option { - return func(opts *options) { - opts.unTar = true - } -} - -// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. -func WithTransport(transport *http.Transport) Option { - return func(opts *options) { - opts.transport = transport - } -} - -// 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, -} - -var ociProvider = Provider{ - Schemes: []string{registry.OCIScheme}, - New: NewOCIGetter, -} - -// 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, ociProvider} - pluginDownloaders, _ := collectPlugins(settings) - result = append(result, pluginDownloaders...) - return result -} diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go deleted file mode 100644 index ab14784ab..000000000 --- a/pkg/getter/getter_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package getter - -import ( - "testing" - - "helm.sh/helm/v3/pkg/cli" -) - -const pluginDir = "testdata/plugins" - -func TestProvider(t *testing.T) { - p := Provider{ - []string{"one", "three"}, - func(_ ...Option) (Getter, error) { return nil, nil }, - } - - if !p.Provides("three") { - t.Error("Expected provider to provide three") - } -} - -func TestProviders(t *testing.T) { - ps := Providers{ - {[]string{"one", "three"}, func(_ ...Option) (Getter, error) { return nil, nil }}, - {[]string{"two", "four"}, func(_ ...Option) (Getter, error) { return nil, nil }}, - } - - if _, err := ps.ByScheme("one"); err != nil { - t.Error(err) - } - if _, err := ps.ByScheme("four"); err != nil { - t.Error(err) - } - - if _, err := ps.ByScheme("five"); err == nil { - t.Error("Did not expect handler for five") - } -} - -func TestAll(t *testing.T) { - env := cli.New() - env.PluginsDirectory = pluginDir - - all := All(env) - if len(all) != 4 { - t.Errorf("expected 4 providers (default plus three plugins), got %d", len(all)) - } - - if _, err := all.ByScheme("test2"); err != nil { - t.Error(err) - } -} - -func TestByScheme(t *testing.T) { - env := cli.New() - env.PluginsDirectory = pluginDir - - g := All(env) - if _, err := g.ByScheme("test"); err != nil { - t.Error(err) - } - if _, err := g.ByScheme("https"); err != nil { - t.Error(err) - } -} diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go deleted file mode 100644 index 081bf059e..000000000 --- a/pkg/getter/httpgetter.go +++ /dev/null @@ -1,157 +0,0 @@ -/* -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" - "net/url" - "sync" - - "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 - transport *http.Transport - once sync.Once -} - -// 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) { - // 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(http.MethodGet, href, nil) - if err != nil { - return nil, err - } - - req.Header.Set("User-Agent", version.GetUserAgent()) - if g.opts.userAgent != "" { - req.Header.Set("User-Agent", g.opts.userAgent) - } - - // Before setting the basic auth credentials, make sure the URL associated - // with the basic auth is the one being fetched. - u1, err := url.Parse(g.opts.url) - if err != nil { - return nil, errors.Wrap(err, "Unable to parse getter URL") - } - u2, err := url.Parse(href) - if err != nil { - return nil, errors.Wrap(err, "Unable to parse URL getting from") - } - - // Host on URL (returned from url.Parse) contains the port if present. - // This check ensures credentials are not passed between different - // services on different ports. - if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { - 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 nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, errors.Errorf("failed to fetch %s : %s", href, resp.Status) - } - - buf := bytes.NewBuffer(nil) - _, err = io.Copy(buf, resp.Body) - 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) { - if g.opts.transport != nil { - return &http.Client{ - Transport: g.opts.transport, - Timeout: g.opts.timeout, - }, nil - } - - g.once.Do(func() { - g.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") - } - - sni, err := urlutil.ExtractHostname(g.opts.url) - if err != nil { - return nil, err - } - tlsConf.ServerName = sni - - g.transport.TLSClientConfig = tlsConf - } - - if g.opts.insecureSkipVerifyTLS { - if g.transport.TLSClientConfig == nil { - g.transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } else { - g.transport.TLSClientConfig.InsecureSkipVerify = true - } - } - - client := &http.Client{ - Transport: g.transport, - Timeout: g.opts.timeout, - } - - return client, nil -} diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go deleted file mode 100644 index 5a90d8b99..000000000 --- a/pkg/getter/httpgetter_test.go +++ /dev/null @@ -1,531 +0,0 @@ -/* -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 ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/cli" -) - -func TestHTTPGetter(t *testing.T) { - g, err := NewHTTPGetter(WithURL("http://example.com")) - if err != nil { - t.Fatal(err) - } - - if _, ok := g.(*HTTPGetter); !ok { - t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter") - } - - cd := "../../testdata" - join := filepath.Join - ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem") - insecure := false - timeout := time.Second * 5 - transport := &http.Transport{} - - // Test with options - g, err = NewHTTPGetter( - WithBasicAuth("I", "Am"), - WithPassCredentialsAll(false), - WithUserAgent("Groot"), - WithTLSClientConfig(pub, priv, ca), - WithInsecureSkipVerifyTLS(insecure), - WithTimeout(timeout), - WithTransport(transport), - ) - if err != nil { - t.Fatal(err) - } - - hg, ok := g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - - if hg.opts.username != "I" { - t.Errorf("Expected NewHTTPGetter to contain %q as the username, got %q", "I", hg.opts.username) - } - - if hg.opts.password != "Am" { - t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password) - } - - if hg.opts.passCredentialsAll != false { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) - } - - if hg.opts.userAgent != "Groot" { - t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent) - } - - if hg.opts.certFile != pub { - t.Errorf("Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile) - } - - if hg.opts.keyFile != priv { - t.Errorf("Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile) - } - - if hg.opts.caFile != ca { - t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile) - } - - if hg.opts.insecureSkipVerifyTLS != insecure { - t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS) - } - - if hg.opts.timeout != timeout { - t.Errorf("Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout) - } - - if hg.opts.transport != transport { - t.Errorf("Expected NewHTTPGetter to contain %p as Transport, got %p", transport, hg.opts.transport) - } - - // Test if setting insecureSkipVerifyTLS is being passed to the ops - insecure = true - - g, err = NewHTTPGetter( - WithInsecureSkipVerifyTLS(insecure), - ) - if err != nil { - t.Fatal(err) - } - - hg, ok = g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - - if hg.opts.insecureSkipVerifyTLS != insecure { - t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS) - } - - // Checking false by default - if hg.opts.passCredentialsAll != false { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) - } - - // Test setting PassCredentialsAll - g, err = NewHTTPGetter( - WithBasicAuth("I", "Am"), - WithPassCredentialsAll(true), - ) - if err != nil { - t.Fatal(err) - } - - hg, ok = g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - if hg.opts.passCredentialsAll != true { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", true, hg.opts.passCredentialsAll) - } -} - -func TestDownload(t *testing.T) { - expect := "Call me Ishmael" - expectedUserAgent := "I am Groot" - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v") - if r.UserAgent() != defaultUserAgent { - t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent()) - } - fmt.Fprint(w, expect) - })) - defer srv.Close() - - g, err := All(cli.New()).ByScheme("http") - if err != nil { - t.Fatal(err) - } - got, err := g.Get(srv.URL, WithURL(srv.URL)) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } - - // test with http server - basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - if r.UserAgent() != expectedUserAgent { - t.Errorf("Expected '%s', got '%s'", expectedUserAgent, r.UserAgent()) - } - fmt.Fprint(w, expect) - })) - - defer basicAuthSrv.Close() - - u, _ := url.ParseRequestURI(basicAuthSrv.URL) - httpgetter, err := NewHTTPGetter( - WithURL(u.String()), - WithBasicAuth("username", "password"), - WithPassCredentialsAll(false), - WithUserAgent(expectedUserAgent), - ) - if err != nil { - t.Fatal(err) - } - got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } - - // test with Get URL differing from withURL - crossAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if ok || username == "username" || password == "password" { - t.Errorf("Expected request to not include but got '%v', '%s', '%s'", ok, username, password) - } - fmt.Fprint(w, expect) - })) - - defer crossAuthSrv.Close() - - u, _ = url.ParseRequestURI(crossAuthSrv.URL) - - // A different host is provided for the WithURL from the one used for Get - u2, _ := url.ParseRequestURI(crossAuthSrv.URL) - host := strings.Split(u2.Host, ":") - host[0] = host[0] + "a" - u2.Host = strings.Join(host, ":") - httpgetter, err = NewHTTPGetter( - WithURL(u2.String()), - WithBasicAuth("username", "password"), - WithPassCredentialsAll(false), - ) - if err != nil { - t.Fatal(err) - } - got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } - - // test with Get URL differing from withURL and should pass creds - crossAuthSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - fmt.Fprint(w, expect) - })) - - defer crossAuthSrv.Close() - - u, _ = url.ParseRequestURI(crossAuthSrv.URL) - - // A different host is provided for the WithURL from the one used for Get - u2, _ = url.ParseRequestURI(crossAuthSrv.URL) - host = strings.Split(u2.Host, ":") - host[0] = host[0] + "a" - u2.Host = strings.Join(host, ":") - httpgetter, err = NewHTTPGetter( - WithURL(u2.String()), - WithBasicAuth("username", "password"), - WithPassCredentialsAll(true), - ) - if err != nil { - t.Fatal(err) - } - got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } -} - -func TestDownloadTLS(t *testing.T) { - cd := "../../testdata" - ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") - - tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca) - if err != nil { - t.Fatal(errors.Wrap(err, "can't create TLS config for client")) - } - tlsConf.ServerName = "helm.sh" - tlsSrv.TLS = tlsConf - tlsSrv.StartTLS() - defer tlsSrv.Close() - - u, _ := url.ParseRequestURI(tlsSrv.URL) - g, err := NewHTTPGetter( - WithURL(u.String()), - WithTLSClientConfig(pub, priv, ca), - ) - if err != nil { - t.Fatal(err) - } - - if _, err := g.Get(u.String()); err != nil { - t.Error(err) - } - - // now test with TLS config being passed along in .Get (see #6635) - g, err = NewHTTPGetter() - if err != nil { - t.Fatal(err) - } - - if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil { - t.Error(err) - } - - // test with only the CA file (see also #6635) - g, err = NewHTTPGetter() - if err != nil { - t.Fatal(err) - } - - if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)); err != nil { - t.Error(err) - } -} - -func TestDownloadInsecureSkipTLSVerify(t *testing.T) { - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - defer ts.Close() - - u, _ := url.ParseRequestURI(ts.URL) - - // Ensure the default behavior did not change - g, err := NewHTTPGetter( - WithURL(u.String()), - ) - if err != nil { - t.Error(err) - } - - if _, err := g.Get(u.String()); err == nil { - t.Errorf("Expected Getter to throw an error, got %s", err) - } - - // Test certificate check skip - g, err = NewHTTPGetter( - WithURL(u.String()), - WithInsecureSkipVerifyTLS(true), - ) - if err != nil { - t.Error(err) - } - if _, err = g.Get(u.String()); err != nil { - t.Error(err) - } - -} - -func TestHTTPGetterTarDownload(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - f, _ := os.Open("testdata/empty-0.0.1.tgz") - defer f.Close() - - b := make([]byte, 512) - f.Read(b) - //Get the file size - FileStat, _ := f.Stat() - FileSize := strconv.FormatInt(FileStat.Size(), 10) - - //Simulating improper header values from bitbucket - w.Header().Set("Content-Type", "application/x-tar") - w.Header().Set("Content-Encoding", "gzip") - w.Header().Set("Content-Length", FileSize) - - f.Seek(0, 0) - io.Copy(w, f) - })) - - defer srv.Close() - - g, err := NewHTTPGetter(WithURL(srv.URL)) - if err != nil { - t.Fatal(err) - } - - data, _ := g.Get(srv.URL) - mimeType := http.DetectContentType(data.Bytes()) - - expectedMimeType := "application/x-gzip" - if mimeType != expectedMimeType { - t.Fatalf("Expected response with MIME type %s, but got %s", expectedMimeType, mimeType) - } -} - -func TestHttpClientInsecureSkipVerify(t *testing.T) { - g := HTTPGetter{} - g.opts.url = "https://localhost" - verifyInsecureSkipVerify(t, &g, "Blank HTTPGetter", false) - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.caFile = "testdata/ca.crt" - verifyInsecureSkipVerify(t, &g, "HTTPGetter with ca file", false) - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.insecureSkipVerifyTLS = true - verifyInsecureSkipVerify(t, &g, "HTTPGetter with skip cert verification only", true) - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.certFile = "testdata/client.crt" - g.opts.keyFile = "testdata/client.key" - g.opts.insecureSkipVerifyTLS = true - transport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", true) - if len(transport.TLSClientConfig.Certificates) <= 0 { - t.Fatal("transport.TLSClientConfig.Certificates is not present") - } - if transport.TLSClientConfig.ServerName == "" { - t.Fatal("TLSClientConfig.ServerName is blank") - } -} - -func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { - returnVal, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if returnVal == nil { - t.Fatalf("Expected non nil value for http client") - } - transport := (returnVal.Transport).(*http.Transport) - gotValue := false - if transport.TLSClientConfig != nil { - gotValue = transport.TLSClientConfig.InsecureSkipVerify - } - if gotValue != expectedValue { - t.Fatalf("Case Name = %s\nInsecureSkipVerify did not come as expected. Expected = %t; Got = %v", - caseName, expectedValue, gotValue) - } - return transport -} - -func TestDefaultHTTPTransportReuse(t *testing.T) { - g := HTTPGetter{} - - httpClient1, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient1 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport1 := (httpClient1.Transport).(*http.Transport) - - httpClient2, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient2 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport2 := (httpClient2.Transport).(*http.Transport) - - if transport1 != transport2 { - t.Fatalf("Expected default transport to be reused") - } -} - -func TestHTTPTransportOption(t *testing.T) { - transport := &http.Transport{} - - g := HTTPGetter{} - g.opts.transport = transport - httpClient1, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient1 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport1 := (httpClient1.Transport).(*http.Transport) - - if transport1 != transport { - t.Fatalf("Expected transport option to be applied") - } - - httpClient2, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient2 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport2 := (httpClient2.Transport).(*http.Transport) - - if transport1 != transport2 { - t.Fatalf("Expected applied transport to be reused") - } - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.certFile = "testdata/client.crt" - g.opts.keyFile = "testdata/client.key" - g.opts.insecureSkipVerifyTLS = true - g.opts.transport = transport - usedTransport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", false) - if usedTransport.TLSClientConfig != nil { - t.Fatal("transport.TLSClientConfig should not be set") - } -} diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go deleted file mode 100644 index 14f5cb3ec..000000000 --- a/pkg/getter/ocigetter.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -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" - "fmt" - "strings" - - "helm.sh/helm/v3/pkg/registry" -) - -// OCIGetter is the default HTTP(/S) backend handler -type OCIGetter struct { - opts options -} - -// Get performs a Get from repo.Getter and returns the body. -func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { - for _, opt := range options { - opt(&g.opts) - } - return g.get(href) -} - -func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { - client := g.opts.registryClient - - ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)) - - var pullOpts []registry.PullOption - requestingProv := strings.HasSuffix(ref, ".prov") - if requestingProv { - ref = strings.TrimSuffix(ref, ".prov") - pullOpts = append(pullOpts, - registry.PullOptWithChart(false), - registry.PullOptWithProv(true)) - } - - result, err := client.Pull(ref, pullOpts...) - if err != nil { - return nil, err - } - - if requestingProv { - return bytes.NewBuffer(result.Prov.Data), nil - } - return bytes.NewBuffer(result.Chart.Data), nil -} - -// NewOCIGetter constructs a valid http/https client as a Getter -func NewOCIGetter(ops ...Option) (Getter, error) { - registryClient, err := registry.NewClient( - registry.ClientOptEnableCache(true), - ) - if err != nil { - return nil, err - } - - client := OCIGetter{ - opts: options{ - registryClient: registryClient, - }, - } - - for _, opt := range ops { - opt(&client.opts) - } - - return &client, nil -} diff --git a/pkg/getter/ocigetter_test.go b/pkg/getter/ocigetter_test.go deleted file mode 100644 index fc548b7a6..000000000 --- a/pkg/getter/ocigetter_test.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package getter - -import ( - "testing" -) - -func TestNewOCIGetter(t *testing.T) { - testfn := func(ops *options) { - if ops.registryClient == nil { - t.Fatalf("the OCIGetter's registryClient should not be null") - } - } - - NewOCIGetter(testfn) -} diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go deleted file mode 100644 index 0d13ade57..000000000 --- a/pkg/getter/plugingetter.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -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 - } -} diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go deleted file mode 100644 index a18fa302b..000000000 --- a/pkg/getter/plugingetter_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -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 ( - "runtime" - "strings" - "testing" - - "helm.sh/helm/v3/pkg/cli" -) - -func TestCollectPlugins(t *testing.T) { - env := cli.New() - env.PluginsDirectory = pluginDir - - p, err := collectPlugins(env) - if err != nil { - t.Fatal(err) - } - - if len(p) != 2 { - t.Errorf("Expected 2 plugins, got %d: %v", len(p), p) - } - - if _, err := p.ByScheme("test2"); err != nil { - t.Error(err) - } - - if _, err := p.ByScheme("test"); err != nil { - t.Error(err) - } - - if _, err := p.ByScheme("nosuchthing"); err == nil { - t.Fatal("did not expect protocol handler for nosuchthing") - } -} - -func TestPluginGetter(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("TODO: refactor this test to work on windows") - } - - env := cli.New() - env.PluginsDirectory = pluginDir - pg := NewPluginGetter("echo", env, "test", ".") - g, err := pg() - if err != nil { - t.Fatal(err) - } - - data, err := g.Get("test://foo/bar") - if err != nil { - t.Fatal(err) - } - - expect := "test://foo/bar" - got := strings.TrimSpace(data.String()) - if got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } -} - -func TestPluginSubCommands(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("TODO: refactor this test to work on windows") - } - - env := cli.New() - env.PluginsDirectory = pluginDir - - pg := NewPluginGetter("echo -n", env, "test", ".") - g, err := pg() - if err != nil { - t.Fatal(err) - } - - data, err := g.Get("test://foo/bar") - if err != nil { - t.Fatal(err) - } - - expect := " test://foo/bar" - got := data.String() - if got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } -} diff --git a/pkg/getter/testdata/ca.crt b/pkg/getter/testdata/ca.crt deleted file mode 100644 index c17820085..000000000 --- a/pkg/getter/testdata/ca.crt +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEJDCCAwygAwIBAgIUcGE5xyj7IH7sZLntsHKxZHCd3awwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCSU4xDzANBgNVBAgMBktlcmFsYTEOMAwGA1UEBwwFS29j -aGkxGDAWBgNVBAoMD2NoYXJ0bXVzZXVtLmNvbTEXMBUGA1UEAwwOY2hhcnRtdXNl -dW1fY2EwIBcNMjAxMjA0MDkxMjU4WhgPMjI5NDA5MTkwOTEyNThaMGExCzAJBgNV -BAYTAklOMQ8wDQYDVQQIDAZLZXJhbGExDjAMBgNVBAcMBUtvY2hpMRgwFgYDVQQK -DA9jaGFydG11c2V1bS5jb20xFzAVBgNVBAMMDmNoYXJ0bXVzZXVtX2NhMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqQJi/BRWzaXlkDP48kUAWgaLtD0Y -72E30WBZDAw3S+BaYulRk1LWK1QM+ALiZQb1a6YgNvuERyywOv45pZaC2xtP6Bju -+59kwBrEtNCTNa2cSqs0hSw6NCDe+K8lpFKlTdh4c5sAkiDkMBr1R6uu7o4HvfO0 -iGMZ9VUdrbf4psZIyPVRdt/sAkAKqbjQfxr6VUmMktrZNND+mwPgrhS2kPL4P+JS -zpxgpkuSUvg5DvJuypmCI0fDr6GwshqXM1ONHE0HT8MEVy1xZj9rVHt7sgQhjBX1 -PsFySZrq1lSz8R864c1l+tCGlk9+1ldQjc9tBzdvCjJB+nYfTTpBUk/VKwIDAQAB -o4HRMIHOMB0GA1UdDgQWBBSv1IMZGHWsZVqJkJoPDzVLMcUivjCBngYDVR0jBIGW -MIGTgBSv1IMZGHWsZVqJkJoPDzVLMcUivqFlpGMwYTELMAkGA1UEBhMCSU4xDzAN -BgNVBAgMBktlcmFsYTEOMAwGA1UEBwwFS29jaGkxGDAWBgNVBAoMD2NoYXJ0bXVz -ZXVtLmNvbTEXMBUGA1UEAwwOY2hhcnRtdXNldW1fY2GCFHBhOcco+yB+7GS57bBy -sWRwnd2sMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAI6Fg9F8cjB9 -2jJn1vZPpynSFs7XPlUBVh0YXBt+o6g7+nKInwFBPzPEQ7ZZotz3GIe4I7wYiQAn -c6TU2nnqK+9TLbJIyv6NOfikLgwrTy+dAW8wrOiu+IIzA8Gdy8z8m3B7v9RUYVhx -zoNoqCEvOIzCZKDH68PZDJrDVSuvPPK33Ywj3zxYeDNXU87BKGER0vjeVG4oTAcQ -hKJURh4IRy/eW9NWiFqvNgst7k5MldOgLIOUBh1faaxlWkjuGpfdr/EBAAr491S5 -IPFU7TopsrgANnxldSzVbcgfo2nt0A976T3xZQHy3xpk1rIt55xVzT0W55NRAc7v -+9NTUOB10so= ------END CERTIFICATE----- diff --git a/pkg/getter/testdata/client.crt b/pkg/getter/testdata/client.crt deleted file mode 100644 index f005f401d..000000000 --- a/pkg/getter/testdata/client.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDejCCAmKgAwIBAgIUfSn63/ldeo1prOaxXV8I0Id6HTEwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCSU4xDzANBgNVBAgMBktlcmFsYTEOMAwGA1UEBwwFS29j -aGkxGDAWBgNVBAoMD2NoYXJ0bXVzZXVtLmNvbTEXMBUGA1UEAwwOY2hhcnRtdXNl -dW1fY2EwIBcNMjAxMjA0MDkxMzIwWhgPMjI5NDA5MTkwOTEzMjBaMFwxCzAJBgNV -BAYTAklOMQ8wDQYDVQQIDAZLZXJhbGExDjAMBgNVBAcMBUtvY2hpMRgwFgYDVQQK -DA9jaGFydG11c2V1bS5jb20xEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKeCbADaK+7yrM9rQszF54334mGoSXbXY6Ca -7FKdkgmKCjeeqZ+lr+i+6WQ+O+Tn0dhlyHier42IqUw5Rzzegvl7QrhiChd8C6sW -pEqDK7Z1U+cv9gIabYd+qWDwFw67xiMNQfdZxwI/AgPzixlfsMw3ZNKM3Q0Vxtdz -EEYdEDDNgZ34Cj+KXCPpYDi2i5hZnha4wzIfbL3+z2o7sPBBLBrrsOtPdVVkxysN -HM4h7wp7w7QyOosndFvcTaX7yRA1ka0BoulCt2wdVc2ZBRPiPMySi893VCQ8zeHP -QMFDL3rGmKVLbP1to2dgf9ZgckMEwE8chm2D8Ls87F9tsK9fVlUCAwEAAaMtMCsw -EwYDVR0lBAwwCgYIKwYBBQUHAwIwFAYDVR0RBA0wC4IJMTI3LjAuMC4xMA0GCSqG -SIb3DQEBCwUAA4IBAQCi7z5U9J5DkM6eYzyyH/8p32Azrunw+ZpwtxbKq3xEkpcX -0XtbyTG2szegKF0eLr9NizgEN8M1nvaMO1zuxFMB6tCWO/MyNWH/0T4xvFnnVzJ4 -OKlGSvyIuMW3wofxCLRG4Cpw750iWpJ0GwjTOu2ep5tbnEMC5Ueg55WqCAE/yDrd -nL1wZSGXy1bj5H6q8EM/4/yrzK80QkfdpbDR0NGkDO2mmAKL8d57NuASWljieyV3 -Ty5C8xXw5jF2JIESvT74by8ufozUOPKmgRqySgEPgAkNm0s5a05KAi5Cpyxgdylm -CEvjni1LYGhJp9wXucF9ehKSdsw4qn9T5ire8YfI ------END CERTIFICATE----- diff --git a/pkg/getter/testdata/client.key b/pkg/getter/testdata/client.key deleted file mode 100644 index 4f676ba42..000000000 --- a/pkg/getter/testdata/client.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAp4JsANor7vKsz2tCzMXnjffiYahJdtdjoJrsUp2SCYoKN56p -n6Wv6L7pZD475OfR2GXIeJ6vjYipTDlHPN6C+XtCuGIKF3wLqxakSoMrtnVT5y/2 -Ahpth36pYPAXDrvGIw1B91nHAj8CA/OLGV+wzDdk0ozdDRXG13MQRh0QMM2BnfgK -P4pcI+lgOLaLmFmeFrjDMh9svf7Pajuw8EEsGuuw6091VWTHKw0cziHvCnvDtDI6 -iyd0W9xNpfvJEDWRrQGi6UK3bB1VzZkFE+I8zJKLz3dUJDzN4c9AwUMvesaYpUts -/W2jZ2B/1mByQwTATxyGbYPwuzzsX22wr19WVQIDAQABAoIBABw7qUSDgUAm+uWC -6KFnAd4115wqJye2qf4Z3pcWI9UjxREW1vQnkvyhoOjabHHqeL4GecGKzYAHdrF4 -Pf+OaXjvQ5GcRKMsrzLJACvm6+k24UtoFAjKt4dM2/OQw/IhyAWEaIfuQ9KnGAne -dKV0MXJaK84pG+DmuLr7k9SddWskElEyxK2j0tvdyI5byRfjf5schac9M4i5ZAYV -pT+PuXZQh8L8GEY2koE+uEMpXGOstD7yUxyV8zHFyBC7FVDkqF4S8IWY+RXQtVd6 -l8B8dRLjKSLBKDB+neStepcwNUyCDYiqyqsKfN7eVHDd0arPm6LeTuAsHKBw2OoN -YdAmUUkCgYEA0vb9mxsMgr0ORTZ14vWghz9K12oKPk9ajYuLTQVn8GQazp0XTIi5 -Mil2I78Qj87ApzGqOyHbkEgpg0C9/mheYLOYNHC4of83kNF+pHbDi1TckwxIaIK0 -rZLb3Az3zZQ2rAWZ2IgSyoeVO9RxYK/RuvPFp+UBeucuXiYoI0YlEXcCgYEAy0Sk -LTiYuLsnk21RIFK01iq4Y+4112K1NGTKu8Wm6wPaPsnLznP6339cEkbiSgbRgERE -jgOfa/CiSw5CVT9dWZuQ3OoQ83pMRb7IB0TobPmhBS/HQZ8Ocbfb6PnxQ3o1Bx7I -QuIpZFxzuTP80p1p2DMDxEl+r/DCvT/wgBKX6ZMCgYAdw1bYMSK8tytyPFK5aGnz -asyGQ6GaVNuzqIJIpYCae6UEjUkiNQ/bsdnHBUey4jpv3CPmH8q4OlYQ/GtRnyvh -fLT2gQirYjRWrBev4EmKOLi9zjfQ9s/CxTtbekDjsgtcjZW85MWx6Rr2y+wK9gMi -2w2BuF9TFZaHFd8Hyvej1QKBgAoFbU6pbqYU3AOhrRE54p54ZrTOhqsCu8pEedY+ -DVeizfyweDLKdwDTx5dDFV7u7R80vmh99zscFvQ6VLzdLd4AFGk/xOwsCFyb5kKt -fAP7Xpvh2iH7FHw4w0e+Is3f1YNvWhIqEj5XbIEh9gHwLsqw4SupL+y+ousvnszB -nemvAoGBAJa7bYG8MMCFJ4OFAmkpgQzHSzq7dzOR6O4GKsQQhiZ/0nRK5l3sLcDO -9viuRfhRepJGbcQ/Hw0AVIRWU01y4mejbuxfUE/FgWBoBBvpbot2zfuJgeFAIvkY -iFsZwuxPQUFobTu2hj6gh0gOKj/LpNXHkZGbZ2zTXmK3GDYlf6bR ------END RSA PRIVATE KEY----- diff --git a/pkg/getter/testdata/empty-0.0.1.tgz b/pkg/getter/testdata/empty-0.0.1.tgz deleted file mode 100644 index 6c4c3d205..000000000 Binary files a/pkg/getter/testdata/empty-0.0.1.tgz and /dev/null differ diff --git a/pkg/getter/testdata/plugins/testgetter/get.sh b/pkg/getter/testdata/plugins/testgetter/get.sh deleted file mode 100755 index cdd992369..000000000 --- a/pkg/getter/testdata/plugins/testgetter/get.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -echo ENVIRONMENT -env - -echo "" -echo ARGUMENTS -echo $@ diff --git a/pkg/getter/testdata/plugins/testgetter/plugin.yaml b/pkg/getter/testdata/plugins/testgetter/plugin.yaml deleted file mode 100644 index d1b929e3f..000000000 --- a/pkg/getter/testdata/plugins/testgetter/plugin.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: "testgetter" -version: "0.1.0" -usage: "Fetch a package from a test:// source" -description: |- - Print the environment that the plugin was given, then exit. - - This registers the test:// protocol. - -command: "$HELM_PLUGIN_DIR/get.sh" -ignoreFlags: true -downloaders: -#- command: "$HELM_PLUGIN_DIR/get.sh" -- command: "echo" - protocols: - - "test" diff --git a/pkg/getter/testdata/plugins/testgetter2/get.sh b/pkg/getter/testdata/plugins/testgetter2/get.sh deleted file mode 100755 index cdd992369..000000000 --- a/pkg/getter/testdata/plugins/testgetter2/get.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -echo ENVIRONMENT -env - -echo "" -echo ARGUMENTS -echo $@ diff --git a/pkg/getter/testdata/plugins/testgetter2/plugin.yaml b/pkg/getter/testdata/plugins/testgetter2/plugin.yaml deleted file mode 100644 index f1a527ef9..000000000 --- a/pkg/getter/testdata/plugins/testgetter2/plugin.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: "testgetter2" -version: "0.1.0" -usage: "Fetch a different package from a test2:// source" -description: "Handle test2 scheme" -command: "$HELM_PLUGIN_DIR/get.sh" -ignoreFlags: true -downloaders: -- command: "echo" - protocols: - - "test2" diff --git a/pkg/getter/testdata/repository/local/index.yaml b/pkg/getter/testdata/repository/local/index.yaml deleted file mode 100644 index efcf30c21..000000000 --- a/pkg/getter/testdata/repository/local/index.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v1 -entries: {} -generated: 2017-04-28T12:34:38.900985501-06:00 diff --git a/pkg/getter/testdata/repository/repositories.yaml b/pkg/getter/testdata/repository/repositories.yaml deleted file mode 100644 index 14ae6a8eb..000000000 --- a/pkg/getter/testdata/repository/repositories.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -generated: 2017-04-28T12:34:38.551693035-06:00 -repositories: -- caFile: "" - cache: repository/cache/stable-index.yaml - certFile: "" - keyFile: "" - name: stable - url: https://charts.helm.sh/stable -- caFile: "" - cache: repository/cache/local-index.yaml - certFile: "" - keyFile: "" - name: local - url: http://127.0.0.1:8879/charts diff --git a/pkg/helmpath/home.go b/pkg/helmpath/home.go deleted file mode 100644 index bd43e8890..000000000 --- a/pkg/helmpath/home.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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" -} diff --git a/pkg/helmpath/home_unix_test.go b/pkg/helmpath/home_unix_test.go deleted file mode 100644 index 977002549..000000000 --- a/pkg/helmpath/home_unix_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// 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. - -//go:build !windows - -package helmpath - -import ( - "os" - "runtime" - "testing" - - "helm.sh/helm/v3/pkg/helmpath/xdg" -) - -func TestHelmHome(t *testing.T) { - os.Setenv(xdg.CacheHomeEnvVar, "/cache") - os.Setenv(xdg.ConfigHomeEnvVar, "/config") - os.Setenv(xdg.DataHomeEnvVar, "/data") - isEq := func(t *testing.T, got, expected string) { - t.Helper() - if expected != got { - t.Error(runtime.GOOS) - t.Errorf("Expected %q, got %q", expected, got) - } - } - - isEq(t, CachePath(), "/cache/helm") - isEq(t, ConfigPath(), "/config/helm") - isEq(t, DataPath(), "/data/helm") - - // test to see if lazy-loading environment variables at runtime works - os.Setenv(xdg.CacheHomeEnvVar, "/cache2") - - isEq(t, CachePath(), "/cache2/helm") -} diff --git a/pkg/helmpath/home_windows_test.go b/pkg/helmpath/home_windows_test.go deleted file mode 100644 index 073e6347f..000000000 --- a/pkg/helmpath/home_windows_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// 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. - -//go:build windows - -package helmpath - -import ( - "os" - "testing" - - "helm.sh/helm/v3/pkg/helmpath/xdg" -) - -func TestHelmHome(t *testing.T) { - os.Setenv(xdg.CacheHomeEnvVar, "c:\\") - os.Setenv(xdg.ConfigHomeEnvVar, "d:\\") - os.Setenv(xdg.DataHomeEnvVar, "e:\\") - isEq := func(t *testing.T, a, b string) { - if a != b { - t.Errorf("Expected %q, got %q", b, a) - } - } - - isEq(t, CachePath(), "c:\\helm") - isEq(t, ConfigPath(), "d:\\helm") - isEq(t, DataPath(), "e:\\helm") - - // test to see if lazy-loading environment variables at runtime works - os.Setenv(xdg.CacheHomeEnvVar, "f:\\") - - isEq(t, CachePath(), "f:\\helm") -} diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go deleted file mode 100644 index 22d7bf0a1..000000000 --- a/pkg/helmpath/lazypath.go +++ /dev/null @@ -1,72 +0,0 @@ -// 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...)) -} diff --git a/pkg/helmpath/lazypath_darwin.go b/pkg/helmpath/lazypath_darwin.go deleted file mode 100644 index eba6dde15..000000000 --- a/pkg/helmpath/lazypath_darwin.go +++ /dev/null @@ -1,34 +0,0 @@ -// 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. - -//go: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") -} diff --git a/pkg/helmpath/lazypath_darwin_test.go b/pkg/helmpath/lazypath_darwin_test.go deleted file mode 100644 index d0503e0e1..000000000 --- a/pkg/helmpath/lazypath_darwin_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// 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. - -//go:build darwin - -package helmpath - -import ( - "os" - "path/filepath" - "testing" - - "k8s.io/client-go/util/homedir" - - "helm.sh/helm/v3/pkg/helmpath/xdg" -) - -const ( - appName = "helm" - testFile = "test.txt" - lazy = lazypath(appName) -) - -func TestDataPath(t *testing.T) { - os.Unsetenv(xdg.DataHomeEnvVar) - - expected := filepath.Join(homedir.HomeDir(), "Library", appName, testFile) - - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } - - os.Setenv(xdg.DataHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) - - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } -} - -func TestConfigPath(t *testing.T) { - os.Unsetenv(xdg.ConfigHomeEnvVar) - - expected := filepath.Join(homedir.HomeDir(), "Library", "Preferences", appName, testFile) - - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } - - os.Setenv(xdg.ConfigHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) - - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } -} - -func TestCachePath(t *testing.T) { - os.Unsetenv(xdg.CacheHomeEnvVar) - - expected := filepath.Join(homedir.HomeDir(), "Library", "Caches", appName, testFile) - - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } - - os.Setenv(xdg.CacheHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) - - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } -} diff --git a/pkg/helmpath/lazypath_unix.go b/pkg/helmpath/lazypath_unix.go deleted file mode 100644 index 82fb4b6f1..000000000 --- a/pkg/helmpath/lazypath_unix.go +++ /dev/null @@ -1,45 +0,0 @@ -// 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. - -//go: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") -} diff --git a/pkg/helmpath/lazypath_unix_test.go b/pkg/helmpath/lazypath_unix_test.go deleted file mode 100644 index 657982b2d..000000000 --- a/pkg/helmpath/lazypath_unix_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// 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. - -//go:build !windows && !darwin - -package helmpath - -import ( - "os" - "path/filepath" - "testing" - - "k8s.io/client-go/util/homedir" - - "helm.sh/helm/v3/pkg/helmpath/xdg" -) - -const ( - appName = "helm" - testFile = "test.txt" - lazy = lazypath(appName) -) - -func TestDataPath(t *testing.T) { - os.Unsetenv(xdg.DataHomeEnvVar) - - expected := filepath.Join(homedir.HomeDir(), ".local", "share", appName, testFile) - - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } - - os.Setenv(xdg.DataHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) - - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } -} - -func TestConfigPath(t *testing.T) { - os.Unsetenv(xdg.ConfigHomeEnvVar) - - expected := filepath.Join(homedir.HomeDir(), ".config", appName, testFile) - - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } - - os.Setenv(xdg.ConfigHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) - - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } -} - -func TestCachePath(t *testing.T) { - os.Unsetenv(xdg.CacheHomeEnvVar) - - expected := filepath.Join(homedir.HomeDir(), ".cache", appName, testFile) - - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } - - os.Setenv(xdg.CacheHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) - - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } -} diff --git a/pkg/helmpath/lazypath_windows.go b/pkg/helmpath/lazypath_windows.go deleted file mode 100644 index 230aee2a9..000000000 --- a/pkg/helmpath/lazypath_windows.go +++ /dev/null @@ -1,24 +0,0 @@ -// 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. - -//go: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") } diff --git a/pkg/helmpath/lazypath_windows_test.go b/pkg/helmpath/lazypath_windows_test.go deleted file mode 100644 index dedfd5720..000000000 --- a/pkg/helmpath/lazypath_windows_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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. - -//go:build windows - -package helmpath - -import ( - "os" - "path/filepath" - "testing" - - "k8s.io/client-go/util/homedir" - - "helm.sh/helm/v3/pkg/helmpath/xdg" -) - -const ( - appName = "helm" - testFile = "test.txt" - lazy = lazypath(appName) -) - -func TestDataPath(t *testing.T) { - os.Unsetenv(xdg.DataHomeEnvVar) - os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo")) - - expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) - - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } - - os.Setenv(xdg.DataHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) - - expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) - - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } -} - -func TestConfigPath(t *testing.T) { - os.Unsetenv(xdg.ConfigHomeEnvVar) - os.Setenv("APPDATA", filepath.Join(homedir.HomeDir(), "foo")) - - expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) - - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } - - os.Setenv(xdg.ConfigHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) - - expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) - - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } -} - -func TestCachePath(t *testing.T) { - os.Unsetenv(xdg.CacheHomeEnvVar) - os.Setenv("TEMP", filepath.Join(homedir.HomeDir(), "foo")) - - expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) - - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } - - os.Setenv(xdg.CacheHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) - - expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) - - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } -} diff --git a/pkg/helmpath/xdg/xdg.go b/pkg/helmpath/xdg/xdg.go deleted file mode 100644 index eaa3e6864..000000000 --- a/pkg/helmpath/xdg/xdg.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -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" -) diff --git a/pkg/kube/client.go b/pkg/kube/client.go deleted file mode 100644 index 5e75c34e4..000000000 --- a/pkg/kube/client.go +++ /dev/null @@ -1,674 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "context" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "sync" - "time" - - jsonpatch "github.com/evanphx/json-patch" - "github.com/pkg/errors" - batch "k8s.io/api/batch/v1" - v1 "k8s.io/api/core/v1" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - cachetools "k8s.io/client-go/tools/cache" - watchtools "k8s.io/client-go/tools/watch" - cmdutil "k8s.io/kubectl/pkg/cmd/util" -) - -// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. -var ErrNoObjectsVisited = errors.New("no objects visited") - -var metadataAccessor = meta.NewAccessor() - -// ManagedFieldsManager is the name of the manager of Kubernetes managedFields -// first introduced in Kubernetes 1.18 -var ManagedFieldsManager string - -// Client represents a client capable of communicating with the Kubernetes API. -type Client struct { - Factory Factory - Log func(string, ...interface{}) - // Namespace allows to bypass the kubeconfig file for the choice of the namespace - Namespace string - - kubeClient *kubernetes.Clientset -} - -var addToScheme sync.Once - -// New creates a new Client. -func New(getter genericclioptions.RESTClientGetter) *Client { - if getter == nil { - getter = genericclioptions.NewConfigFlags(true) - } - // Add CRDs to the scheme. They are missing by default. - addToScheme.Do(func() { - if err := apiextv1.AddToScheme(scheme.Scheme); err != nil { - // This should never happen. - panic(err) - } - if err := apiextv1beta1.AddToScheme(scheme.Scheme); err != nil { - panic(err) - } - }) - return &Client{ - Factory: cmdutil.NewFactory(getter), - Log: nopLogger, - } -} - -var nopLogger = func(_ string, _ ...interface{}) {} - -// getKubeClient get or create a new KubernetesClientSet -func (c *Client) getKubeClient() (*kubernetes.Clientset, error) { - var err error - if c.kubeClient == nil { - c.kubeClient, err = c.Factory.KubernetesClientSet() - } - - return c.kubeClient, err -} - -// IsReachable tests connectivity to the cluster. -func (c *Client) IsReachable() error { - client, err := c.getKubeClient() - if err == genericclioptions.ErrEmptyConfig { - // re-replace kubernetes ErrEmptyConfig error with a friendy error - // moar workarounds for Kubernetes API breaking. - return errors.New("Kubernetes cluster unreachable") - } - if err != nil { - return errors.Wrap(err, "Kubernetes cluster unreachable") - } - if _, err := client.ServerVersion(); err != nil { - return errors.Wrap(err, "Kubernetes cluster unreachable") - } - return nil -} - -// Create creates Kubernetes resources specified in the resource list. -func (c *Client) Create(resources ResourceList) (*Result, error) { - c.Log("creating %d resource(s)", len(resources)) - if err := perform(resources, createResource); err != nil { - return nil, err - } - return &Result{Created: resources}, nil -} - -// Wait waits up to the given timeout for the specified resources to be ready. -func (c *Client) Wait(resources ResourceList, timeout time.Duration) error { - cs, err := c.getKubeClient() - if err != nil { - return err - } - checker := NewReadyChecker(cs, c.Log, PausedAsReady(true)) - w := waiter{ - c: checker, - log: c.Log, - timeout: timeout, - } - return w.waitForResources(resources) -} - -// WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs. -func (c *Client) WaitWithJobs(resources ResourceList, timeout time.Duration) error { - cs, err := c.getKubeClient() - if err != nil { - return err - } - checker := NewReadyChecker(cs, c.Log, PausedAsReady(true), CheckJobs(true)) - w := waiter{ - c: checker, - log: c.Log, - timeout: timeout, - } - return w.waitForResources(resources) -} - -// WaitForDelete wait up to the given timeout for the specified resources to be deleted. -func (c *Client) WaitForDelete(resources ResourceList, timeout time.Duration) error { - w := waiter{ - log: c.Log, - timeout: timeout, - } - return w.waitForDeletedResources(resources) -} - -func (c *Client) namespace() string { - if c.Namespace != "" { - return c.Namespace - } - if ns, _, err := c.Factory.ToRawKubeConfigLoader().Namespace(); err == nil { - return ns - } - return v1.NamespaceDefault -} - -// newBuilder returns a new resource builder for structured api objects. -func (c *Client) newBuilder() *resource.Builder { - return c.Factory.NewBuilder(). - ContinueOnError(). - NamespaceParam(c.namespace()). - DefaultNamespace(). - Flatten() -} - -// Build validates for Kubernetes objects and returns unstructured infos. -func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) { - validationDirective := metav1.FieldValidationIgnore - if validate { - validationDirective = metav1.FieldValidationStrict - } - - dynamicClient, err := c.Factory.DynamicClient() - if err != nil { - return nil, err - } - - verifier := resource.NewQueryParamVerifier(dynamicClient, c.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation) - schema, err := c.Factory.Validator(validationDirective, verifier) - if err != nil { - return nil, err - } - result, err := c.newBuilder(). - Unstructured(). - Schema(schema). - Stream(reader, ""). - Do().Infos() - return result, scrubValidationError(err) -} - -// Update takes the current list of objects and target list of objects and -// creates resources that don't already exist, updates resources that have been -// modified in the target configuration, and deletes resources from the current -// configuration that are not present in the target configuration. If an error -// occurs, a Result will still be returned with the error, containing all -// resource updates, creations, and deletions that were attempted. These can be -// used for cleanup or other logging purposes. -func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { - updateErrors := []string{} - res := &Result{} - - c.Log("checking %d resources for changes", len(target)) - err := target.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err - } - - helper := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()) - if _, err := helper.Get(info.Namespace, info.Name); err != nil { - if !apierrors.IsNotFound(err) { - return errors.Wrap(err, "could not get information about the resource") - } - - // Append the created resource to the results, even if something fails - res.Created = append(res.Created, info) - - // Since the resource does not exist, create it. - if err := createResource(info); err != nil { - return errors.Wrap(err, "failed to create resource") - } - - kind := info.Mapping.GroupVersionKind.Kind - c.Log("Created a new %s called %q in %s\n", kind, info.Name, info.Namespace) - return nil - } - - originalInfo := original.Get(info) - if originalInfo == nil { - kind := info.Mapping.GroupVersionKind.Kind - return errors.Errorf("no %s with the name %q found", kind, info.Name) - } - - if err := updateResource(c, info, originalInfo.Object, force); err != nil { - c.Log("error updating the resource %q:\n\t %v", info.Name, err) - updateErrors = append(updateErrors, err.Error()) - } - // Because we check for errors later, append the info regardless - res.Updated = append(res.Updated, info) - - return nil - }) - - switch { - case err != nil: - return res, err - case len(updateErrors) != 0: - return res, errors.Errorf(strings.Join(updateErrors, " && ")) - } - - for _, info := range original.Difference(target) { - c.Log("Deleting %s %q in namespace %s...", info.Mapping.GroupVersionKind.Kind, info.Name, info.Namespace) - - if err := info.Get(); err != nil { - c.Log("Unable to get obj %q, err: %s", info.Name, err) - continue - } - annotations, err := metadataAccessor.Annotations(info.Object) - if err != nil { - c.Log("Unable to get annotations on %q, err: %s", info.Name, err) - } - if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { - c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy) - continue - } - if err := deleteResource(info); err != nil { - c.Log("Failed to delete %q, err: %s", info.ObjectName(), err) - continue - } - res.Deleted = append(res.Deleted, info) - } - return res, nil -} - -// Delete deletes Kubernetes resources specified in the resources list. It will -// attempt to delete all resources even if one or more fail and collect any -// errors. All successfully deleted items will be returned in the `Deleted` -// ResourceList that is part of the result. -func (c *Client) Delete(resources ResourceList) (*Result, []error) { - var errs []error - res := &Result{} - mtx := sync.Mutex{} - err := perform(resources, func(info *resource.Info) error { - c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) - err := deleteResource(info) - if err == nil || apierrors.IsNotFound(err) { - if err != nil { - c.Log("Ignoring delete failure for %q %s: %v", info.Name, info.Mapping.GroupVersionKind, err) - } - mtx.Lock() - defer mtx.Unlock() - res.Deleted = append(res.Deleted, info) - return nil - } - mtx.Lock() - defer mtx.Unlock() - // Collect the error and continue on - errs = append(errs, err) - return nil - }) - if err != nil { - // Rewrite the message from "no objects visited" if that is what we got - // back - if err == ErrNoObjectsVisited { - err = errors.New("object not found, skipping delete") - } - errs = append(errs, err) - } - if errs != nil { - return nil, errs - } - return res, nil -} - -func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error { - return func(info *resource.Info) error { - return c.watchUntilReady(t, info) - } -} - -// WatchUntilReady watches the resources given and waits until it is ready. -// -// This method is mainly for hook implementations. It watches for a resource to -// hit a particular milestone. The milestone depends on the Kind. -// -// For most kinds, it checks to see if the resource is marked as Added or Modified -// by the Kubernetes event stream. For some kinds, it does more: -// -// - Jobs: A job is marked "Ready" when it has successfully completed. This is -// ascertained by watching the Status fields in a job's output. -// - Pods: A pod is marked "Ready" when it has successfully completed. This is -// ascertained by watching the status.phase field in a pod's output. -// -// Handling for other kinds will be added as necessary. -func (c *Client) WatchUntilReady(resources ResourceList, timeout time.Duration) error { - // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): - // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 - return perform(resources, c.watchTimeout(timeout)) -} - -func perform(infos ResourceList, fn func(*resource.Info) error) error { - if len(infos) == 0 { - return ErrNoObjectsVisited - } - - errs := make(chan error) - go batchPerform(infos, fn, errs) - - for range infos { - err := <-errs - if err != nil { - return err - } - } - return nil -} - -// getManagedFieldsManager returns the manager string. If one was set it will be returned. -// Otherwise, one is calculated based on the name of the binary. -func getManagedFieldsManager() string { - - // When a manager is explicitly set use it - if ManagedFieldsManager != "" { - return ManagedFieldsManager - } - - // When no manager is set and no calling application can be found it is unknown - if len(os.Args[0]) == 0 { - return "unknown" - } - - // When there is an application that can be determined and no set manager - // use the base name. This is one of the ways Kubernetes libs handle figuring - // names out. - return filepath.Base(os.Args[0]) -} - -func batchPerform(infos ResourceList, fn func(*resource.Info) error, errs chan<- error) { - var kind string - var wg sync.WaitGroup - for _, info := range infos { - currentKind := info.Object.GetObjectKind().GroupVersionKind().Kind - if kind != currentKind { - wg.Wait() - kind = currentKind - } - wg.Add(1) - go func(i *resource.Info) { - errs <- fn(i) - wg.Done() - }(info) - } -} - -func createResource(info *resource.Info) error { - obj, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).Create(info.Namespace, true, info.Object) - if err != nil { - return err - } - return info.Refresh(obj, true) -} - -func deleteResource(info *resource.Info) error { - policy := metav1.DeletePropagationBackground - opts := &metav1.DeleteOptions{PropagationPolicy: &policy} - _, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).DeleteWithOptions(info.Namespace, info.Name, opts) - return err -} - -func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) { - oldData, err := json.Marshal(current) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing current configuration") - } - newData, err := json.Marshal(target.Object) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing target configuration") - } - - // Fetch the current object for the three way merge - helper := resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) - currentObj, err := helper.Get(target.Namespace, target.Name) - if err != nil && !apierrors.IsNotFound(err) { - return nil, types.StrategicMergePatchType, errors.Wrapf(err, "unable to get data for current object %s/%s", target.Namespace, target.Name) - } - - // Even if currentObj is nil (because it was not found), it will marshal just fine - currentData, err := json.Marshal(currentObj) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing live configuration") - } - - // Get a versioned object - versionedObject := AsVersioned(target) - - // Unstructured objects, such as CRDs, may not have an not registered error - // returned from ConvertToVersion. Anything that's unstructured should - // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported - // on objects like CRDs. - _, isUnstructured := versionedObject.(runtime.Unstructured) - - // On newer K8s versions, CRDs aren't unstructured but has this dedicated type - _, isCRD := versionedObject.(*apiextv1beta1.CustomResourceDefinition) - - if isUnstructured || isCRD { - // fall back to generic JSON merge patch - patch, err := jsonpatch.CreateMergePatch(oldData, newData) - return patch, types.MergePatchType, err - } - - patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) - if err != nil { - return nil, types.StrategicMergePatchType, errors.Wrap(err, "unable to create patch metadata from object") - } - - patch, err := strategicpatch.CreateThreeWayMergePatch(oldData, newData, currentData, patchMeta, true) - return patch, types.StrategicMergePatchType, err -} - -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { - var ( - obj runtime.Object - helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) - kind = target.Mapping.GroupVersionKind.Kind - ) - - // if --force is applied, attempt to replace the existing resource with the new object. - if force { - var err error - obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object) - if err != nil { - return errors.Wrap(err, "failed to replace object") - } - c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) - } else { - patch, patchType, err := createPatch(target, currentObj) - if err != nil { - return errors.Wrap(err, "failed to create patch") - } - - if patch == nil || string(patch) == "{}" { - c.Log("Looks like there are no changes for %s %q", kind, target.Name) - // This needs to happen to make sure that Helm has the latest info from the API - // Otherwise there will be no labels and other functions that use labels will panic - if err := target.Get(); err != nil { - return errors.Wrap(err, "failed to refresh resource information") - } - return nil - } - // send patch to server - c.Log("Patch %s %q in namespace %s", kind, target.Name, target.Namespace) - obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil) - if err != nil { - return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind) - } - } - - target.Refresh(obj, true) - return nil -} - -func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { - kind := info.Mapping.GroupVersionKind.Kind - switch kind { - case "Job", "Pod": - default: - return nil - } - - c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) - - // Use a selector on the name of the resource. This should be unique for the - // given version and kind - selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) - if err != nil { - return err - } - lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, selector) - - // What we watch for depends on the Kind. - // - For a Job, we watch for completion. - // - For all else, we watch until Ready. - // In the future, we might want to add some special logic for types - // like Ingress, Volume, etc. - - ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) - defer cancel() - _, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) { - // Make sure the incoming object is versioned as we use unstructured - // objects when we build manifests - obj := convertWithMapper(e.Object, info.Mapping) - switch e.Type { - case watch.Added, watch.Modified: - // For things like a secret or a config map, this is the best indicator - // we get. We care mostly about jobs, where what we want to see is - // the status go into a good state. For other types, like ReplicaSet - // we don't really do anything to support these as hooks. - c.Log("Add/Modify event for %s: %v", info.Name, e.Type) - switch kind { - case "Job": - return c.waitForJob(obj, info.Name) - case "Pod": - return c.waitForPodSuccess(obj, info.Name) - } - return true, nil - case watch.Deleted: - c.Log("Deleted event for %s", info.Name) - return true, nil - case watch.Error: - // Handle error and return with an error. - c.Log("Error event for %s", info.Name) - return true, errors.Errorf("failed to deploy %s", info.Name) - default: - return false, nil - } - }) - return err -} - -// waitForJob is a helper that waits for a job to complete. -// -// This operates on an event returned from a watcher. -func (c *Client) waitForJob(obj runtime.Object, name string) (bool, error) { - o, ok := obj.(*batch.Job) - if !ok { - return true, errors.Errorf("expected %s to be a *batch.Job, got %T", name, obj) - } - - for _, c := range o.Status.Conditions { - if c.Type == batch.JobComplete && c.Status == "True" { - return true, nil - } else if c.Type == batch.JobFailed && c.Status == "True" { - return true, errors.Errorf("job failed: %s", c.Reason) - } - } - - c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) - return false, nil -} - -// waitForPodSuccess is a helper that waits for a pod to complete. -// -// This operates on an event returned from a watcher. -func (c *Client) waitForPodSuccess(obj runtime.Object, name string) (bool, error) { - o, ok := obj.(*v1.Pod) - if !ok { - return true, errors.Errorf("expected %s to be a *v1.Pod, got %T", name, obj) - } - - switch o.Status.Phase { - case v1.PodSucceeded: - c.Log("Pod %s succeeded", o.Name) - return true, nil - case v1.PodFailed: - return true, errors.Errorf("pod %s failed", o.Name) - case v1.PodPending: - c.Log("Pod %s pending", o.Name) - case v1.PodRunning: - c.Log("Pod %s running", o.Name) - } - - return false, nil -} - -// scrubValidationError removes kubectl info from the message. -func scrubValidationError(err error) error { - if err == nil { - return nil - } - const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false" - - if strings.Contains(err.Error(), stopValidateMessage) { - return errors.New(strings.ReplaceAll(err.Error(), "; "+stopValidateMessage, "")) - } - return err -} - -// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase -// and returns said phase (PodSucceeded or PodFailed qualify). -func (c *Client) WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) { - client, err := c.getKubeClient() - if err != nil { - return v1.PodUnknown, err - } - to := int64(timeout) - watcher, err := client.CoreV1().Pods(c.namespace()).Watch(context.Background(), metav1.ListOptions{ - FieldSelector: fmt.Sprintf("metadata.name=%s", name), - TimeoutSeconds: &to, - }) - if err != nil { - return v1.PodUnknown, err - } - - for event := range watcher.ResultChan() { - p, ok := event.Object.(*v1.Pod) - if !ok { - return v1.PodUnknown, fmt.Errorf("%s not a pod", name) - } - switch p.Status.Phase { - case v1.PodFailed: - return v1.PodFailed, nil - case v1.PodSucceeded: - return v1.PodSucceeded, nil - } - } - - return v1.PodUnknown, err -} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go deleted file mode 100644 index de5358aee..000000000 --- a/pkg/kube/client_test.go +++ /dev/null @@ -1,522 +0,0 @@ -/* -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 kube - -import ( - "bytes" - "io" - "io/ioutil" - "net/http" - "strings" - "testing" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest/fake" - cmdtesting "k8s.io/kubectl/pkg/cmd/testing" -) - -var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer -var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - -func objBody(obj runtime.Object) io.ReadCloser { - return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) -} - -func newPod(name string) v1.Pod { - return newPodWithStatus(name, v1.PodStatus{}, "") -} - -func newPodWithStatus(name string, status v1.PodStatus, namespace string) v1.Pod { - ns := v1.NamespaceDefault - if namespace != "" { - ns = namespace - } - return v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns, - SelfLink: "/api/v1/namespaces/default/pods/" + name, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{{ - Name: "app:v4", - Image: "abc/app:v4", - Ports: []v1.ContainerPort{{Name: "http", ContainerPort: 80}}, - }}, - }, - Status: status, - } -} - -func newPodList(names ...string) v1.PodList { - var list v1.PodList - for _, name := range names { - list.Items = append(list.Items, newPod(name)) - } - return list -} - -func notFoundBody() *metav1.Status { - return &metav1.Status{ - Code: http.StatusNotFound, - Status: metav1.StatusFailure, - Reason: metav1.StatusReasonNotFound, - Message: " \"\" not found", - Details: &metav1.StatusDetails{}, - } -} - -func newResponse(code int, obj runtime.Object) (*http.Response, error) { - header := http.Header{} - header.Set("Content-Type", runtime.ContentTypeJSON) - body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) - return &http.Response{StatusCode: code, Header: header, Body: body}, nil -} - -func newTestClient(t *testing.T) *Client { - testFactory := cmdtesting.NewTestFactory() - t.Cleanup(testFactory.Cleanup) - - return &Client{ - Factory: testFactory.WithNamespace("default"), - Log: nopLogger, - } -} - -func TestUpdate(t *testing.T) { - listA := newPodList("starfish", "otter", "squid") - listB := newPodList("starfish", "otter", "dolphin") - listC := newPodList("starfish", "otter", "dolphin") - listB.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} - listC.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} - - var actions []string - - c := newTestClient(t) - c.Factory.(*cmdtesting.TestFactory).UnstructuredClient = &fake.RESTClient{ - NegotiatedSerializer: unstructuredSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - p, m := req.URL.Path, req.Method - actions = append(actions, p+":"+m) - t.Logf("got request %s %s", p, m) - switch { - case p == "/namespaces/default/pods/starfish" && m == "GET": - return newResponse(200, &listA.Items[0]) - case p == "/namespaces/default/pods/otter" && m == "GET": - return newResponse(200, &listA.Items[1]) - case p == "/namespaces/default/pods/otter" && m == "PATCH": - data, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatalf("could not dump request: %s", err) - } - req.Body.Close() - expected := `{}` - if string(data) != expected { - t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) - } - return newResponse(200, &listB.Items[0]) - case p == "/namespaces/default/pods/dolphin" && m == "GET": - return newResponse(404, notFoundBody()) - case p == "/namespaces/default/pods/starfish" && m == "PATCH": - data, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatalf("could not dump request: %s", err) - } - req.Body.Close() - expected := `{"spec":{"$setElementOrder/containers":[{"name":"app:v4"}],"containers":[{"$setElementOrder/ports":[{"containerPort":443}],"name":"app:v4","ports":[{"containerPort":443,"name":"https"},{"$patch":"delete","containerPort":80}]}]}}` - if string(data) != expected { - t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) - } - return newResponse(200, &listB.Items[0]) - case p == "/namespaces/default/pods" && m == "POST": - return newResponse(200, &listB.Items[1]) - case p == "/namespaces/default/pods/squid" && m == "DELETE": - return newResponse(200, &listB.Items[1]) - case p == "/namespaces/default/pods/squid" && m == "GET": - return newResponse(200, &listB.Items[2]) - default: - t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) - return nil, nil - } - }), - } - first, err := c.Build(objBody(&listA), false) - if err != nil { - t.Fatal(err) - } - second, err := c.Build(objBody(&listB), false) - if err != nil { - t.Fatal(err) - } - - result, err := c.Update(first, second, false) - if err != nil { - t.Fatal(err) - } - - if len(result.Created) != 1 { - t.Errorf("expected 1 resource created, got %d", len(result.Created)) - } - if len(result.Updated) != 2 { - t.Errorf("expected 2 resource updated, got %d", len(result.Updated)) - } - if len(result.Deleted) != 1 { - t.Errorf("expected 1 resource deleted, got %d", len(result.Deleted)) - } - - // TODO: Find a way to test methods that use Client Set - // Test with a wait - // if err := c.Update("test", objBody(codec, &listB), objBody(codec, &listC), false, 300, true); err != nil { - // t.Fatal(err) - // } - // Test with a wait should fail - // TODO: A way to make this not based off of an extremely short timeout? - // if err := c.Update("test", objBody(codec, &listC), objBody(codec, &listA), false, 2, true); err != nil { - // t.Fatal(err) - // } - expectedActions := []string{ - "/namespaces/default/pods/starfish:GET", - "/namespaces/default/pods/starfish:GET", - "/namespaces/default/pods/starfish:PATCH", - "/namespaces/default/pods/otter:GET", - "/namespaces/default/pods/otter:GET", - "/namespaces/default/pods/otter:GET", - "/namespaces/default/pods/dolphin:GET", - "/namespaces/default/pods:POST", - "/namespaces/default/pods/squid:GET", - "/namespaces/default/pods/squid:DELETE", - } - if len(expectedActions) != len(actions) { - t.Fatalf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions)) - } - for k, v := range expectedActions { - if actions[k] != v { - t.Errorf("expected %s request got %s", v, actions[k]) - } - } -} - -func TestBuild(t *testing.T) { - tests := []struct { - name string - namespace string - reader io.Reader - count int - err bool - }{ - { - name: "Valid input", - namespace: "test", - reader: strings.NewReader(guestbookManifest), - count: 6, - }, { - name: "Valid input, deploying resources into different namespaces", - namespace: "test", - reader: strings.NewReader(namespacedGuestbookManifest), - count: 1, - }, - } - - c := newTestClient(t) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test for an invalid manifest - infos, err := c.Build(tt.reader, false) - if err != nil && !tt.err { - t.Errorf("Got error message when no error should have occurred: %v", err) - } else if err != nil && strings.Contains(err.Error(), "--validate=false") { - t.Error("error message was not scrubbed") - } - - if len(infos) != tt.count { - t.Errorf("expected %d result objects, got %d", tt.count, len(infos)) - } - }) - } -} - -func TestPerform(t *testing.T) { - tests := []struct { - name string - reader io.Reader - count int - err bool - errMessage string - }{ - { - name: "Valid input", - reader: strings.NewReader(guestbookManifest), - count: 6, - }, { - name: "Empty manifests", - reader: strings.NewReader(""), - err: true, - errMessage: "no objects visited", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - results := []*resource.Info{} - - fn := func(info *resource.Info) error { - results = append(results, info) - return nil - } - - c := newTestClient(t) - infos, err := c.Build(tt.reader, false) - if err != nil && err.Error() != tt.errMessage { - t.Errorf("Error while building manifests: %v", err) - } - - err = perform(infos, fn) - if (err != nil) != tt.err { - t.Errorf("expected error: %v, got %v", tt.err, err) - } - if err != nil && err.Error() != tt.errMessage { - t.Errorf("expected error message: %v, got %v", tt.errMessage, err) - } - - if len(results) != tt.count { - t.Errorf("expected %d result objects, got %d", tt.count, len(results)) - } - }) - } -} - -func TestReal(t *testing.T) { - t.Skip("This is a live test, comment this line to run") - c := New(nil) - resources, err := c.Build(strings.NewReader(guestbookManifest), false) - if err != nil { - t.Fatal(err) - } - if _, err := c.Create(resources); err != nil { - t.Fatal(err) - } - - testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest - c = New(nil) - resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) - if err != nil { - t.Fatal(err) - } - if _, err := c.Create(resources); err != nil { - t.Fatal(err) - } - - resources, err = c.Build(strings.NewReader(testEndpointManifest), false) - if err != nil { - t.Fatal(err) - } - - if _, errs := c.Delete(resources); errs != nil { - t.Fatal(errs) - } - - resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) - if err != nil { - t.Fatal(err) - } - // ensures that delete does not fail if a resource is not found - if _, errs := c.Delete(resources); errs != nil { - t.Fatal(errs) - } -} - -const testServiceManifest = ` -kind: Service -apiVersion: v1 -metadata: - name: my-service -spec: - selector: - app: myapp - ports: - - port: 80 - protocol: TCP - targetPort: 9376 -` - -const testEndpointManifest = ` -kind: Endpoints -apiVersion: v1 -metadata: - name: my-service -subsets: - - addresses: - - ip: "1.2.3.4" - ports: - - port: 9376 -` - -const guestbookManifest = ` -apiVersion: v1 -kind: Service -metadata: - name: redis-master - labels: - app: redis - tier: backend - role: master -spec: - ports: - - port: 6379 - targetPort: 6379 - selector: - app: redis - tier: backend - role: master ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: redis-master -spec: - replicas: 1 - template: - metadata: - labels: - app: redis - role: master - tier: backend - spec: - containers: - - name: master - image: k8s.gcr.io/redis:e2e # or just image: redis - resources: - requests: - cpu: 100m - memory: 100Mi - ports: - - containerPort: 6379 ---- -apiVersion: v1 -kind: Service -metadata: - name: redis-slave - labels: - app: redis - tier: backend - role: slave -spec: - ports: - # the port that this service should serve on - - port: 6379 - selector: - app: redis - tier: backend - role: slave ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: redis-slave -spec: - replicas: 2 - template: - metadata: - labels: - app: redis - role: slave - tier: backend - spec: - containers: - - name: slave - image: gcr.io/google_samples/gb-redisslave:v1 - resources: - requests: - cpu: 100m - memory: 100Mi - env: - - name: GET_HOSTS_FROM - value: dns - ports: - - containerPort: 6379 ---- -apiVersion: v1 -kind: Service -metadata: - name: frontend - labels: - app: guestbook - tier: frontend -spec: - ports: - - port: 80 - selector: - app: guestbook - tier: frontend ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: frontend -spec: - replicas: 3 - template: - metadata: - labels: - app: guestbook - tier: frontend - spec: - containers: - - name: php-redis - image: gcr.io/google-samples/gb-frontend:v4 - resources: - requests: - cpu: 100m - memory: 100Mi - env: - - name: GET_HOSTS_FROM - value: dns - ports: - - containerPort: 80 -` - -const namespacedGuestbookManifest = ` -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: frontend - namespace: guestbook -spec: - replicas: 3 - template: - metadata: - labels: - app: guestbook - tier: frontend - spec: - containers: - - name: php-redis - image: gcr.io/google-samples/gb-frontend:v4 - resources: - requests: - cpu: 100m - memory: 100Mi - env: - - name: GET_HOSTS_FROM - value: dns - ports: - - containerPort: 80 -` diff --git a/pkg/kube/config.go b/pkg/kube/config.go deleted file mode 100644 index e00c9acb1..000000000 --- a/pkg/kube/config.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import "k8s.io/cli-runtime/pkg/genericclioptions" - -// GetConfig returns a Kubernetes client config. -// -// Deprecated -func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags { - cf := genericclioptions.NewConfigFlags(true) - cf.Namespace = &namespace - cf.Context = &context - cf.KubeConfig = &kubeconfig - return cf -} diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go deleted file mode 100644 index 3bf0e358c..000000000 --- a/pkg/kube/converter.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "sync" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/kubernetes/scheme" -) - -var k8sNativeScheme *runtime.Scheme -var k8sNativeSchemeOnce sync.Once - -// AsVersioned converts the given info into a runtime.Object with the correct -// group and version set -func AsVersioned(info *resource.Info) runtime.Object { - return convertWithMapper(info.Object, info.Mapping) -} - -// convertWithMapper converts the given object with the optional provided -// RESTMapping. If no mapping is provided, the default schema versioner is used -func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { - s := kubernetesNativeScheme() - var gv = runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups())) - if mapping != nil { - gv = mapping.GroupVersionKind.GroupVersion() - } - if obj, err := runtime.ObjectConvertor(s).ConvertToVersion(obj, gv); err == nil { - return obj - } - return obj -} - -// kubernetesNativeScheme returns a clean *runtime.Scheme with _only_ Kubernetes -// native resources added to it. This is required to break free of custom resources -// that may have been added to scheme.Scheme due to Helm being used as a package in -// combination with e.g. a versioned kube client. If we would not do this, the client -// may attempt to perform e.g. a 3-way-merge strategy patch for custom resources. -func kubernetesNativeScheme() *runtime.Scheme { - k8sNativeSchemeOnce.Do(func() { - k8sNativeScheme = runtime.NewScheme() - scheme.AddToScheme(k8sNativeScheme) - // API extensions are not in the above scheme set, - // and must thus be added separately. - apiextensionsv1beta1.AddToScheme(k8sNativeScheme) - apiextensionsv1.AddToScheme(k8sNativeScheme) - }) - return k8sNativeScheme -} diff --git a/pkg/kube/factory.go b/pkg/kube/factory.go deleted file mode 100644 index fdba8cf8f..000000000 --- a/pkg/kube/factory.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/kubectl/pkg/validation" -) - -// Factory provides abstractions that allow the Kubectl command to be extended across multiple types -// of resources and different API sets. -type Factory interface { - // ToRawKubeConfigLoader return kubeconfig loader as-is - ToRawKubeConfigLoader() clientcmd.ClientConfig - - // DynamicClient returns a dynamic client ready for use - DynamicClient() (dynamic.Interface, error) - - // KubernetesClientSet gives you back an external clientset - KubernetesClientSet() (*kubernetes.Clientset, error) - - // NewBuilder returns an object that assists in loading objects from both disk and the server - // and which implements the common patterns for CLI interactions with generic resources. - NewBuilder() *resource.Builder - - // Returns a schema that can validate objects stored on disk. - Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) - // OpenAPIGetter returns a getter for the openapi schema document - OpenAPIGetter() discovery.OpenAPISchemaInterface -} diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go deleted file mode 100644 index 0fc953116..000000000 --- a/pkg/kube/fake/fake.go +++ /dev/null @@ -1,117 +0,0 @@ -/* -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 fake implements various fake KubeClients for use in testing -package fake - -import ( - "io" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/cli-runtime/pkg/resource" - - "helm.sh/helm/v3/pkg/kube" -) - -// FailingKubeClient implements KubeClient for testing purposes. It also has -// additional errors you can set to fail different functions, otherwise it -// delegates all its calls to `PrintingKubeClient` -type FailingKubeClient struct { - PrintingKubeClient - CreateError error - WaitError error - DeleteError error - WatchUntilReadyError error - UpdateError error - BuildError error - BuildUnstructuredError error - WaitAndGetCompletedPodPhaseError error - WaitDuration time.Duration -} - -// Create returns the configured error if set or prints -func (f *FailingKubeClient) Create(resources kube.ResourceList) (*kube.Result, error) { - if f.CreateError != nil { - return nil, f.CreateError - } - return f.PrintingKubeClient.Create(resources) -} - -// Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints. -func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error { - time.Sleep(f.WaitDuration) - if f.WaitError != nil { - return f.WaitError - } - return f.PrintingKubeClient.Wait(resources, d) -} - -// WaitWithJobs returns the configured error if set or prints -func (f *FailingKubeClient) WaitWithJobs(resources kube.ResourceList, d time.Duration) error { - if f.WaitError != nil { - return f.WaitError - } - return f.PrintingKubeClient.WaitWithJobs(resources, d) -} - -// WaitForDelete returns the configured error if set or prints -func (f *FailingKubeClient) WaitForDelete(resources kube.ResourceList, d time.Duration) error { - if f.WaitError != nil { - return f.WaitError - } - return f.PrintingKubeClient.WaitForDelete(resources, d) -} - -// Delete returns the configured error if set or prints -func (f *FailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, []error) { - if f.DeleteError != nil { - return nil, []error{f.DeleteError} - } - return f.PrintingKubeClient.Delete(resources) -} - -// WatchUntilReady returns the configured error if set or prints -func (f *FailingKubeClient) WatchUntilReady(resources kube.ResourceList, d time.Duration) error { - if f.WatchUntilReadyError != nil { - return f.WatchUntilReadyError - } - return f.PrintingKubeClient.WatchUntilReady(resources, d) -} - -// Update returns the configured error if set or prints -func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool) (*kube.Result, error) { - if f.UpdateError != nil { - return &kube.Result{}, f.UpdateError - } - return f.PrintingKubeClient.Update(r, modified, ignoreMe) -} - -// Build returns the configured error if set or prints -func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error) { - if f.BuildError != nil { - return []*resource.Info{}, f.BuildError - } - return f.PrintingKubeClient.Build(r, false) -} - -// WaitAndGetCompletedPodPhase returns the configured error if set or prints -func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) { - if f.WaitAndGetCompletedPodPhaseError != nil { - return v1.PodSucceeded, f.WaitAndGetCompletedPodPhaseError - } - return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d) -} diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go deleted file mode 100644 index 1e8cf0066..000000000 --- a/pkg/kube/fake/printer.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -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 fake - -import ( - "io" - "strings" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/cli-runtime/pkg/resource" - - "helm.sh/helm/v3/pkg/kube" -) - -// PrintingKubeClient implements KubeClient, but simply prints the reader to -// the given output. -type PrintingKubeClient struct { - Out io.Writer -} - -// IsReachable checks if the cluster is reachable -func (p *PrintingKubeClient) IsReachable() error { - return nil -} - -// Create prints the values of what would be created with a real KubeClient. -func (p *PrintingKubeClient) Create(resources kube.ResourceList) (*kube.Result, error) { - _, err := io.Copy(p.Out, bufferize(resources)) - if err != nil { - return nil, err - } - return &kube.Result{Created: resources}, nil -} - -func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error { - _, err := io.Copy(p.Out, bufferize(resources)) - return err -} - -func (p *PrintingKubeClient) WaitWithJobs(resources kube.ResourceList, _ time.Duration) error { - _, err := io.Copy(p.Out, bufferize(resources)) - return err -} - -func (p *PrintingKubeClient) WaitForDelete(resources kube.ResourceList, _ time.Duration) error { - _, err := io.Copy(p.Out, bufferize(resources)) - return err -} - -// Delete implements KubeClient delete. -// -// It only prints out the content to be deleted. -func (p *PrintingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, []error) { - _, err := io.Copy(p.Out, bufferize(resources)) - if err != nil { - return nil, []error{err} - } - return &kube.Result{Deleted: resources}, nil -} - -// WatchUntilReady implements KubeClient WatchUntilReady. -func (p *PrintingKubeClient) WatchUntilReady(resources kube.ResourceList, _ time.Duration) error { - _, err := io.Copy(p.Out, bufferize(resources)) - return err -} - -// Update implements KubeClient Update. -func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kube.Result, error) { - _, err := io.Copy(p.Out, bufferize(modified)) - if err != nil { - return nil, err - } - // TODO: This doesn't completely mock out have some that get created, - // updated, and deleted. I don't think these are used in any unit tests, but - // we may want to refactor a way to handle future tests - return &kube.Result{Updated: modified}, nil -} - -// Build implements KubeClient Build. -func (p *PrintingKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, error) { - return []*resource.Info{}, nil -} - -// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase. -func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) { - return v1.PodSucceeded, nil -} - -func bufferize(resources kube.ResourceList) io.Reader { - var builder strings.Builder - for _, info := range resources { - builder.WriteString(info.String() + "\n") - } - return strings.NewReader(builder.String()) -} diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go deleted file mode 100644 index 299e34e95..000000000 --- a/pkg/kube/interface.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -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 kube - -import ( - "io" - "time" - - v1 "k8s.io/api/core/v1" -) - -// Interface represents a client capable of communicating with the Kubernetes API. -// -// A KubernetesClient must be concurrency safe. -type Interface interface { - // Create creates one or more resources. - Create(resources ResourceList) (*Result, error) - - // Wait waits up to the given timeout for the specified resources to be ready. - Wait(resources ResourceList, timeout time.Duration) error - - // WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs. - WaitWithJobs(resources ResourceList, timeout time.Duration) error - - // Delete destroys one or more resources. - Delete(resources ResourceList) (*Result, []error) - - // WatchUntilReady watches the resources given and waits until it is ready. - // - // This method is mainly for hook implementations. It watches for a resource to - // hit a particular milestone. The milestone depends on the Kind. - // - // For Jobs, "ready" means the Job ran to completion (exited without error). - // For Pods, "ready" means the Pod phase is marked "succeeded". - // For all other kinds, it means the kind was created or modified without - // error. - WatchUntilReady(resources ResourceList, timeout time.Duration) error - - // Update updates one or more resources or creates the resource - // if it doesn't exist. - Update(original, target ResourceList, force bool) (*Result, error) - - // Build creates a resource list from a Reader. - // - // Reader must contain a YAML stream (one or more YAML documents separated - // by "\n---\n") - // - // Validates against OpenAPI schema if validate is true. - Build(reader io.Reader, validate bool) (ResourceList, error) - - // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase - // and returns said phase (PodSucceeded or PodFailed qualify). - WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) - - // IsReachable checks whether the client is able to connect to the cluster. - IsReachable() error -} - -// InterfaceExt is introduced to avoid breaking backwards compatibility for Interface implementers. -// -// TODO Helm 4: Remove InterfaceExt and integrate its method(s) into the Interface. -type InterfaceExt interface { - // WaitForDelete wait up to the given timeout for the specified resources to be deleted. - WaitForDelete(resources ResourceList, timeout time.Duration) error -} - -var _ Interface = (*Client)(nil) -var _ InterfaceExt = (*Client)(nil) diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go deleted file mode 100644 index 0554c1729..000000000 --- a/pkg/kube/ready.go +++ /dev/null @@ -1,411 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "context" - - appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - appsv1beta2 "k8s.io/api/apps/v1beta2" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - - deploymentutil "helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util" -) - -// ReadyCheckerOption is a function that configures a ReadyChecker. -type ReadyCheckerOption func(*ReadyChecker) - -// PausedAsReady returns a ReadyCheckerOption that configures a ReadyChecker -// to consider paused resources to be ready. For example a Deployment -// with spec.paused equal to true would be considered ready. -func PausedAsReady(pausedAsReady bool) ReadyCheckerOption { - return func(c *ReadyChecker) { - c.pausedAsReady = pausedAsReady - } -} - -// CheckJobs returns a ReadyCheckerOption that configures a ReadyChecker -// to consider readiness of Job resources. -func CheckJobs(checkJobs bool) ReadyCheckerOption { - return func(c *ReadyChecker) { - c.checkJobs = checkJobs - } -} - -// NewReadyChecker creates a new checker. Passed ReadyCheckerOptions can -// be used to override defaults. -func NewReadyChecker(cl kubernetes.Interface, log func(string, ...interface{}), opts ...ReadyCheckerOption) ReadyChecker { - c := ReadyChecker{ - client: cl, - log: log, - } - if c.log == nil { - c.log = nopLogger - } - for _, opt := range opts { - opt(&c) - } - return c -} - -// ReadyChecker is a type that can check core Kubernetes types for readiness. -type ReadyChecker struct { - client kubernetes.Interface - log func(string, ...interface{}) - checkJobs bool - pausedAsReady bool -} - -// IsReady checks if v is ready. It supports checking readiness for pods, -// deployments, persistent volume claims, services, daemon sets, custom -// resource definitions, stateful sets, replication controllers, and replica -// sets. All other resource kinds are always considered ready. -// -// IsReady will fetch the latest state of the object from the server prior to -// performing readiness checks, and it will return any error encountered. -func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, error) { - var ( - // This defaults to true, otherwise we get to a point where - // things will always return false unless one of the objects - // that manages pods has been hit - ok = true - err error - ) - switch value := AsVersioned(v).(type) { - case *corev1.Pod: - pod, err := c.client.CoreV1().Pods(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil || !c.isPodReady(pod) { - return false, err - } - case *batchv1.Job: - if c.checkJobs { - job, err := c.client.BatchV1().Jobs(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil || !c.jobReady(job) { - return false, err - } - } - case *appsv1.Deployment, *appsv1beta1.Deployment, *appsv1beta2.Deployment, *extensionsv1beta1.Deployment: - currentDeployment, err := c.client.AppsV1().Deployments(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - // If paused deployment will never be ready - if currentDeployment.Spec.Paused { - return c.pausedAsReady, nil - } - // Find RS associated with deployment - newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, c.client.AppsV1()) - if err != nil || newReplicaSet == nil { - return false, err - } - if !c.deploymentReady(newReplicaSet, currentDeployment) { - return false, nil - } - case *corev1.PersistentVolumeClaim: - claim, err := c.client.CoreV1().PersistentVolumeClaims(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - if !c.volumeReady(claim) { - return false, nil - } - case *corev1.Service: - svc, err := c.client.CoreV1().Services(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - if !c.serviceReady(svc) { - return false, nil - } - case *extensionsv1beta1.DaemonSet, *appsv1.DaemonSet, *appsv1beta2.DaemonSet: - ds, err := c.client.AppsV1().DaemonSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - if !c.daemonSetReady(ds) { - return false, nil - } - case *apiextv1beta1.CustomResourceDefinition: - if err := v.Get(); err != nil { - return false, err - } - crd := &apiextv1beta1.CustomResourceDefinition{} - if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil { - return false, err - } - if !c.crdBetaReady(*crd) { - return false, nil - } - case *apiextv1.CustomResourceDefinition: - if err := v.Get(); err != nil { - return false, err - } - crd := &apiextv1.CustomResourceDefinition{} - if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil { - return false, err - } - if !c.crdReady(*crd) { - return false, nil - } - case *appsv1.StatefulSet, *appsv1beta1.StatefulSet, *appsv1beta2.StatefulSet: - sts, err := c.client.AppsV1().StatefulSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - if !c.statefulSetReady(sts) { - return false, nil - } - case *corev1.ReplicationController, *extensionsv1beta1.ReplicaSet, *appsv1beta2.ReplicaSet, *appsv1.ReplicaSet: - ok, err = c.podsReadyForObject(ctx, v.Namespace, value) - } - if !ok || err != nil { - return false, err - } - return true, nil -} - -func (c *ReadyChecker) podsReadyForObject(ctx context.Context, namespace string, obj runtime.Object) (bool, error) { - pods, err := c.podsforObject(ctx, namespace, obj) - if err != nil { - return false, err - } - for _, pod := range pods { - if !c.isPodReady(&pod) { - return false, nil - } - } - return true, nil -} - -func (c *ReadyChecker) podsforObject(ctx context.Context, namespace string, obj runtime.Object) ([]corev1.Pod, error) { - selector, err := SelectorsForObject(obj) - if err != nil { - return nil, err - } - list, err := getPods(ctx, c.client, namespace, selector.String()) - return list, err -} - -// isPodReady returns true if a pod is ready; false otherwise. -func (c *ReadyChecker) isPodReady(pod *corev1.Pod) bool { - for _, c := range pod.Status.Conditions { - if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue { - return true - } - } - c.log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName()) - return false -} - -func (c *ReadyChecker) jobReady(job *batchv1.Job) bool { - if job.Status.Failed > *job.Spec.BackoffLimit { - c.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName()) - return false - } - if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions { - c.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName()) - return false - } - return true -} - -func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { - // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) - if s.Spec.Type == corev1.ServiceTypeExternalName { - return true - } - - // Ensure that the service cluster IP is not empty - if s.Spec.ClusterIP == "" { - c.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName()) - return false - } - - // This checks if the service has a LoadBalancer and that balancer has an Ingress defined - if s.Spec.Type == corev1.ServiceTypeLoadBalancer { - // do not wait when at least 1 external IP is set - if len(s.Spec.ExternalIPs) > 0 { - c.log("Service %s/%s has external IP addresses (%v), marking as ready", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) - return true - } - - if s.Status.LoadBalancer.Ingress == nil { - c.log("Service does not have load balancer ingress IP address: %s/%s", s.GetNamespace(), s.GetName()) - return false - } - } - - return true -} - -func (c *ReadyChecker) volumeReady(v *corev1.PersistentVolumeClaim) bool { - if v.Status.Phase != corev1.ClaimBound { - c.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName()) - return false - } - return true -} - -func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool { - expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep) - if !(rs.Status.ReadyReplicas >= expectedReady) { - c.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady) - return false - } - return true -} - -func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { - // If the update strategy is not a rolling update, there will be nothing to wait for - if ds.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType { - return true - } - - // Make sure all the updated pods have been scheduled - if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled) - return false - } - maxUnavailable, err := intstr.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true) - if err != nil { - // If for some reason the value is invalid, set max unavailable to the - // number of desired replicas. This is the same behavior as the - // `MaxUnavailable` function in deploymentutil - maxUnavailable = int(ds.Status.DesiredNumberScheduled) - } - - expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable - if !(int(ds.Status.NumberReady) >= expectedReady) { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready", ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady) - return false - } - return true -} - -// Because the v1 extensions API is not available on all supported k8s versions -// yet and because Go doesn't support generics, we need to have a duplicate -// function to support the v1beta1 types -func (c *ReadyChecker) crdBetaReady(crd apiextv1beta1.CustomResourceDefinition) bool { - for _, cond := range crd.Status.Conditions { - switch cond.Type { - case apiextv1beta1.Established: - if cond.Status == apiextv1beta1.ConditionTrue { - return true - } - case apiextv1beta1.NamesAccepted: - if cond.Status == apiextv1beta1.ConditionFalse { - // This indicates a naming conflict, but it's probably not the - // job of this function to fail because of that. Instead, - // we treat it as a success, since the process should be able to - // continue. - return true - } - } - } - return false -} - -func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { - for _, cond := range crd.Status.Conditions { - switch cond.Type { - case apiextv1.Established: - if cond.Status == apiextv1.ConditionTrue { - return true - } - case apiextv1.NamesAccepted: - if cond.Status == apiextv1.ConditionFalse { - // This indicates a naming conflict, but it's probably not the - // job of this function to fail because of that. Instead, - // we treat it as a success, since the process should be able to - // continue. - return true - } - } - } - return false -} - -func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { - // If the update strategy is not a rolling update, there will be nothing to wait for - if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { - c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type) - return true - } - - // Make sure the status is up-to-date with the StatefulSet changes - if sts.Status.ObservedGeneration < sts.Generation { - c.log("StatefulSet is not ready: %s/%s. update has not yet been observed", sts.Namespace, sts.Name) - return false - } - - // Dereference all the pointers because StatefulSets like them - var partition int - // 1 is the default for replicas if not set - var replicas = 1 - // For some reason, even if the update strategy is a rolling update, the - // actual rollingUpdate field can be nil. If it is, we can safely assume - // there is no partition value - if sts.Spec.UpdateStrategy.RollingUpdate != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil { - partition = int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition) - } - if sts.Spec.Replicas != nil { - replicas = int(*sts.Spec.Replicas) - } - - // Because an update strategy can use partitioning, we need to calculate the - // number of updated replicas we should have. For example, if the replicas - // is set to 3 and the partition is 2, we'd expect only one pod to be - // updated - expectedReplicas := replicas - partition - - // Make sure all the updated pods have been scheduled - if int(sts.Status.UpdatedReplicas) < expectedReplicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", sts.Namespace, sts.Name, sts.Status.UpdatedReplicas, expectedReplicas) - return false - } - - if int(sts.Status.ReadyReplicas) != replicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) - return false - } - - if sts.Status.CurrentRevision != sts.Status.UpdateRevision { - c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision) - return false - } - - c.log("StatefulSet is ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) - return true -} - -func getPods(ctx context.Context, client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) { - list, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ - LabelSelector: selector, - }) - return list.Items, err -} diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go deleted file mode 100644 index 9fe20d8cb..000000000 --- a/pkg/kube/ready_test.go +++ /dev/null @@ -1,566 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "context" - "testing" - - appsv1 "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes/fake" -) - -const defaultNamespace = metav1.NamespaceDefault - -func Test_ReadyChecker_deploymentReady(t *testing.T) { - type args struct { - rs *appsv1.ReplicaSet - dep *appsv1.Deployment - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "deployment is ready", - args: args{ - rs: newReplicaSet("foo", 1, 1), - dep: newDeployment("foo", 1, 1, 0), - }, - want: true, - }, - { - name: "deployment is not ready", - args: args{ - rs: newReplicaSet("foo", 0, 0), - dep: newDeployment("foo", 1, 1, 0), - }, - want: false, - }, - { - name: "deployment is ready when maxUnavailable is set", - args: args{ - rs: newReplicaSet("foo", 2, 1), - dep: newDeployment("foo", 2, 1, 1), - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want { - t.Errorf("deploymentReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_daemonSetReady(t *testing.T) { - type args struct { - ds *appsv1.DaemonSet - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "daemonset is ready", - args: args{ - ds: newDaemonSet("foo", 0, 1, 1, 1), - }, - want: true, - }, - { - name: "daemonset is not ready", - args: args{ - ds: newDaemonSet("foo", 0, 0, 1, 1), - }, - want: false, - }, - { - name: "daemonset pods have not been scheduled successfully", - args: args{ - ds: newDaemonSet("foo", 0, 0, 1, 0), - }, - want: false, - }, - { - name: "daemonset is ready when maxUnavailable is set", - args: args{ - ds: newDaemonSet("foo", 1, 1, 2, 2), - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.daemonSetReady(tt.args.ds); got != tt.want { - t.Errorf("daemonSetReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_statefulSetReady(t *testing.T) { - type args struct { - sts *appsv1.StatefulSet - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "statefulset is ready", - args: args{ - sts: newStatefulSet("foo", 1, 0, 1, 1), - }, - want: true, - }, - { - name: "statefulset is not ready", - args: args{ - sts: newStatefulSet("foo", 1, 0, 0, 1), - }, - want: false, - }, - { - name: "statefulset is ready when partition is specified", - args: args{ - sts: newStatefulSet("foo", 2, 1, 2, 1), - }, - want: true, - }, - { - name: "statefulset is not ready when partition is set", - args: args{ - sts: newStatefulSet("foo", 2, 1, 1, 0), - }, - want: false, - }, - { - name: "statefulset is ready when partition is set and no change in template", - args: args{ - sts: newStatefulSet("foo", 2, 1, 2, 2), - }, - want: true, - }, - { - name: "statefulset is ready when partition is greater than replicas", - args: args{ - sts: newStatefulSet("foo", 1, 2, 1, 1), - }, - want: true, - }, - { - name: "statefulset is not ready when status of latest generation has not yet been observed", - args: args{ - sts: newStatefulSetWithNewGeneration("foo", 1, 0, 1, 1), - }, - want: false, - }, - { - name: "statefulset is not ready when current revision for current replicas does not match update revision for updated replicas", - args: args{ - sts: newStatefulSetWithUpdateRevision("foo", 1, 0, 1, 1, "foo-bbbbbbb"), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.statefulSetReady(tt.args.sts); got != tt.want { - t.Errorf("statefulSetReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_podsReadyForObject(t *testing.T) { - type args struct { - namespace string - obj runtime.Object - } - tests := []struct { - name string - args args - existPods []corev1.Pod - want bool - wantErr bool - }{ - { - name: "pods ready for a replicaset", - args: args{ - namespace: defaultNamespace, - obj: newReplicaSet("foo", 1, 1), - }, - existPods: []corev1.Pod{ - *newPodWithCondition("foo", corev1.ConditionTrue), - }, - want: true, - wantErr: false, - }, - { - name: "pods not ready for a replicaset", - args: args{ - namespace: defaultNamespace, - obj: newReplicaSet("foo", 1, 1), - }, - existPods: []corev1.Pod{ - *newPodWithCondition("foo", corev1.ConditionFalse), - }, - want: false, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - for _, pod := range tt.existPods { - if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Pod error: %v", err) - return - } - } - got, err := c.podsReadyForObject(context.TODO(), tt.args.namespace, tt.args.obj) - if (err != nil) != tt.wantErr { - t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("podsReadyForObject() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_jobReady(t *testing.T) { - type args struct { - job *batchv1.Job - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "job is completed", - args: args{job: newJob("foo", 1, intToInt32(1), 1, 0)}, - want: true, - }, - { - name: "job is incomplete", - args: args{job: newJob("foo", 1, intToInt32(1), 0, 0)}, - want: false, - }, - { - name: "job is failed", - args: args{job: newJob("foo", 1, intToInt32(1), 0, 1)}, - want: false, - }, - { - name: "job is completed with retry", - args: args{job: newJob("foo", 1, intToInt32(1), 1, 1)}, - want: true, - }, - { - name: "job is failed with retry", - args: args{job: newJob("foo", 1, intToInt32(1), 0, 2)}, - want: false, - }, - { - name: "job is completed single run", - args: args{job: newJob("foo", 0, intToInt32(1), 1, 0)}, - want: true, - }, - { - name: "job is failed single run", - args: args{job: newJob("foo", 0, intToInt32(1), 0, 1)}, - want: false, - }, - { - name: "job with null completions", - args: args{job: newJob("foo", 0, nil, 1, 0)}, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.jobReady(tt.args.job); got != tt.want { - t.Errorf("jobReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_volumeReady(t *testing.T) { - type args struct { - v *corev1.PersistentVolumeClaim - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "pvc is bound", - args: args{ - v: newPersistentVolumeClaim("foo", corev1.ClaimBound), - }, - want: true, - }, - { - name: "pvc is not ready", - args: args{ - v: newPersistentVolumeClaim("foo", corev1.ClaimPending), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.volumeReady(tt.args.v); got != tt.want { - t.Errorf("volumeReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int) *appsv1.DaemonSet { - return &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Spec: appsv1.DaemonSetSpec{ - UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ - Type: appsv1.RollingUpdateDaemonSetStrategyType, - RollingUpdate: &appsv1.RollingUpdateDaemonSet{ - MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(), - }, - }, - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - Status: appsv1.DaemonSetStatus{ - DesiredNumberScheduled: int32(desiredNumberScheduled), - NumberReady: int32(numberReady), - UpdatedNumberScheduled: int32(updatedNumberScheduled), - }, - } -} - -func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { - return &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - Generation: int64(1), - }, - Spec: appsv1.StatefulSetSpec{ - UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ - Type: appsv1.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ - Partition: intToInt32(partition), - }, - }, - Replicas: intToInt32(replicas), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - Status: appsv1.StatefulSetStatus{ - ObservedGeneration: int64(1), - CurrentRevision: name + "-aaaaaaa", - UpdateRevision: name + "-aaaaaaa", - UpdatedReplicas: int32(updatedReplicas), - ReadyReplicas: int32(readyReplicas), - }, - } -} - -func newStatefulSetWithNewGeneration(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { - ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas) - ss.Generation++ - return ss -} - -func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string) *appsv1.StatefulSet { - ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas) - ss.Status.UpdateRevision = updateRevision - return ss -} - -func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment { - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Spec: appsv1.DeploymentSpec{ - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(), - MaxSurge: func() *intstr.IntOrString { i := intstr.FromInt(maxSurge); return &i }(), - }, - }, - Replicas: intToInt32(replicas), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - } -} - -func newReplicaSet(name string, replicas int, readyReplicas int) *appsv1.ReplicaSet { - d := newDeployment(name, replicas, 0, 0) - return &appsv1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - Labels: d.Spec.Selector.MatchLabels, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, d.GroupVersionKind())}, - }, - Spec: appsv1.ReplicaSetSpec{ - Selector: d.Spec.Selector, - Replicas: intToInt32(replicas), - Template: d.Spec.Template, - }, - Status: appsv1.ReplicaSetStatus{ - ReadyReplicas: int32(readyReplicas), - }, - } -} - -func newPodWithCondition(name string, podReadyCondition corev1.ConditionStatus) *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - Status: corev1.PodStatus{ - Conditions: []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: podReadyCondition, - }, - }, - }, - } -} - -func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim { - return &corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Status: corev1.PersistentVolumeClaimStatus{ - Phase: phase, - }, - } -} - -func newJob(name string, backoffLimit int, completions *int32, succeeded int, failed int) *batchv1.Job { - return &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Spec: batchv1.JobSpec{ - BackoffLimit: intToInt32(backoffLimit), - Completions: completions, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - Status: batchv1.JobStatus{ - Succeeded: int32(succeeded), - Failed: int32(failed), - }, - } -} - -func intToInt32(i int) *int32 { - i32 := int32(i) - return &i32 -} diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go deleted file mode 100644 index ee8f83a25..000000000 --- a/pkg/kube/resource.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import "k8s.io/cli-runtime/pkg/resource" - -// ResourceList provides convenience methods for comparing collections of Infos. -type ResourceList []*resource.Info - -// Append adds an Info to the Result. -func (r *ResourceList) Append(val *resource.Info) { - *r = append(*r, val) -} - -// Visit implements resource.Visitor. -func (r ResourceList) Visit(fn resource.VisitorFunc) error { - for _, i := range r { - if err := fn(i, nil); err != nil { - return err - } - } - return nil -} - -// Filter returns a new Result with Infos that satisfy the predicate fn. -func (r ResourceList) Filter(fn func(*resource.Info) bool) ResourceList { - var result ResourceList - for _, i := range r { - if fn(i) { - result.Append(i) - } - } - return result -} - -// Get returns the Info from the result that matches the name and kind. -func (r ResourceList) Get(info *resource.Info) *resource.Info { - for _, i := range r { - if isMatchingInfo(i, info) { - return i - } - } - return nil -} - -// Contains checks to see if an object exists. -func (r ResourceList) Contains(info *resource.Info) bool { - for _, i := range r { - if isMatchingInfo(i, info) { - return true - } - } - return false -} - -// Difference will return a new Result with objects not contained in rs. -func (r ResourceList) Difference(rs ResourceList) ResourceList { - return r.Filter(func(info *resource.Info) bool { - return !rs.Contains(info) - }) -} - -// Intersect will return a new Result with objects contained in both Results. -func (r ResourceList) Intersect(rs ResourceList) ResourceList { - return r.Filter(rs.Contains) -} - -// isMatchingInfo returns true if infos match on Name and GroupVersionKind. -func isMatchingInfo(a, b *resource.Info) bool { - return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind -} diff --git a/pkg/kube/resource_policy.go b/pkg/kube/resource_policy.go deleted file mode 100644 index 5f391eb50..000000000 --- a/pkg/kube/resource_policy.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -// ResourcePolicyAnno is the annotation name for a resource policy -const ResourcePolicyAnno = "helm.sh/resource-policy" - -// KeepPolicy is the resource policy type for keep -// -// This resource policy type allows resources to skip being deleted -// during an uninstallRelease action. -const KeepPolicy = "keep" diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go deleted file mode 100644 index 3c906ceca..000000000 --- a/pkg/kube/resource_test.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "testing" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/resource" -) - -func TestResourceList(t *testing.T) { - mapping := &meta.RESTMapping{ - Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "pod"}, - } - - info := func(name string) *resource.Info { - return &resource.Info{Name: name, Mapping: mapping} - } - - var r1, r2 ResourceList - r1 = []*resource.Info{info("foo"), info("bar")} - r2 = []*resource.Info{info("bar")} - - if r1.Get(info("bar")).Mapping.Resource.Resource != "pod" { - t.Error("expected get pod") - } - - diff := r1.Difference(r2) - if len(diff) != 1 { - t.Error("expected 1 result") - } - - if !diff.Contains(info("foo")) { - t.Error("expected diff to return foo") - } - - inter := r1.Intersect(r2) - if len(inter) != 1 { - t.Error("expected 1 result") - } - - if !inter.Contains(info("bar")) { - t.Error("expected intersect to return bar") - } -} diff --git a/pkg/kube/result.go b/pkg/kube/result.go deleted file mode 100644 index c3e171c2e..000000000 --- a/pkg/kube/result.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -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 kube - -// Result contains the information of created, updated, and deleted resources -// for various kube API calls along with helper methods for using those -// resources -type Result struct { - Created ResourceList - Updated ResourceList - Deleted ResourceList -} - -// If needed, we can add methods to the Result type for things like diffing diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go deleted file mode 100644 index fd01e9bc7..000000000 --- a/pkg/kube/wait.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "context" - "fmt" - "time" - - "github.com/pkg/errors" - "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" - "google.golang.org/grpc/codes" - - appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - appsv1beta2 "k8s.io/api/apps/v1beta2" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" -) - -type waiter struct { - c ReadyChecker - timeout time.Duration - log func(string, ...interface{}) -} - -// isServiceUnavailable helps figure out if the error is caused by etcd not being available -// see https://pkg.go.dev/go.etcd.io/etcd/api/v3/v3rpc/rpctypes for `codes.Unavailable` -// we use this to check if the etcdserver is not available we should retry in case -// this is a temporary situation -func isServiceUnavailable(err error) bool { - if err != nil { - err = rpctypes.Error(err) - if ev, ok := err.(rpctypes.EtcdError); ok { - if ev.Code() == codes.Unavailable { - return true - } - } - } - return false -} - -// waitForResources polls to get the current status of all pods, PVCs, Services and -// Jobs(optional) until all are ready or a timeout is reached -func (w *waiter) waitForResources(created ResourceList) error { - w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout) - - ctx, cancel := context.WithTimeout(context.Background(), w.timeout) - defer cancel() - - return wait.PollImmediateUntil(2*time.Second, func() (bool, error) { - for _, v := range created { - ready, err := w.c.IsReady(ctx, v) - if !ready || err != nil { - if isServiceUnavailable(err) { - return false, nil - } - return false, err - } - } - return true, nil - }, ctx.Done()) -} - -// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached -func (w *waiter) waitForDeletedResources(deleted ResourceList) error { - w.log("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), w.timeout) - - ctx, cancel := context.WithTimeout(context.Background(), w.timeout) - defer cancel() - - return wait.PollImmediateUntil(2*time.Second, func() (bool, error) { - for _, v := range deleted { - err := v.Get() - if err == nil || !apierrors.IsNotFound(err) { - if isServiceUnavailable(err) { - return false, nil - } - return false, err - } - } - return true, nil - }, ctx.Done()) -} - -// SelectorsForObject returns the pod label selector for a given object -// -// Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84 -func SelectorsForObject(object runtime.Object) (selector labels.Selector, err error) { - switch t := object.(type) { - case *extensionsv1beta1.ReplicaSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1.ReplicaSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.ReplicaSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *corev1.ReplicationController: - selector = labels.SelectorFromSet(t.Spec.Selector) - case *appsv1.StatefulSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta1.StatefulSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.StatefulSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *extensionsv1beta1.DaemonSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1.DaemonSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.DaemonSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *extensionsv1beta1.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta1.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *batchv1.Job: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *corev1.Service: - if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { - return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) - } - selector = labels.SelectorFromSet(t.Spec.Selector) - - default: - return nil, fmt.Errorf("selector for %T not implemented", object) - } - - return selector, errors.Wrap(err, "invalid label selector") -} diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go deleted file mode 100644 index 5f18e49ce..000000000 --- a/pkg/kube/wait_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "errors" - "testing" - - "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" -) - -func Test_isServiceUnavailable(t *testing.T) { - tests := []struct { - err error - expect bool - }{ - {err: nil, expect: false}, - {err: errors.New("random error from somewhere"), expect: false}, - {err: rpctypes.ErrGRPCLeaderChanged, expect: true}, - {err: errors.New("etcdserver: leader changed"), expect: true}, - } - - for _, tt := range tests { - if isServiceUnavailable(tt.err) != tt.expect { - t.Errorf("failed test for %q (expect equal: %t)", tt.err, tt.expect) - } - } -} diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go deleted file mode 100644 index 67e76bd3d..000000000 --- a/pkg/lint/lint.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 lint // import "helm.sh/helm/v3/pkg/lint" - -import ( - "path/filepath" - - "helm.sh/helm/v3/pkg/lint/rules" - "helm.sh/helm/v3/pkg/lint/support" -) - -// All runs all of the available linters on the given base directory. -func All(basedir string, values map[string]interface{}, namespace string, strict bool) support.Linter { - // Using abs path to get directory context - chartDir, _ := filepath.Abs(basedir) - - linter := support.Linter{ChartDir: chartDir} - rules.Chartfile(&linter) - rules.ValuesWithOverrides(&linter, values) - rules.Templates(&linter, values, namespace, strict) - rules.Dependencies(&linter) - return linter -} diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go deleted file mode 100644 index 0e5d42391..000000000 --- a/pkg/lint/lint_test.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -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 lint - -import ( - "strings" - "testing" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -var values map[string]interface{} - -const namespace = "testNamespace" -const strict = false - -const badChartDir = "rules/testdata/badchartfile" -const badValuesFileDir = "rules/testdata/badvaluesfile" -const badYamlFileDir = "rules/testdata/albatross" -const goodChartDir = "rules/testdata/goodone" -const subChartValuesDir = "rules/testdata/withsubchart" - -func TestBadChart(t *testing.T) { - m := All(badChartDir, values, namespace, strict).Messages - if len(m) != 8 { - t.Errorf("Number of errors %v", len(m)) - t.Errorf("All didn't fail with expected errors, got %#v", m) - } - // There should be one INFO, 2 WARNINGs and 2 ERROR messages, check for them - var i, w, e, e2, e3, e4, e5, e6 bool - for _, msg := range m { - if msg.Severity == support.InfoSev { - if strings.Contains(msg.Err.Error(), "icon is recommended") { - i = true - } - } - if msg.Severity == support.WarningSev { - if strings.Contains(msg.Err.Error(), "directory not found") { - w = true - } - } - if msg.Severity == support.ErrorSev { - if strings.Contains(msg.Err.Error(), "version '0.0.0.0' is not a valid SemVer") { - e = true - } - if strings.Contains(msg.Err.Error(), "name is required") { - e2 = true - } - - if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { - e3 = true - } - - if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") { - e4 = true - } - - if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - e5 = true - } - // This comes from the dependency check, which loads dependency info from the Chart.yaml - if strings.Contains(msg.Err.Error(), "unable to load chart") { - e6 = true - } - } - } - if !e || !e2 || !e3 || !e4 || !e5 || !w || !i || !e6 { - t.Errorf("Didn't find all the expected errors, got %#v", m) - } -} - -func TestInvalidYaml(t *testing.T) { - m := All(badYamlFileDir, values, namespace, strict).Messages - if len(m) != 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("All didn't have the error for deliberateSyntaxError") - } -} - -func TestBadValues(t *testing.T) { - m := All(badValuesFileDir, values, namespace, strict).Messages - if len(m) < 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") { - t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) - } -} - -func TestGoodChart(t *testing.T) { - m := All(goodChartDir, values, namespace, strict).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") - for i, msg := range m { - t.Logf("Message %d: %s", i, msg) - } - } -} - -// TestHelmCreateChart tests that a `helm create` always passes a `helm lint` test. -// -// See https://github.com/helm/helm/issues/7923 -func TestHelmCreateChart(t *testing.T) { - dir := t.TempDir() - - createdChart, err := chartutil.Create("testhelmcreatepasseslint", dir) - if err != nil { - t.Error(err) - // Fatal is bad because of the defer. - return - } - - // Note: we test with strict=true here, even though others have - // strict = false. - m := All(createdChart, values, namespace, true).Messages - if ll := len(m); ll != 1 { - t.Errorf("All should have had exactly 1 error. Got %d", ll) - for i, msg := range m { - t.Logf("Message %d: %s", i, msg.Error()) - } - } else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { - t.Errorf("Unexpected lint error: %s", msg) - } -} - -// lint ignores import-values -// See https://github.com/helm/helm/issues/9658 -func TestSubChartValuesChart(t *testing.T) { - m := All(subChartValuesDir, values, namespace, strict).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") - for i, msg := range m { - t.Logf("Message %d: %s", i, msg) - } - } -} diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go deleted file mode 100644 index b49f2cec0..000000000 --- a/pkg/lint/rules/chartfile.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/Masterminds/semver/v3" - "github.com/asaskevich/govalidator" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Chartfile runs a set of linter rules related to Chart.yaml file -func Chartfile(linter *support.Linter) { - chartFileName := "Chart.yaml" - chartPath := filepath.Join(linter.ChartDir, chartFileName) - - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlNotDirectory(chartPath)) - - chartFile, err := chartutil.LoadChartfile(chartPath) - validChartFile := linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlFormat(err)) - - // Guard clause. Following linter rules require a parsable ChartFile - if !validChartFile { - return - } - - // type check for Chart.yaml . ignoring error as any parse - // errors would already be caught in the above load function - chartFileForTypeCheck, _ := loadChartFileForTypeCheck(chartPath) - - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) - - // Chart metadata - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile)) - - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersionType(chartFileForTypeCheck)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAppVersionType(chartFileForTypeCheck)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile)) - linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartType(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile)) -} - -func validateChartVersionType(data map[string]interface{}) error { - return isStringValue(data, "version") -} - -func validateChartAppVersionType(data map[string]interface{}) error { - return isStringValue(data, "appVersion") -} - -func isStringValue(data map[string]interface{}, key string) error { - value, ok := data[key] - if !ok { - return nil - } - valueType := fmt.Sprintf("%T", value) - if valueType != "string" { - return errors.Errorf("%s should be of type string but it's of type %s", key, valueType) - } - return nil -} - -func validateChartYamlNotDirectory(chartPath string) error { - fi, err := os.Stat(chartPath) - - if err == nil && fi.IsDir() { - return errors.New("should be a file, not a directory") - } - return nil -} - -func validateChartYamlFormat(chartFileError error) error { - if chartFileError != nil { - return errors.Errorf("unable to parse YAML\n\t%s", chartFileError.Error()) - } - return nil -} - -func validateChartName(cf *chart.Metadata) error { - if cf.Name == "" { - return errors.New("name is required") - } - return nil -} - -func validateChartAPIVersion(cf *chart.Metadata) error { - if cf.APIVersion == "" { - return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"") - } - - if cf.APIVersion != chart.APIVersionV1 && cf.APIVersion != chart.APIVersionV2 { - return fmt.Errorf("apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"", cf.APIVersion) - } - - return nil -} - -func validateChartVersion(cf *chart.Metadata) error { - if cf.Version == "" { - return errors.New("version is required") - } - - version, err := semver.NewVersion(cf.Version) - - if err != nil { - return errors.Errorf("version '%s' is not a valid SemVer", cf.Version) - } - - c, err := semver.NewConstraint(">0.0.0-0") - if err != nil { - return err - } - valid, msg := c.Validate(version) - - if !valid && len(msg) > 0 { - return errors.Errorf("version %v", msg[0]) - } - - return nil -} - -func validateChartMaintainer(cf *chart.Metadata) error { - for _, maintainer := range cf.Maintainers { - if maintainer.Name == "" { - return errors.New("each maintainer requires a name") - } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { - return errors.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) - } else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) { - return errors.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name) - } - } - return nil -} - -func validateChartSources(cf *chart.Metadata) error { - for _, source := range cf.Sources { - if source == "" || !govalidator.IsRequestURL(source) { - return errors.Errorf("invalid source URL '%s'", source) - } - } - return nil -} - -func validateChartIconPresence(cf *chart.Metadata) error { - if cf.Icon == "" { - return errors.New("icon is recommended") - } - return nil -} - -func validateChartIconURL(cf *chart.Metadata) error { - if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) { - return errors.Errorf("invalid icon URL '%s'", cf.Icon) - } - return nil -} - -func validateChartDependencies(cf *chart.Metadata) error { - if len(cf.Dependencies) > 0 && cf.APIVersion != chart.APIVersionV2 { - return fmt.Errorf("dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) - } - return nil -} - -func validateChartType(cf *chart.Metadata) error { - if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV2 { - return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) - } - return nil -} - -// loadChartFileForTypeCheck loads the Chart.yaml -// in a generic form of a map[string]interface{}, so that the type -// of the values can be checked -func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - y := make(map[string]interface{}) - err = yaml.Unmarshal(b, &y) - return y, err -} diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go deleted file mode 100644 index 087cda047..000000000 --- a/pkg/lint/rules/chartfile_test.go +++ /dev/null @@ -1,247 +0,0 @@ -/* -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 rules - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -const ( - badChartDir = "testdata/badchartfile" - anotherBadChartDir = "testdata/anotherbadchartfile" -) - -var ( - badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") - nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") -) - -var badChart, _ = chartutil.LoadChartfile(badChartFilePath) - -// Validation functions Test -func TestValidateChartYamlNotDirectory(t *testing.T) { - _ = os.Mkdir(nonExistingChartFilePath, os.ModePerm) - defer os.Remove(nonExistingChartFilePath) - - err := validateChartYamlNotDirectory(nonExistingChartFilePath) - if err == nil { - t.Errorf("validateChartYamlNotDirectory to return a linter error, got no error") - } -} - -func TestValidateChartYamlFormat(t *testing.T) { - err := validateChartYamlFormat(errors.New("Read error")) - if err == nil { - t.Errorf("validateChartYamlFormat to return a linter error, got no error") - } - - err = validateChartYamlFormat(nil) - if err != nil { - t.Errorf("validateChartYamlFormat to return no error, got a linter error") - } -} - -func TestValidateChartName(t *testing.T) { - err := validateChartName(badChart) - if err == nil { - t.Errorf("validateChartName to return a linter error, got no error") - } -} - -func TestValidateChartVersion(t *testing.T) { - var failTest = []struct { - Version string - ErrorMsg string - }{ - {"", "version is required"}, - {"1.2.3.4", "version '1.2.3.4' is not a valid SemVer"}, - {"waps", "'waps' is not a valid SemVer"}, - {"-3", "'-3' is not a valid SemVer"}, - } - - var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} - - for _, test := range failTest { - badChart.Version = test.Version - err := validateChartVersion(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) - } - } - - for _, version := range successTest { - badChart.Version = version - err := validateChartVersion(badChart) - if err != nil { - t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version) - } - } -} - -func TestValidateChartMaintainer(t *testing.T) { - var failTest = []struct { - Name string - Email string - ErrorMsg string - }{ - {"", "", "each maintainer requires a name"}, - {"", "test@test.com", "each maintainer requires a name"}, - {"John Snow", "wrongFormatEmail.com", "invalid email"}, - } - - var successTest = []struct { - Name string - Email string - }{ - {"John Snow", ""}, - {"John Snow", "john@winterfell.com"}, - } - - for _, test := range failTest { - badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) - } - } - - for _, test := range successTest { - badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err != nil { - t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error()) - } - } -} - -func TestValidateChartSources(t *testing.T) { - var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} - for _, test := range failTest { - badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid source URL") { - t.Errorf("validateChartSources(%s) to return \"invalid source URL\", got no error", test) - } - } - - for _, test := range successTest { - badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error()) - } - } -} - -func TestValidateChartIconPresence(t *testing.T) { - err := validateChartIconPresence(badChart) - if err == nil { - t.Errorf("validateChartIconPresence to return a linter error, got no error") - } -} - -func TestValidateChartIconURL(t *testing.T) { - var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} - for _, test := range failTest { - badChart.Icon = test - err := validateChartIconURL(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid icon URL") { - t.Errorf("validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) - } - } - - for _, test := range successTest { - badChart.Icon = test - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartIconURL(%s) to return no error, got %s", test, err.Error()) - } - } -} - -func TestChartfile(t *testing.T) { - t.Run("Chart.yaml basic validity issues", func(t *testing.T) { - linter := support.Linter{ChartDir: badChartDir} - Chartfile(&linter) - msgs := linter.Messages - expectedNumberOfErrorMessages := 6 - - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "name is required") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - - if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") { - t.Errorf("Unexpected message 3: %s", msgs[3].Err) - } - - if !strings.Contains(msgs[4].Err.Error(), "chart type is not valid in apiVersion") { - t.Errorf("Unexpected message 4: %s", msgs[4].Err) - } - - if !strings.Contains(msgs[5].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - t.Errorf("Unexpected message 5: %s", msgs[5].Err) - } - }) - - t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) { - linter := support.Linter{ChartDir: anotherBadChartDir} - Chartfile(&linter) - msgs := linter.Messages - expectedNumberOfErrorMessages := 3 - - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - }) -} diff --git a/pkg/lint/rules/dependencies.go b/pkg/lint/rules/dependencies.go deleted file mode 100644 index abecd1feb..000000000 --- a/pkg/lint/rules/dependencies.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Dependencies runs lints against a chart's dependencies -// -// See https://github.com/helm/helm/issues/7910 -func Dependencies(linter *support.Linter) { - c, err := loader.LoadDir(linter.ChartDir) - if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) { - return - } - - linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c)) - linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c)) -} - -func validateChartFormat(chartError error) error { - if chartError != nil { - return errors.Errorf("unable to load chart\n\t%s", chartError) - } - return nil -} - -func validateDependencyInChartsDir(c *chart.Chart) (err error) { - dependencies := map[string]struct{}{} - missing := []string{} - for _, dep := range c.Dependencies() { - dependencies[dep.Metadata.Name] = struct{}{} - } - for _, dep := range c.Metadata.Dependencies { - if _, ok := dependencies[dep.Name]; !ok { - missing = append(missing, dep.Name) - } - } - if len(missing) > 0 { - err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ",")) - } - return err -} - -func validateDependencyInMetadata(c *chart.Chart) (err error) { - dependencies := map[string]struct{}{} - missing := []string{} - for _, dep := range c.Metadata.Dependencies { - dependencies[dep.Name] = struct{}{} - } - for _, dep := range c.Dependencies() { - if _, ok := dependencies[dep.Metadata.Name]; !ok { - missing = append(missing, dep.Metadata.Name) - } - } - if len(missing) > 0 { - err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ",")) - } - return err -} diff --git a/pkg/lint/rules/dependencies_test.go b/pkg/lint/rules/dependencies_test.go deleted file mode 100644 index 075190eac..000000000 --- a/pkg/lint/rules/dependencies_test.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -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 rules - -import ( - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -func chartWithBadDependencies() chart.Chart { - badChartDeps := chart.Chart{ - Metadata: &chart.Metadata{ - Name: "badchart", - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{ - { - Name: "sub2", - }, - { - Name: "sub3", - }, - }, - }, - } - - badChartDeps.SetDependencies( - &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "sub1", - Version: "0.1.0", - APIVersion: "v2", - }, - }, - &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "sub2", - Version: "0.1.0", - APIVersion: "v2", - }, - }, - ) - return badChartDeps -} - -func TestValidateDependencyInChartsDir(t *testing.T) { - c := chartWithBadDependencies() - - if err := validateDependencyInChartsDir(&c); err == nil { - t.Error("chart should have been flagged for missing deps in chart directory") - } -} - -func TestValidateDependencyInMetadata(t *testing.T) { - c := chartWithBadDependencies() - - if err := validateDependencyInMetadata(&c); err == nil { - t.Errorf("chart should have been flagged for missing deps in chart metadata") - } -} - -func TestDependencies(t *testing.T) { - tmp := ensure.TempDir(t) - defer os.RemoveAll(tmp) - - c := chartWithBadDependencies() - err := chartutil.SaveDir(&c, tmp) - if err != nil { - t.Fatal(err) - } - linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)} - - Dependencies(&linter) - if l := len(linter.Messages); l != 2 { - t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l) - for i, msg := range linter.Messages { - t.Logf("Message: %d, Error: %#v", i, msg) - } - } -} diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go deleted file mode 100644 index ce19b91d5..000000000 --- a/pkg/lint/rules/deprecations.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import ( - "fmt" - "strconv" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/endpoints/deprecation" - kscheme "k8s.io/client-go/kubernetes/scheme" -) - -var ( - // This should be set in the Makefile based on the version of client-go being imported. - // These constants will be overwritten with LDFLAGS. The version components must be - // strings in order for LDFLAGS to set them. - k8sVersionMajor = "1" - k8sVersionMinor = "20" -) - -// deprecatedAPIError indicates than an API is deprecated in Kubernetes -type deprecatedAPIError struct { - Deprecated string - Message string -} - -func (e deprecatedAPIError) Error() string { - msg := e.Message - return msg -} - -func validateNoDeprecations(resource *K8sYamlStruct) error { - // if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation - if resource.APIVersion == "" { - return nil - } - if resource.Kind == "" { - return nil - } - - runtimeObject, err := resourceToRuntimeObject(resource) - if err != nil { - // do not error for non-kubernetes resources - if runtime.IsNotRegisteredError(err) { - return nil - } - return err - } - maj, err := strconv.Atoi(k8sVersionMajor) - if err != nil { - return err - } - min, err := strconv.Atoi(k8sVersionMinor) - if err != nil { - return err - } - - if !deprecation.IsDeprecated(runtimeObject, maj, min) { - return nil - } - gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) - return deprecatedAPIError{ - Deprecated: gvk, - Message: deprecation.WarningMessage(runtimeObject), - } -} - -func resourceToRuntimeObject(resource *K8sYamlStruct) (runtime.Object, error) { - scheme := runtime.NewScheme() - kscheme.AddToScheme(scheme) - - gvk := schema.FromAPIVersionAndKind(resource.APIVersion, resource.Kind) - out, err := scheme.New(gvk) - if err != nil { - return nil, err - } - out.GetObjectKind().SetGroupVersionKind(gvk) - return out, nil -} diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go deleted file mode 100644 index 96e072d14..000000000 --- a/pkg/lint/rules/deprecations_test.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import "testing" - -func TestValidateNoDeprecations(t *testing.T) { - deprecated := &K8sYamlStruct{ - APIVersion: "extensions/v1beta1", - Kind: "Deployment", - } - err := validateNoDeprecations(deprecated) - if err == nil { - t.Fatal("Expected deprecated extension to be flagged") - } - depErr := err.(deprecatedAPIError) - if depErr.Message == "" { - t.Fatalf("Expected error message to be non-blank: %v", err) - } - - if err := validateNoDeprecations(&K8sYamlStruct{ - APIVersion: "v1", - Kind: "Pod", - }); err != nil { - t.Errorf("Expected a v1 Pod to not be deprecated") - } -} diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go deleted file mode 100644 index 61425f92e..000000000 --- a/pkg/lint/rules/template.go +++ /dev/null @@ -1,339 +0,0 @@ -/* -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 rules - -import ( - "bufio" - "bytes" - "fmt" - "io" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/api/validation" - apipath "k8s.io/apimachinery/pkg/api/validation/path" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apimachinery/pkg/util/yaml" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "helm.sh/helm/v3/pkg/lint/support" -) - -var ( - crdHookSearch = regexp.MustCompile(`"?helm\.sh/hook"?:\s+crd-install`) - releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`) -) - -// Templates lints the templates in the Linter. -func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { - fpath := "templates/" - templatesPath := filepath.Join(linter.ChartDir, fpath) - - templatesDirExist := linter.RunLinterRule(support.WarningSev, fpath, validateTemplatesDir(templatesPath)) - - // Templates directory is optional for now - if !templatesDirExist { - return - } - - // Load chart and parse templates - chart, err := loader.Load(linter.ChartDir) - - chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err) - - if !chartLoaded { - return - } - - options := chartutil.ReleaseOptions{ - Name: "test-release", - Namespace: namespace, - } - - // lint ignores import-values - // See https://github.com/helm/helm/issues/9658 - if err := chartutil.ProcessDependencies(chart, values); err != nil { - return - } - - cvals, err := chartutil.CoalesceValues(chart, values) - if err != nil { - return - } - valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil) - if err != nil { - linter.RunLinterRule(support.ErrorSev, fpath, err) - return - } - var e engine.Engine - e.LintMode = true - renderedContentMap, err := e.Render(chart, valuesToRender) - - renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err) - - if !renderOk { - return - } - - /* Iterate over all the templates to check: - - It is a .yaml file - - All the values in the template file is defined - - {{}} include | quote - - Generated content is a valid Yaml file - - Metadata.Namespace is not set - */ - for _, template := range chart.Templates { - fileName, data := template.Name, template.Data - fpath = fileName - - linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName)) - // These are v3 specific checks to make sure and warn people if their - // chart is not compatible with v3 - linter.RunLinterRule(support.WarningSev, fpath, validateNoCRDHooks(data)) - linter.RunLinterRule(support.ErrorSev, fpath, validateNoReleaseTime(data)) - - // We only apply the following lint rules to yaml files - if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { - continue - } - - // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463 - // Check that all the templates have a matching value - // linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) - - // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037 - // linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate))) - - renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] - if strings.TrimSpace(renderedContent) != "" { - linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent)) - - decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096) - - // Lint all resources if the file contains multiple documents separated by --- - for { - // Even though K8sYamlStruct only defines a few fields, an error in any other - // key will be raised as well - var yamlStruct *K8sYamlStruct - - err := decoder.Decode(&yamlStruct) - if err == io.EOF { - break - } - - // If YAML linting fails, we sill progress. So we don't capture the returned state - // on this linter run. - linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) - - if yamlStruct != nil { - // NOTE: set to warnings to allow users to support out-of-date kubernetes - // Refs https://github.com/helm/helm/issues/8596 - linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct)) - linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct)) - - linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent)) - linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent)) - } - } - } - } -} - -// validateTopIndentLevel checks that the content does not start with an indent level > 0. -// -// This error can occur when a template accidentally inserts space. It can cause -// unpredictable errors depending on whether the text is normalized before being passed -// into the YAML parser. So we trap it here. -// -// See https://github.com/helm/helm/issues/8467 -func validateTopIndentLevel(content string) error { - // Read lines until we get to a non-empty one - scanner := bufio.NewScanner(bytes.NewBufferString(content)) - for scanner.Scan() { - line := scanner.Text() - // If line is empty, skip - if strings.TrimSpace(line) == "" { - continue - } - // If it starts with one or more spaces, this is an error - if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { - return fmt.Errorf("document starts with an illegal indent: %q, which may cause parsing problems", line) - } - // Any other condition passes. - return nil - } - return scanner.Err() -} - -// Validation functions -func validateTemplatesDir(templatesPath string) error { - if fi, err := os.Stat(templatesPath); err != nil { - return errors.New("directory not found") - } else if !fi.IsDir() { - return errors.New("not a directory") - } - return nil -} - -func validateAllowedExtension(fileName string) error { - ext := filepath.Ext(fileName) - validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"} - - for _, b := range validExtensions { - if b == ext { - return nil - } - } - - return errors.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) -} - -func validateYamlContent(err error) error { - return errors.Wrap(err, "unable to parse YAML") -} - -// validateMetadataName uses the correct validation function for the object -// Kind, or if not set, defaults to the standard definition of a subdomain in -// DNS (RFC 1123), used by most resources. -func validateMetadataName(obj *K8sYamlStruct) error { - fn := validateMetadataNameFunc(obj) - allErrs := field.ErrorList{} - for _, msg := range fn(obj.Metadata.Name, false) { - allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), obj.Metadata.Name, msg)) - } - if len(allErrs) > 0 { - return errors.Wrapf(allErrs.ToAggregate(), "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name) - } - return nil -} - -// validateMetadataNameFunc will return a name validation function for the -// object kind, if defined below. -// -// Rules should match those set in the various api validations: -// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/core/validation/validation.go#L205-L274 -// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/apps/validation/validation.go#L39 -// ... -// -// Implementing here to avoid importing k/k. -// -// If no mapping is defined, returns NameIsDNSSubdomain. This is used by object -// kinds that don't have special requirements, so is the most likely to work if -// new kinds are added. -func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc { - switch strings.ToLower(obj.Kind) { - case "pod", "node", "secret", "endpoints", "resourcequota", // core - "controllerrevision", "daemonset", "deployment", "replicaset", "statefulset", // apps - "autoscaler", // autoscaler - "cronjob", "job", // batch - "lease", // coordination - "endpointslice", // discovery - "networkpolicy", "ingress", // networking - "podsecuritypolicy", // policy - "priorityclass", // scheduling - "podpreset", // settings - "storageclass", "volumeattachment", "csinode": // storage - return validation.NameIsDNSSubdomain - case "service": - return validation.NameIsDNS1035Label - case "namespace": - return validation.ValidateNamespaceName - case "serviceaccount": - return validation.ValidateServiceAccountName - case "certificatesigningrequest": - // No validation. - // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/certificates/validation/validation.go#L137-L140 - return func(name string, prefix bool) []string { return nil } - case "role", "clusterrole", "rolebinding", "clusterrolebinding": - // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 - return func(name string, prefix bool) []string { - return apipath.IsValidPathSegmentName(name) - } - default: - return validation.NameIsDNSSubdomain - } -} - -func validateNoCRDHooks(manifest []byte) error { - if crdHookSearch.Match(manifest) { - return errors.New("manifest is a crd-install hook. This hook is no longer supported in v3 and all CRDs should also exist the crds/ directory at the top level of the chart") - } - return nil -} - -func validateNoReleaseTime(manifest []byte) error { - if releaseTimeSearch.Match(manifest) { - return errors.New(".Release.Time has been removed in v3, please replace with the `now` function in your templates") - } - return nil -} - -// validateMatchSelector ensures that template specs have a selector declared. -// See https://github.com/helm/helm/issues/1990 -func validateMatchSelector(yamlStruct *K8sYamlStruct, manifest string) error { - switch yamlStruct.Kind { - case "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet": - // verify that matchLabels or matchExpressions is present - if !(strings.Contains(manifest, "matchLabels") || strings.Contains(manifest, "matchExpressions")) { - return fmt.Errorf("a %s must contain matchLabels or matchExpressions, and %q does not", yamlStruct.Kind, yamlStruct.Metadata.Name) - } - } - return nil -} -func validateListAnnotations(yamlStruct *K8sYamlStruct, manifest string) error { - if yamlStruct.Kind == "List" { - m := struct { - Items []struct { - Metadata struct { - Annotations map[string]string - } - } - }{} - - if err := yaml.Unmarshal([]byte(manifest), &m); err != nil { - return validateYamlContent(err) - } - - for _, i := range m.Items { - if _, ok := i.Metadata.Annotations["helm.sh/resource-policy"]; ok { - return errors.New("Annotation 'helm.sh/resource-policy' within List objects are ignored") - } - } - } - return nil -} - -// K8sYamlStruct stubs a Kubernetes YAML file. -// -// DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within -// the rules package. -type K8sYamlStruct struct { - APIVersion string `json:"apiVersion"` - Kind string - Metadata k8sYamlMetadata -} - -type k8sYamlMetadata struct { - Namespace string - Name string -} diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go deleted file mode 100644 index f3aa641f2..000000000 --- a/pkg/lint/rules/template_test.go +++ /dev/null @@ -1,464 +0,0 @@ -/* -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 rules - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -const templateTestBasedir = "./testdata/albatross" - -func TestValidateAllowedExtension(t *testing.T) { - var failTest = []string{"/foo", "/test.toml"} - for _, test := range failTest { - err := validateAllowedExtension(test) - if err == nil || !strings.Contains(err.Error(), "Valid extensions are .yaml, .yml, .tpl, or .txt") { - t.Errorf("validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) - } - } - var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} - for _, test := range successTest { - err := validateAllowedExtension(test) - if err != nil { - t.Errorf("validateAllowedExtension('%s') to return no error but got \"%s\"", test, err.Error()) - } - } -} - -var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} - -const namespace = "testNamespace" -const strict = false - -func TestTemplateParsing(t *testing.T) { - linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 1 { - t.Fatalf("Expected one error, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("Unexpected error: %s", res[0]) - } -} - -var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") -var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") - -// Test a template with all the existing features: -// namespaces, partial templates -func TestTemplateIntegrationHappyPath(t *testing.T) { - // Rename file so it gets ignored by the linter - os.Rename(wrongTemplatePath, ignoredTemplatePath) - defer os.Rename(ignoredTemplatePath, wrongTemplatePath) - - linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 0 { - t.Fatalf("Expected no error, got %d, %v", len(res), res) - } -} - -func TestV3Fail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/v3-fail"} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 3 { - t.Fatalf("Expected 3 errors, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") { - t.Errorf("Unexpected error: %s", res[0].Err) - } - if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[1].Err) - } - if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[2].Err) - } -} - -func TestMultiTemplateFail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 1 { - t.Fatalf("Expected 1 error, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") { - t.Errorf("Unexpected error: %s", res[0].Err) - } -} - -func TestValidateMetadataName(t *testing.T) { - tests := []struct { - obj *K8sYamlStruct - wantErr bool - }{ - // Most kinds use IsDNS1123Subdomain. - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: ""}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "FOO"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one-two"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "-two"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one_two"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "a..b"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "FOO"}}, true}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "operator:sa"}}, true}, - - // Service uses IsDNS1035Label. - {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "123baz"}}, true}, - {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true}, - - // Namespace uses IsDNS1123Label. - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true}, - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo-bar"}}, false}, - - // CertificateSigningRequest has no validation. - {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: ""}}, false}, - {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, false}, - - // RBAC uses path validation. - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true}, - {&K8sYamlStruct{Kind: "RoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - - // Unknown Kind - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: ""}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "FOO"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one-two"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "-two"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one_two"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "a..b"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true}, - - // No kind - {&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true}, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) { - if err := validateMetadataName(tt.obj); (err != nil) != tt.wantErr { - t.Errorf("validateMetadataName() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestDeprecatedAPIFails(t *testing.T) { - mychart := chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "failapi", - Version: "0.1.0", - Icon: "satisfy-the-linting-gods.gif", - }, - Templates: []*chart.File{ - { - Name: "templates/baddeployment.yaml", - Data: []byte("apiVersion: apps/v1beta1\nkind: Deployment\nmetadata:\n name: baddep\nspec: {selector: {matchLabels: {foo: bar}}}"), - }, - { - Name: "templates/goodsecret.yaml", - Data: []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: goodsecret"), - }, - }, - } - tmpdir := ensure.TempDir(t) - defer os.RemoveAll(tmpdir) - - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } - - linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) - if l := len(linter.Messages); l != 1 { - for i, msg := range linter.Messages { - t.Logf("Message %d: %s", i, msg) - } - t.Fatalf("Expected 1 lint error, got %d", l) - } - - err := linter.Messages[0].Err.(deprecatedAPIError) - if err.Deprecated != "apps/v1beta1 Deployment" { - t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) - } -} - -const manifest = `apiVersion: v1 -kind: ConfigMap -metadata: - name: foo -data: - myval1: {{default "val" .Values.mymap.key1 }} - myval2: {{default "val" .Values.mymap.key2 }} -` - -// TestStrictTemplateParsingMapError is a regression test. -// -// The template engine should not produce an error when a map in values.yaml does -// not contain all possible keys. -// -// See https://github.com/helm/helm/issues/7483 -func TestStrictTemplateParsingMapError(t *testing.T) { - - ch := chart.Chart{ - Metadata: &chart.Metadata{ - Name: "regression7483", - APIVersion: "v2", - Version: "0.1.0", - }, - Values: map[string]interface{}{ - "mymap": map[string]string{ - "key1": "val1", - }, - }, - Templates: []*chart.File{ - { - Name: "templates/configmap.yaml", - Data: []byte(manifest), - }, - }, - } - dir := ensure.TempDir(t) - defer os.RemoveAll(dir) - if err := chartutil.SaveDir(&ch, dir); err != nil { - t.Fatal(err) - } - linter := &support.Linter{ - ChartDir: filepath.Join(dir, ch.Metadata.Name), - } - Templates(linter, ch.Values, namespace, strict) - if len(linter.Messages) != 0 { - t.Errorf("expected zero messages, got %d", len(linter.Messages)) - for i, msg := range linter.Messages { - t.Logf("Message %d: %q", i, msg) - } - } -} - -func TestValidateMatchSelector(t *testing.T) { - md := &K8sYamlStruct{ - APIVersion: "apps/v1", - Kind: "Deployment", - Metadata: k8sYamlMetadata{ - Name: "mydeployment", - }, - } - manifest := ` - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } - manifest = ` - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - selector: - matchExpressions: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } - manifest = ` - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ` - if err := validateMatchSelector(md, manifest); err == nil { - t.Error("expected Deployment with no selector to fail") - } -} - -func TestValidateTopIndentLevel(t *testing.T) { - for doc, shouldFail := range map[string]bool{ - // Should not fail - "\n\n\n\t\n \t\n": false, - "apiVersion:foo\n bar:baz": false, - "\n\n\napiVersion:foo\n\n\n": false, - // Should fail - " apiVersion:foo": true, - "\n\n apiVersion:foo\n\n": true, - } { - if err := validateTopIndentLevel(doc); (err == nil) == shouldFail { - t.Errorf("Expected %t for %q", shouldFail, doc) - } - } - -} - -// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments -// See https://github.com/helm/helm/issues/8621 -func TestEmptyWithCommentsManifests(t *testing.T) { - mychart := chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "emptymanifests", - Version: "0.1.0", - Icon: "satisfy-the-linting-gods.gif", - }, - Templates: []*chart.File{ - { - Name: "templates/empty-with-comments.yaml", - Data: []byte("#@formatter:off\n"), - }, - }, - } - tmpdir := ensure.TempDir(t) - defer os.RemoveAll(tmpdir) - - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } - - linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) - if l := len(linter.Messages); l > 0 { - for i, msg := range linter.Messages { - t.Logf("Message %d: %s", i, msg) - } - t.Fatalf("Expected 0 lint errors, got %d", l) - } -} -func TestValidateListAnnotations(t *testing.T) { - md := &K8sYamlStruct{ - APIVersion: "v1", - Kind: "List", - Metadata: k8sYamlMetadata{ - Name: "list", - }, - } - manifest := ` -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ConfigMap - metadata: - annotations: - helm.sh/resource-policy: keep -` - - if err := validateListAnnotations(md, manifest); err == nil { - t.Fatal("expected list with nested keep annotations to fail") - } - - manifest = ` -apiVersion: v1 -kind: List -metadata: - annotations: - helm.sh/resource-policy: keep -items: - - apiVersion: v1 - kind: ConfigMap -` - - if err := validateListAnnotations(md, manifest); err != nil { - t.Fatalf("List objects keep annotations should pass. got: %s", err) - } -} diff --git a/pkg/lint/rules/testdata/albatross/Chart.yaml b/pkg/lint/rules/testdata/albatross/Chart.yaml deleted file mode 100644 index 21124acfc..000000000 --- a/pkg/lint/rules/testdata/albatross/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: albatross -description: testing chart -version: 199.44.12345-Alpha.1+cafe009 -icon: http://riverrun.io diff --git a/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl b/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl deleted file mode 100644 index 24f76db73..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl +++ /dev/null @@ -1,16 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{define "name"}}{{default "nginx" .Values.nameOverride | trunc 63 | trimSuffix "-" }}{{end}} - -{{/* -Create a default fully qualified app name. - -We truncate at 63 chars because some Kubernetes name fields are limited to this -(by the DNS naming spec). -*/}} -{{define "fullname"}} -{{- $name := default "nginx" .Values.nameOverride -}} -{{printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{end}} diff --git a/pkg/lint/rules/testdata/albatross/templates/fail.yaml b/pkg/lint/rules/testdata/albatross/templates/fail.yaml deleted file mode 100644 index a11e0e90e..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/fail.yaml +++ /dev/null @@ -1 +0,0 @@ -{{ deliberateSyntaxError }} diff --git a/pkg/lint/rules/testdata/albatross/templates/svc.yaml b/pkg/lint/rules/testdata/albatross/templates/svc.yaml deleted file mode 100644 index 16bb27d55..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/svc.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# This is a service gateway to the replica set created by the deployment. -# Take a look at the deployment.yaml for general notes about this chart. -apiVersion: v1 -kind: Service -metadata: - name: "{{ .Values.name }}" - labels: - app.kubernetes.io/managed-by: {{ .Release.Service | quote }} - app.kubernetes.io/instance: {{ .Release.Name | quote }} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" - kubeVersion: {{ .Capabilities.KubeVersion.Major }} -spec: - ports: - - port: {{default 80 .Values.httpPort | quote}} - targetPort: 80 - protocol: TCP - name: http - selector: - app.kubernetes.io/name: {{template "fullname" .}} diff --git a/pkg/lint/rules/testdata/albatross/values.yaml b/pkg/lint/rules/testdata/albatross/values.yaml deleted file mode 100644 index 74cc6a0dc..000000000 --- a/pkg/lint/rules/testdata/albatross/values.yaml +++ /dev/null @@ -1 +0,0 @@ -name: "mariner" diff --git a/pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml b/pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml deleted file mode 100644 index e6bac7693..000000000 --- a/pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: "some-chart" -apiVersion: v2 -description: A Helm chart for Kubernetes -version: 72445e2 -home: "" -type: application -appVersion: 72225e2 -icon: "https://some-url.com/icon.jpeg" -dependencies: - - name: mariadb - version: 5.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - database diff --git a/pkg/lint/rules/testdata/badchartfile/Chart.yaml b/pkg/lint/rules/testdata/badchartfile/Chart.yaml deleted file mode 100644 index 3564ede3e..000000000 --- a/pkg/lint/rules/testdata/badchartfile/Chart.yaml +++ /dev/null @@ -1,11 +0,0 @@ -description: A Helm chart for Kubernetes -version: 0.0.0.0 -home: "" -type: application -dependencies: -- name: mariadb - version: 5.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - database diff --git a/pkg/lint/rules/testdata/badchartfile/values.yaml b/pkg/lint/rules/testdata/badchartfile/values.yaml deleted file mode 100644 index 9f367033b..000000000 --- a/pkg/lint/rules/testdata/badchartfile/values.yaml +++ /dev/null @@ -1 +0,0 @@ -# Default values for badchartfile. diff --git a/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml b/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml deleted file mode 100644 index 632919d03..000000000 --- a/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -name: badvaluesfile -description: A Helm chart for Kubernetes -version: 0.0.1 -home: "" -icon: http://riverrun.io diff --git a/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml b/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml deleted file mode 100644 index 6c2ceb8db..000000000 --- a/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{.name | default "foo" | title}} diff --git a/pkg/lint/rules/testdata/badvaluesfile/values.yaml b/pkg/lint/rules/testdata/badvaluesfile/values.yaml deleted file mode 100644 index b5a10271c..000000000 --- a/pkg/lint/rules/testdata/badvaluesfile/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Invalid value for badvaluesfile for testing lint fails with invalid yaml format -name= "value" diff --git a/pkg/lint/rules/testdata/goodone/Chart.yaml b/pkg/lint/rules/testdata/goodone/Chart.yaml deleted file mode 100644 index cb7a4bf20..000000000 --- a/pkg/lint/rules/testdata/goodone/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: goodone -description: good testing chart -version: 199.44.12345-Alpha.1+cafe009 -icon: http://riverrun.io diff --git a/pkg/lint/rules/testdata/goodone/templates/goodone.yaml b/pkg/lint/rules/testdata/goodone/templates/goodone.yaml deleted file mode 100644 index cd46f62c7..000000000 --- a/pkg/lint/rules/testdata/goodone/templates/goodone.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{ .Values.name | default "foo" | lower }} diff --git a/pkg/lint/rules/testdata/goodone/values.yaml b/pkg/lint/rules/testdata/goodone/values.yaml deleted file mode 100644 index 92c3d9bb9..000000000 --- a/pkg/lint/rules/testdata/goodone/values.yaml +++ /dev/null @@ -1 +0,0 @@ -name: "goodone-here" diff --git a/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml b/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml deleted file mode 100644 index b57427de9..000000000 --- a/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v2 -name: multi-template-fail -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application and it is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml b/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml deleted file mode 100644 index 835be07be..000000000 --- a/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: game-config -data: - game.properties: cheat ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: -this:name-is-not_valid$ -data: - game.properties: empty diff --git a/pkg/lint/rules/testdata/v3-fail/Chart.yaml b/pkg/lint/rules/testdata/v3-fail/Chart.yaml deleted file mode 100644 index 7097e17d8..000000000 --- a/pkg/lint/rules/testdata/v3-fail/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v2 -name: v3-fail -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application and it is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl b/pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl deleted file mode 100644 index 0b89e723b..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl +++ /dev/null @@ -1,63 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "v3-fail.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "v3-fail.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "v3-fail.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Common labels -*/}} -{{- define "v3-fail.labels" -}} -helm.sh/chart: {{ include "v3-fail.chart" . }} -{{ include "v3-fail.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Selector labels -*/}} -{{- define "v3-fail.selectorLabels" -}} -app.kubernetes.io/name: {{ include "v3-fail.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} - -{{/* -Create the name of the service account to use -*/}} -{{- define "v3-fail.serviceAccountName" -}} -{{- if .Values.serviceAccount.create -}} - {{ default (include "v3-fail.fullname" .) .Values.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.serviceAccount.name }} -{{- end -}} -{{- end -}} diff --git a/pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml b/pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml deleted file mode 100644 index 6d651ab8e..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "v3-fail.fullname" . }} - labels: - nope: {{ .Release.Time }} - {{- include "v3-fail.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.replicaCount }} - selector: - matchLabels: - {{- include "v3-fail.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "v3-fail.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "v3-fail.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: 80 - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http - resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml b/pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml deleted file mode 100644 index 4790650d0..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml +++ /dev/null @@ -1,62 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "v3-fail.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "v3-fail.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - "helm.sh/hook": crd-install - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} - {{- end }} - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/pkg/lint/rules/testdata/v3-fail/templates/service.yaml b/pkg/lint/rules/testdata/v3-fail/templates/service.yaml deleted file mode 100644 index 79a0f40b0..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "v3-fail.fullname" . }} - annotations: - helm.sh/hook: crd-install - labels: - {{- include "v3-fail.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "v3-fail.selectorLabels" . | nindent 4 }} diff --git a/pkg/lint/rules/testdata/v3-fail/values.yaml b/pkg/lint/rules/testdata/v3-fail/values.yaml deleted file mode 100644 index 01d99b4e6..000000000 --- a/pkg/lint/rules/testdata/v3-fail/values.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# Default values for v3-fail. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - repository: nginx - pullPolicy: IfNotPresent - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 80 - -ingress: - enabled: false - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: [] - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -nodeSelector: {} - -tolerations: [] - -affinity: {} diff --git a/pkg/lint/rules/testdata/withsubchart/Chart.yaml b/pkg/lint/rules/testdata/withsubchart/Chart.yaml deleted file mode 100644 index 6648daf56..000000000 --- a/pkg/lint/rules/testdata/withsubchart/Chart.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v2 -name: withsubchart -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 -appVersion: "1.16.0" -icon: http://riverrun.io - -dependencies: - - name: subchart - version: 0.1.16 - repository: "file://../subchart" - import-values: - - child: subchart - parent: subchart - diff --git a/pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml b/pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml deleted file mode 100644 index 8610a4f25..000000000 --- a/pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v2 -name: subchart -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 -appVersion: "1.16.0" diff --git a/pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml b/pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml deleted file mode 100644 index 6cb6cc2af..000000000 --- a/pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{ .Values.subchart.name | lower }} diff --git a/pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml b/pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml deleted file mode 100644 index 422a359d5..000000000 --- a/pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -subchart: - name: subchart \ No newline at end of file diff --git a/pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml b/pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml deleted file mode 100644 index 6cb6cc2af..000000000 --- a/pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{ .Values.subchart.name | lower }} diff --git a/pkg/lint/rules/testdata/withsubchart/values.yaml b/pkg/lint/rules/testdata/withsubchart/values.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/lint/rules/values.go b/pkg/lint/rules/values.go deleted file mode 100644 index 79a294326..000000000 --- a/pkg/lint/rules/values.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -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 rules - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Values lints a chart's values.yaml file. -// -// This function is deprecated and will be removed in Helm 4. -func Values(linter *support.Linter) { - ValuesWithOverrides(linter, map[string]interface{}{}) -} - -// ValuesWithOverrides tests the values.yaml file. -// -// If a schema is present in the chart, values are tested against that. Otherwise, -// they are only tested for well-formedness. -// -// If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, values map[string]interface{}) { - file := "values.yaml" - vf := filepath.Join(linter.ChartDir, file) - fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) - - if !fileExists { - return - } - - linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, values)) -} - -func validateValuesFileExistence(valuesPath string) error { - _, err := os.Stat(valuesPath) - if err != nil { - return errors.Errorf("file does not exist") - } - return nil -} - -func validateValuesFile(valuesPath string, overrides map[string]interface{}) error { - values, err := chartutil.ReadValuesFile(valuesPath) - if err != nil { - return errors.Wrap(err, "unable to parse YAML") - } - - // Helm 3.0.0 carried over the values linting from Helm 2.x, which only tests the top - // level values against the top-level expectations. Subchart values are not linted. - // We could change that. For now, though, we retain that strategy, and thus can - // coalesce tables (like reuse-values does) instead of doing the full chart - // CoalesceValues - coalescedValues := chartutil.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) - coalescedValues = chartutil.CoalesceTables(coalescedValues, values) - - ext := filepath.Ext(valuesPath) - schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json" - schema, err := ioutil.ReadFile(schemaPath) - if len(schema) == 0 { - return nil - } - if err != nil { - return err - } - return chartutil.ValidateAgainstSingleSchema(coalescedValues, schema) -} diff --git a/pkg/lint/rules/values_test.go b/pkg/lint/rules/values_test.go deleted file mode 100644 index 23335cc01..000000000 --- a/pkg/lint/rules/values_test.go +++ /dev/null @@ -1,175 +0,0 @@ -/* -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 rules - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/internal/test/ensure" -) - -var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml") - -const testSchema = ` -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "helm values test schema", - "type": "object", - "additionalProperties": false, - "required": [ - "username", - "password" - ], - "properties": { - "username": { - "description": "Your username", - "type": "string" - }, - "password": { - "description": "Your password", - "type": "string" - } - } -} -` - -func TestValidateValuesYamlNotDirectory(t *testing.T) { - _ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm) - defer os.Remove(nonExistingValuesFilePath) - - err := validateValuesFileExistence(nonExistingValuesFilePath) - if err == nil { - t.Errorf("validateValuesFileExistence to return a linter error, got no error") - } -} - -func TestValidateValuesFileWellFormed(t *testing.T) { - badYaml := ` - not:well[]{}formed - ` - tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) - defer os.RemoveAll(tmpdir) - valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err == nil { - t.Fatal("expected values file to fail parsing") - } -} - -func TestValidateValuesFileSchema(t *testing.T) { - yaml := "username: admin\npassword: swordfish" - tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err != nil { - t.Fatalf("Failed validation with %s", err) - } -} - -func TestValidateValuesFileSchemaFailure(t *testing.T) { - // 1234 is an int, not a string. This should fail. - yaml := "username: 1234\npassword: swordfish" - tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - - err := validateValuesFile(valfile, map[string]interface{}{}) - if err == nil { - t.Fatal("expected values file to fail parsing") - } - - assert.Contains(t, err.Error(), "Expected: string, given: integer", "integer should be caught by schema") -} - -func TestValidateValuesFileSchemaOverrides(t *testing.T) { - yaml := "username: admin" - overrides := map[string]interface{}{ - "password": "swordfish", - } - tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, overrides); err != nil { - t.Fatalf("Failed validation with %s", err) - } -} - -func TestValidateValuesFile(t *testing.T) { - tests := []struct { - name string - yaml string - overrides map[string]interface{} - errorMessage string - }{ - { - name: "value added", - yaml: "username: admin", - overrides: map[string]interface{}{"password": "swordfish"}, - }, - { - name: "value not overridden", - yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser"}, - errorMessage: "Expected: string, given: null", - }, - { - name: "value overridden", - yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tmpdir := ensure.TempFile(t, "values.yaml", []byte(tt.yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - - err := validateValuesFile(valfile, tt.overrides) - - switch { - case err != nil && tt.errorMessage == "": - t.Errorf("Failed validation with %s", err) - case err == nil && tt.errorMessage != "": - t.Error("expected values file to fail parsing") - case err != nil && tt.errorMessage != "": - assert.Contains(t, err.Error(), tt.errorMessage, "Failed with unexpected error") - } - }) - } -} - -func createTestingSchema(t *testing.T, dir string) string { - t.Helper() - schemafile := filepath.Join(dir, "values.schema.json") - if err := ioutil.WriteFile(schemafile, []byte(testSchema), 0700); err != nil { - t.Fatalf("Failed to write schema to tmpdir: %s", err) - } - return schemafile -} diff --git a/pkg/lint/support/doc.go b/pkg/lint/support/doc.go deleted file mode 100644 index b9a9d0918..000000000 --- a/pkg/lint/support/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -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 support contains tools for linting charts. - -Linting is the process of testing charts for errors or warnings regarding -formatting, compilation, or standards compliance. -*/ -package support // import "helm.sh/helm/v3/pkg/lint/support" diff --git a/pkg/lint/support/message.go b/pkg/lint/support/message.go deleted file mode 100644 index 5efbc7a61..000000000 --- a/pkg/lint/support/message.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -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 support - -import "fmt" - -// Severity indicates the severity of a Message. -const ( - // UnknownSev indicates that the severity of the error is unknown, and should not stop processing. - UnknownSev = iota - // InfoSev indicates information, for example missing values.yaml file - InfoSev - // WarningSev indicates that something does not meet code standards, but will likely function. - WarningSev - // ErrorSev indicates that something will not likely function. - ErrorSev -) - -// sev matches the *Sev states. -var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"} - -// Linter encapsulates a linting run of a particular chart. -type Linter struct { - Messages []Message - // The highest severity of all the failing lint rules - HighestSeverity int - ChartDir string -} - -// Message describes an error encountered while linting. -type Message struct { - // Severity is one of the *Sev constants - Severity int - Path string - Err error -} - -func (m Message) Error() string { - return fmt.Sprintf("[%s] %s: %s", sev[m.Severity], m.Path, m.Err.Error()) -} - -// NewMessage creates a new Message struct -func NewMessage(severity int, path string, err error) Message { - return Message{Severity: severity, Path: path, Err: err} -} - -// RunLinterRule returns true if the validation passed -func (l *Linter) RunLinterRule(severity int, path string, err error) bool { - // severity is out of bound - if severity < 0 || severity >= len(sev) { - return false - } - - if err != nil { - l.Messages = append(l.Messages, NewMessage(severity, path, err)) - - if severity > l.HighestSeverity { - l.HighestSeverity = severity - } - } - return err == nil -} diff --git a/pkg/lint/support/message_test.go b/pkg/lint/support/message_test.go deleted file mode 100644 index 9e12a638b..000000000 --- a/pkg/lint/support/message_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -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 support - -import ( - "testing" - - "github.com/pkg/errors" -) - -var linter = Linter{} -var errLint = errors.New("lint failed") - -func TestRunLinterRule(t *testing.T) { - var tests = []struct { - Severity int - LintError error - ExpectedMessages int - ExpectedReturn bool - ExpectedHighestSeverity int - }{ - {InfoSev, errLint, 1, false, InfoSev}, - {WarningSev, errLint, 2, false, WarningSev}, - {ErrorSev, errLint, 3, false, ErrorSev}, - // No error so it returns true - {ErrorSev, nil, 3, true, ErrorSev}, - // Retains highest severity - {InfoSev, errLint, 4, false, ErrorSev}, - // Invalid severity values - {4, errLint, 4, false, ErrorSev}, - {22, errLint, 4, false, ErrorSev}, - {-1, errLint, 4, false, ErrorSev}, - } - - for _, test := range tests { - isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError) - if len(linter.Messages) != test.ExpectedMessages { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) - } - - if linter.HighestSeverity != test.ExpectedHighestSeverity { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity) - } - - if isValid != test.ExpectedReturn { - t.Errorf("RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid) - } - } -} - -func TestMessage(t *testing.T) { - m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} - if m.Error() != "[ERROR] Chart.yaml: Foo" { - t.Errorf("Unexpected output: %s", m.Error()) - } - - m = Message{WarningSev, "templates/", errors.New("Bar")} - if m.Error() != "[WARNING] templates/: Bar" { - t.Errorf("Unexpected output: %s", m.Error()) - } - - m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} - if m.Error() != "[INFO] templates/rc.yaml: FooBar" { - t.Errorf("Unexpected output: %s", m.Error()) - } -} diff --git a/pkg/plugin/cache/cache.go b/pkg/plugin/cache/cache.go deleted file mode 100644 index 5f3345b63..000000000 --- a/pkg/plugin/cache/cache.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -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 cache provides a key generator for vcs urls. -package cache // import "helm.sh/helm/v3/pkg/plugin/cache" - -import ( - "net/url" - "regexp" - "strings" -) - -// Thanks glide! - -// scpSyntaxRe matches the SCP-like addresses used to access repos over SSH. -var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`) - -// Key generates a cache key based on a url or scp string. The key is file -// system safe. -func Key(repo string) (string, error) { - var ( - u *url.URL - err error - ) - if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil { - // Match SCP-like syntax and convert it to a URL. - // Eg, "git@github.com:user/repo" becomes - // "ssh://git@github.com/user/repo". - u = &url.URL{ - User: url.User(m[1]), - Host: m[2], - Path: "/" + m[3], - } - } else { - u, err = url.Parse(repo) - if err != nil { - return "", err - } - } - - var key strings.Builder - if u.Scheme != "" { - key.WriteString(u.Scheme) - key.WriteString("-") - } - if u.User != nil && u.User.Username() != "" { - key.WriteString(u.User.Username()) - key.WriteString("-") - } - key.WriteString(u.Host) - if u.Path != "" { - key.WriteString(strings.ReplaceAll(u.Path, "/", "-")) - } - return strings.ReplaceAll(key.String(), ":", "-"), nil -} diff --git a/pkg/plugin/hooks.go b/pkg/plugin/hooks.go deleted file mode 100644 index e3481515f..000000000 --- a/pkg/plugin/hooks.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -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 diff --git a/pkg/plugin/installer/base.go b/pkg/plugin/installer/base.go deleted file mode 100644 index ba6a55d55..000000000 --- a/pkg/plugin/installer/base.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "path/filepath" - - "helm.sh/helm/v3/pkg/cli" -) - -type base struct { - // Source is the reference to a plugin - Source string - // PluginsDirectory is the directory where plugins are installed - PluginsDirectory string -} - -func newBase(source string) base { - settings := cli.New() - return base{ - Source: source, - PluginsDirectory: settings.PluginsDirectory, - } -} - -// Path is where the plugin will be installed. -func (b *base) Path() string { - if b.Source == "" { - return "" - } - return filepath.Join(b.PluginsDirectory, filepath.Base(b.Source)) -} diff --git a/pkg/plugin/installer/base_test.go b/pkg/plugin/installer/base_test.go deleted file mode 100644 index 38ef28c3e..000000000 --- a/pkg/plugin/installer/base_test.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "os" - "testing" -) - -func TestPath(t *testing.T) { - tests := []struct { - source string - helmPluginsDir string - expectPath string - }{ - { - source: "", - helmPluginsDir: "/helm/data/plugins", - expectPath: "", - }, { - source: "https://github.com/jkroepke/helm-secrets", - helmPluginsDir: "/helm/data/plugins", - expectPath: "/helm/data/plugins/helm-secrets", - }, - } - - for _, tt := range tests { - - os.Setenv("HELM_PLUGINS", tt.helmPluginsDir) - baseIns := newBase(tt.source) - baseInsPath := baseIns.Path() - if baseInsPath != tt.expectPath { - t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath) - } - os.Unsetenv("HELM_PLUGINS") - } -} diff --git a/pkg/plugin/installer/doc.go b/pkg/plugin/installer/doc.go deleted file mode 100644 index 3e3b2ebeb..000000000 --- a/pkg/plugin/installer/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* -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 installer provides an interface for installing Helm plugins. -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go deleted file mode 100644 index bcbcbde93..000000000 --- a/pkg/plugin/installer/http_installer.go +++ /dev/null @@ -1,268 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "io" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/plugin/cache" -) - -// HTTPInstaller installs plugins from an archive served by a web server. -type HTTPInstaller struct { - CacheDir string - PluginName string - base - extractor Extractor - getter getter.Getter -} - -// TarGzExtractor extracts gzip compressed tar archives -type TarGzExtractor struct{} - -// Extractor provides an interface for extracting archives -type Extractor interface { - Extract(buffer *bytes.Buffer, targetDir string) error -} - -// Extractors contains a map of suffixes and matching implementations of extractor to return -var Extractors = map[string]Extractor{ - ".tar.gz": &TarGzExtractor{}, - ".tgz": &TarGzExtractor{}, -} - -// Convert a media type to an extractor extension. -// -// This should be refactored in Helm 4, combined with the extension-based mechanism. -func mediaTypeToExtension(mt string) (string, bool) { - switch strings.ToLower(mt) { - case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar": - return ".tgz", true - default: - return "", false - } -} - -// NewExtractor creates a new extractor matching the source file name -func NewExtractor(source string) (Extractor, error) { - for suffix, extractor := range Extractors { - if strings.HasSuffix(source, suffix) { - return extractor, nil - } - } - return nil, errors.Errorf("no extractor implemented yet for %s", source) -} - -// NewHTTPInstaller creates a new HttpInstaller. -func NewHTTPInstaller(source string) (*HTTPInstaller, error) { - key, err := cache.Key(source) - if err != nil { - return nil, err - } - - extractor, err := NewExtractor(source) - if err != nil { - return nil, err - } - - get, err := getter.All(new(cli.EnvSettings)).ByScheme("http") - if err != nil { - return nil, err - } - - i := &HTTPInstaller{ - CacheDir: helmpath.CachePath("plugins", key), - PluginName: stripPluginName(filepath.Base(source)), - base: newBase(source), - extractor: extractor, - getter: get, - } - return i, nil -} - -// helper that relies on some sort of convention for plugin name (plugin-name-) -func stripPluginName(name string) string { - var strippedName string - for suffix := range Extractors { - if strings.HasSuffix(name, suffix) { - strippedName = strings.TrimSuffix(name, suffix) - break - } - } - re := regexp.MustCompile(`(.*)-[0-9]+\..*`) - return re.ReplaceAllString(strippedName, `$1`) -} - -// Install downloads and extracts the tarball into the cache directory -// and installs into the plugin directory. -// -// Implements Installer. -func (i *HTTPInstaller) Install() error { - pluginData, err := i.getter.Get(i.Source) - if err != nil { - return err - } - - if err := i.extractor.Extract(pluginData, i.CacheDir); err != nil { - return errors.Wrap(err, "extracting files from archive") - } - - if !isPlugin(i.CacheDir) { - return ErrMissingMetadata - } - - src, err := filepath.Abs(i.CacheDir) - if err != nil { - return err - } - - debug("copying %s to %s", src, i.Path()) - return fs.CopyDir(src, i.Path()) -} - -// Update updates a local repository -// Not implemented for now since tarball most likely will be packaged by version -func (i *HTTPInstaller) Update() error { - return errors.Errorf("method Update() not implemented for HttpInstaller") -} - -// Path is overridden because we want to join on the plugin name not the file name -func (i HTTPInstaller) Path() string { - if i.base.Source == "" { - return "" - } - return helmpath.DataPath("plugins", i.PluginName) -} - -// CleanJoin resolves dest as a subpath of root. -// -// This function runs several security checks on the path, generating an error if -// the supplied `dest` looks suspicious or would result in dubious behavior on the -// filesystem. -// -// CleanJoin assumes that any attempt by `dest` to break out of the CWD is an attempt -// to be malicious. (If you don't care about this, use the securejoin-filepath library.) -// It will emit an error if it detects paths that _look_ malicious, operating on the -// assumption that we don't actually want to do anything with files that already -// appear to be nefarious. -// -// - The character `:` is considered illegal because it is a separator on UNIX and a -// drive designator on Windows. -// - The path component `..` is considered suspicions, and therefore illegal -// - The character \ (backslash) is treated as a path separator and is converted to /. -// - Beginning a path with a path separator is illegal -// - Rudimentary symlink protects are offered by SecureJoin. -func cleanJoin(root, dest string) (string, error) { - - // On Windows, this is a drive separator. On UNIX-like, this is the path list separator. - // In neither case do we want to trust a TAR that contains these. - if strings.Contains(dest, ":") { - return "", errors.New("path contains ':', which is illegal") - } - - // The Go tar library does not convert separators for us. - // We assume here, as we do elsewhere, that `\\` means a Windows path. - dest = strings.ReplaceAll(dest, "\\", "/") - - // We want to alert the user that something bad was attempted. Cleaning it - // is not a good practice. - for _, part := range strings.Split(dest, "/") { - if part == ".." { - return "", errors.New("path contains '..', which is illegal") - } - } - - // If a path is absolute, the creator of the TAR is doing something shady. - if path.IsAbs(dest) { - return "", errors.New("path is absolute, which is illegal") - } - - // SecureJoin will do some cleaning, as well as some rudimentary checking of symlinks. - newpath, err := securejoin.SecureJoin(root, dest) - if err != nil { - return "", err - } - - return filepath.ToSlash(newpath), nil -} - -// Extract extracts compressed archives -// -// Implements Extractor. -func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { - uncompressedStream, err := gzip.NewReader(buffer) - if err != nil { - return err - } - - if err := os.MkdirAll(targetDir, 0755); err != nil { - return err - } - - tarReader := tar.NewReader(uncompressedStream) - for { - header, err := tarReader.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - path, err := cleanJoin(targetDir, header.Name) - if err != nil { - return err - } - - switch header.Typeflag { - case tar.TypeDir: - if err := os.Mkdir(path, 0755); err != nil { - return err - } - case tar.TypeReg: - outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) - if err != nil { - return err - } - if _, err := io.Copy(outFile, tarReader); err != nil { - outFile.Close() - return err - } - outFile.Close() - // We don't want to process these extension header files. - case tar.TypeXGlobalHeader, tar.TypeXHeader: - continue - default: - return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) - } - } - return nil -} diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go deleted file mode 100644 index 165007af0..000000000 --- a/pkg/plugin/installer/http_installer_test.go +++ /dev/null @@ -1,350 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "encoding/base64" - "fmt" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "syscall" - "testing" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" -) - -var _ Installer = new(HTTPInstaller) - -// Fake http client -type TestHTTPGetter struct { - MockResponse *bytes.Buffer - MockError error -} - -func (t *TestHTTPGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) { - return t.MockResponse, t.MockError -} - -// Fake plugin tarball data -var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA=" - -func TestStripName(t *testing.T) { - if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } - if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } - if stripPluginName("fake-plugin.tgz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } - if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } -} - -func mockArchiveServer() *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !strings.HasSuffix(r.URL.Path, ".tar.gz") { - w.Header().Add("Content-Type", "text/html") - fmt.Fprintln(w, "broken") - return - } - w.Header().Add("Content-Type", "application/gzip") - fmt.Fprintln(w, "test") - })) -} - -func TestHTTPInstaller(t *testing.T) { - defer ensure.HelmHome(t)() - - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - i, err := NewForSource(source, "0.0.1") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a HTTPInstaller was returned - httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } - - // inject fake http client responding with minimal plugin tarball - mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) - if err != nil { - t.Fatalf("Could not decode fake tgz plugin: %s", err) - } - - httpInstaller.getter = &TestHTTPGetter{ - MockResponse: bytes.NewBuffer(mockTgz), - } - - // install the plugin - if err := Install(i); err != nil { - t.Fatal(err) - } - if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) - } - - // Install again to test plugin exists error - if err := Install(i); err == nil { - t.Fatal("expected error for plugin exists, got none") - } else if err.Error() != "plugin already exists" { - t.Fatalf("expected error for plugin exists, got (%v)", err) - } - -} - -func TestHTTPInstallerNonExistentVersion(t *testing.T) { - defer ensure.HelmHome(t)() - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - i, err := NewForSource(source, "0.0.2") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a HTTPInstaller was returned - httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } - - // inject fake http client responding with error - httpInstaller.getter = &TestHTTPGetter{ - MockError: errors.Errorf("failed to download plugin for some reason"), - } - - // attempt to install the plugin - if err := Install(i); err == nil { - t.Fatal("expected error from http client") - } - -} - -func TestHTTPInstallerUpdate(t *testing.T) { - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - defer ensure.HelmHome(t)() - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - i, err := NewForSource(source, "0.0.1") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a HTTPInstaller was returned - httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } - - // inject fake http client responding with minimal plugin tarball - mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) - if err != nil { - t.Fatalf("Could not decode fake tgz plugin: %s", err) - } - - httpInstaller.getter = &TestHTTPGetter{ - MockResponse: bytes.NewBuffer(mockTgz), - } - - // install the plugin before updating - if err := Install(i); err != nil { - t.Fatal(err) - } - if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) - } - - // Update plugin, should fail because it is not implemented - if err := Update(i); err == nil { - t.Fatal("update method not implemented for http installer") - } -} - -func TestExtract(t *testing.T) { - source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" - - tempDir := t.TempDir() - - // Set the umask to default open permissions so we can actually test - oldmask := syscall.Umask(0000) - defer func() { - syscall.Umask(oldmask) - }() - - // Write a tarball to a buffer for us to extract - var tarbuf bytes.Buffer - tw := tar.NewWriter(&tarbuf) - var files = []struct { - Name, Body string - Mode int64 - }{ - {"plugin.yaml", "plugin metadata", 0600}, - {"README.md", "some text", 0777}, - } - for _, file := range files { - hdr := &tar.Header{ - Name: file.Name, - Typeflag: tar.TypeReg, - Mode: file.Mode, - Size: int64(len(file.Body)), - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } - } - - // Add pax global headers. This should be ignored. - // Note the PAX header that isn't global cannot be written using WriteHeader. - // Details are in the internal Go function for the tar packaged named - // allowedFormats. For a TypeXHeader it will return a message stating - // "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers" - if err := tw.WriteHeader(&tar.Header{ - Name: "pax_global_header", - Typeflag: tar.TypeXGlobalHeader, - }); err != nil { - t.Fatal(err) - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - gz := gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } - gz.Close() - // END tarball creation - - extractor, err := NewExtractor(source) - if err != nil { - t.Fatal(err) - } - - if err = extractor.Extract(&buf, tempDir); err != nil { - t.Fatalf("Did not expect error but got error: %v", err) - } - - pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") - if info, err := os.Stat(pluginYAMLFullPath); err != nil { - if os.IsNotExist(err) { - t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath) - } - t.Fatal(err) - } else if info.Mode().Perm() != 0600 { - t.Fatalf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm()) - } - - readmeFullPath := filepath.Join(tempDir, "README.md") - if info, err := os.Stat(readmeFullPath); err != nil { - if os.IsNotExist(err) { - t.Fatalf("Expected %s to exist but doesn't", readmeFullPath) - } - t.Fatal(err) - } else if info.Mode().Perm() != 0777 { - t.Fatalf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm()) - } - -} - -func TestCleanJoin(t *testing.T) { - for i, fixture := range []struct { - path string - expect string - expectError bool - }{ - {"foo/bar.txt", "/tmp/foo/bar.txt", false}, - {"/foo/bar.txt", "", true}, - {"./foo/bar.txt", "/tmp/foo/bar.txt", false}, - {"./././././foo/bar.txt", "/tmp/foo/bar.txt", false}, - {"../../../../foo/bar.txt", "", true}, - {"foo/../../../../bar.txt", "", true}, - {"c:/foo/bar.txt", "/tmp/c:/foo/bar.txt", true}, - {"foo\\bar.txt", "/tmp/foo/bar.txt", false}, - {"c:\\foo\\bar.txt", "", true}, - } { - out, err := cleanJoin("/tmp", fixture.path) - if err != nil { - if !fixture.expectError { - t.Errorf("Test %d: Path was not cleaned: %s", i, err) - } - continue - } - if fixture.expect != out { - t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) - } - } - -} - -func TestMediaTypeToExtension(t *testing.T) { - - for mt, shouldPass := range map[string]bool{ - "": false, - "application/gzip": true, - "application/x-gzip": true, - "application/x-tgz": true, - "application/x-gtar": true, - "application/json": false, - } { - ext, ok := mediaTypeToExtension(mt) - if ok != shouldPass { - t.Errorf("Media type %q failed test", mt) - } - if shouldPass && ext == "" { - t.Errorf("Expected an extension but got empty string") - } - if !shouldPass && len(ext) != 0 { - t.Error("Expected extension to be empty for unrecognized type") - } - } -} diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go deleted file mode 100644 index 6f01494e5..000000000 --- a/pkg/plugin/installer/installer.go +++ /dev/null @@ -1,135 +0,0 @@ -/* -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 installer - -import ( - "fmt" - "log" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/plugin" -) - -// ErrMissingMetadata indicates that plugin.yaml is missing. -var ErrMissingMetadata = errors.New("plugin metadata (plugin.yaml) missing") - -// Debug enables verbose output. -var Debug bool - -// Installer provides an interface for installing helm client plugins. -type Installer interface { - // Install adds a plugin. - Install() error - // Path is the directory of the installed plugin. - Path() string - // Update updates a plugin. - Update() error -} - -// Install installs a plugin. -func Install(i Installer) error { - if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil { - return err - } - if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { - return errors.New("plugin already exists") - } - return i.Install() -} - -// Update updates a plugin. -func Update(i Installer) error { - if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { - return errors.New("plugin does not exist") - } - return i.Update() -} - -// NewForSource determines the correct Installer for the given source. -func NewForSource(source, version string) (Installer, error) { - // Check if source is a local directory - if isLocalReference(source) { - return NewLocalInstaller(source) - } else if isRemoteHTTPArchive(source) { - return NewHTTPInstaller(source) - } - return NewVCSInstaller(source, version) -} - -// FindSource determines the correct Installer for the given source. -func FindSource(location string) (Installer, error) { - installer, err := existingVCSRepo(location) - if err != nil && err.Error() == "Cannot detect VCS" { - return installer, errors.New("cannot get information about plugin source") - } - return installer, err -} - -// isLocalReference checks if the source exists on the filesystem. -func isLocalReference(source string) bool { - _, err := os.Stat(source) - return err == nil -} - -// isRemoteHTTPArchive checks if the source is a http/https url and is an archive -// -// It works by checking whether the source looks like a URL and, if it does, running a -// HEAD operation to see if the remote resource is a file that we understand. -func isRemoteHTTPArchive(source string) bool { - if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { - res, err := http.Head(source) - if err != nil { - // If we get an error at the network layer, we can't install it. So - // we return false. - return false - } - - // Next, we look for the content type or content disposition headers to see - // if they have matching extractors. - contentType := res.Header.Get("content-type") - foundSuffix, ok := mediaTypeToExtension(contentType) - if !ok { - // Media type not recognized - return false - } - - for suffix := range Extractors { - if strings.HasSuffix(foundSuffix, suffix) { - return true - } - } - } - return false -} - -// isPlugin checks if the directory contains a plugin.yaml file. -func isPlugin(dirname string) bool { - _, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName)) - return err == nil -} - -var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile) - -func debug(format string, args ...interface{}) { - if Debug { - logger.Output(2, fmt.Sprintf(format, args...)) - } -} diff --git a/pkg/plugin/installer/installer_test.go b/pkg/plugin/installer/installer_test.go deleted file mode 100644 index a11464924..000000000 --- a/pkg/plugin/installer/installer_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -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 installer - -import "testing" - -func TestIsRemoteHTTPArchive(t *testing.T) { - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - - if isRemoteHTTPArchive("/not/a/URL") { - t.Errorf("Expected non-URL to return false") - } - - if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") { - t.Errorf("Bad URL should not have succeeded.") - } - - if !isRemoteHTTPArchive(source) { - t.Errorf("Expected %q to be a valid archive URL", source) - } - - if isRemoteHTTPArchive(source + "-not-an-extension") { - t.Error("Expected media type match to fail") - } -} diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go deleted file mode 100644 index c92bc3fb0..000000000 --- a/pkg/plugin/installer/local_installer.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "os" - "path/filepath" - - "github.com/pkg/errors" -) - -// LocalInstaller installs plugins from the filesystem. -type LocalInstaller struct { - base -} - -// NewLocalInstaller creates a new LocalInstaller. -func NewLocalInstaller(source string) (*LocalInstaller, error) { - src, err := filepath.Abs(source) - if err != nil { - return nil, errors.Wrap(err, "unable to get absolute path to plugin") - } - i := &LocalInstaller{ - base: newBase(src), - } - return i, nil -} - -// Install creates a symlink to the plugin directory. -// -// Implements Installer. -func (i *LocalInstaller) Install() error { - if !isPlugin(i.Source) { - return ErrMissingMetadata - } - debug("symlinking %s to %s", i.Source, i.Path()) - return os.Symlink(i.Source, i.Path()) -} - -// Update updates a local repository -func (i *LocalInstaller) Update() error { - debug("local repository is auto-updated") - return nil -} diff --git a/pkg/plugin/installer/local_installer_test.go b/pkg/plugin/installer/local_installer_test.go deleted file mode 100644 index 9b5cbf59e..000000000 --- a/pkg/plugin/installer/local_installer_test.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/pkg/helmpath" -) - -var _ Installer = new(LocalInstaller) - -func TestLocalInstaller(t *testing.T) { - // Make a temp dir - tdir := t.TempDir() - if err := ioutil.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil { - t.Fatal(err) - } - - source := "../testdata/plugdir/good/echo" - i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if err := Install(i); err != nil { - t.Fatal(err) - } - - if i.Path() != helmpath.DataPath("plugins", "echo") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) - } - defer os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm -} diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go deleted file mode 100644 index f7df5b322..000000000 --- a/pkg/plugin/installer/vcs_installer.go +++ /dev/null @@ -1,176 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "os" - "sort" - - "github.com/Masterminds/semver/v3" - "github.com/Masterminds/vcs" - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/plugin/cache" -) - -// VCSInstaller installs plugins from remote a repository. -type VCSInstaller struct { - Repo vcs.Repo - Version string - base -} - -func existingVCSRepo(location string) (Installer, error) { - repo, err := vcs.NewRepo("", location) - if err != nil { - return nil, err - } - i := &VCSInstaller{ - Repo: repo, - base: newBase(repo.Remote()), - } - return i, nil -} - -// NewVCSInstaller creates a new VCSInstaller. -func NewVCSInstaller(source, version string) (*VCSInstaller, error) { - key, err := cache.Key(source) - if err != nil { - return nil, err - } - cachedpath := helmpath.CachePath("plugins", key) - repo, err := vcs.NewRepo(source, cachedpath) - if err != nil { - return nil, err - } - i := &VCSInstaller{ - Repo: repo, - Version: version, - base: newBase(source), - } - return i, err -} - -// Install clones a remote repository and installs into the plugin directory. -// -// Implements Installer. -func (i *VCSInstaller) Install() error { - if err := i.sync(i.Repo); err != nil { - return err - } - - ref, err := i.solveVersion(i.Repo) - if err != nil { - return err - } - if ref != "" { - if err := i.setVersion(i.Repo, ref); err != nil { - return err - } - } - - if !isPlugin(i.Repo.LocalPath()) { - return ErrMissingMetadata - } - - debug("copying %s to %s", i.Repo.LocalPath(), i.Path()) - return fs.CopyDir(i.Repo.LocalPath(), i.Path()) -} - -// Update updates a remote repository -func (i *VCSInstaller) Update() error { - debug("updating %s", i.Repo.Remote()) - if i.Repo.IsDirty() { - return errors.New("plugin repo was modified") - } - if err := i.Repo.Update(); err != nil { - return err - } - if !isPlugin(i.Repo.LocalPath()) { - return ErrMissingMetadata - } - return nil -} - -func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { - if i.Version == "" { - return "", nil - } - - if repo.IsReference(i.Version) { - return i.Version, nil - } - - // Create the constraint first to make sure it's valid before - // working on the repo. - constraint, err := semver.NewConstraint(i.Version) - if err != nil { - return "", err - } - - // Get the tags - refs, err := repo.Tags() - if err != nil { - return "", err - } - debug("found refs: %s", refs) - - // Convert and filter the list to semver.Version instances - semvers := getSemVers(refs) - - // Sort semver list - sort.Sort(sort.Reverse(semver.Collection(semvers))) - for _, v := range semvers { - if constraint.Check(v) { - // If the constraint passes get the original reference - ver := v.Original() - debug("setting to %s", ver) - return ver, nil - } - } - - return "", errors.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote()) -} - -// setVersion attempts to checkout the version -func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error { - debug("setting version to %q", i.Version) - return repo.UpdateVersion(ref) -} - -// sync will clone or update a remote repo. -func (i *VCSInstaller) sync(repo vcs.Repo) error { - if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) { - debug("cloning %s to %s", repo.Remote(), repo.LocalPath()) - return repo.Get() - } - debug("updating %s", repo.Remote()) - return repo.Update() -} - -// Filter a list of versions to only included semantic versions. The response -// is a mapping of the original version to the semantic version. -func getSemVers(refs []string) []*semver.Version { - var sv []*semver.Version - for _, r := range refs { - if v, err := semver.NewVersion(r); err == nil { - sv = append(sv, v) - } - } - return sv -} diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go deleted file mode 100644 index 6785264b3..000000000 --- a/pkg/plugin/installer/vcs_installer_test.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/Masterminds/vcs" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/helmpath" -) - -var _ Installer = new(VCSInstaller) - -type testRepo struct { - local, remote, current string - tags, branches []string - err error - vcs.Repo -} - -func (r *testRepo) LocalPath() string { return r.local } -func (r *testRepo) Remote() string { return r.remote } -func (r *testRepo) Update() error { return r.err } -func (r *testRepo) Get() error { return r.err } -func (r *testRepo) IsReference(string) bool { return false } -func (r *testRepo) Tags() ([]string, error) { return r.tags, r.err } -func (r *testRepo) Branches() ([]string, error) { return r.branches, r.err } -func (r *testRepo) UpdateVersion(version string) error { - r.current = version - return r.err -} - -func TestVCSInstaller(t *testing.T) { - defer ensure.HelmHome(t)() - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") - repo := &testRepo{ - local: testRepoPath, - tags: []string{"0.1.0", "0.1.1"}, - } - - i, err := NewForSource(source, "~0.1.0") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a VCSInstaller was returned - vcsInstaller, ok := i.(*VCSInstaller) - if !ok { - t.Fatal("expected a VCSInstaller") - } - - // set the testRepo in the VCSInstaller - vcsInstaller.Repo = repo - - if err := Install(i); err != nil { - t.Fatal(err) - } - if repo.current != "0.1.1" { - t.Fatalf("expected version '0.1.1', got %q", repo.current) - } - if i.Path() != helmpath.DataPath("plugins", "helm-env") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) - } - - // Install again to test plugin exists error - if err := Install(i); err == nil { - t.Fatalf("expected error for plugin exists, got none") - } else if err.Error() != "plugin already exists" { - t.Fatalf("expected error for plugin exists, got (%v)", err) - } - - // Testing FindSource method, expect error because plugin code is not a cloned repository - if _, err := FindSource(i.Path()); err == nil { - t.Fatalf("expected error for inability to find plugin source, got none") - } else if err.Error() != "cannot get information about plugin source" { - t.Fatalf("expected error for inability to find plugin source, got (%v)", err) - } -} - -func TestVCSInstallerNonExistentVersion(t *testing.T) { - defer ensure.HelmHome(t)() - - source := "https://github.com/adamreese/helm-env" - version := "0.2.0" - - i, err := NewForSource(source, version) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a VCSInstaller was returned - if _, ok := i.(*VCSInstaller); !ok { - t.Fatal("expected a VCSInstaller") - } - - if err := Install(i); err == nil { - t.Fatalf("expected error for version does not exists, got none") - } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { - t.Fatalf("expected error for version does not exists, got (%v)", err) - } -} -func TestVCSInstallerUpdate(t *testing.T) { - defer ensure.HelmHome(t)() - - source := "https://github.com/adamreese/helm-env" - - i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a VCSInstaller was returned - if _, ok := i.(*VCSInstaller); !ok { - t.Fatal("expected a VCSInstaller") - } - - if err := Update(i); err == nil { - t.Fatal("expected error for plugin does not exist, got none") - } else if err.Error() != "plugin does not exist" { - t.Fatalf("expected error for plugin does not exist, got (%v)", err) - } - - // Install plugin before update - if err := Install(i); err != nil { - t.Fatal(err) - } - - // Test FindSource method for positive result - pluginInfo, err := FindSource(i.Path()) - if err != nil { - t.Fatal(err) - } - - vcsInstaller := pluginInfo.(*VCSInstaller) - - repoRemote := vcsInstaller.Repo.Remote() - if repoRemote != source { - t.Fatalf("invalid source found, expected %q got %q", source, repoRemote) - } - - // Update plugin - if err := Update(i); err != nil { - t.Fatal(err) - } - - // Test update failure - if err := os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")); err != nil { - t.Fatal(err) - } - // Testing update for error - if err := Update(vcsInstaller); err == nil { - t.Fatalf("expected error for plugin modified, got none") - } else if err.Error() != "plugin repo was modified" { - t.Fatalf("expected error for plugin modified, got (%v)", err) - } - -} diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go deleted file mode 100644 index 1399b7116..000000000 --- a/pkg/plugin/plugin.go +++ /dev/null @@ -1,282 +0,0 @@ -/* -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" - "regexp" - "runtime" - "strings" - "unicode" - - "github.com/pkg/errors" - "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"` - - // UseTunnelDeprecated indicates that this command needs a tunnel. - // Setting this will cause a number of side effects, such as the - // automatic setting of HELM_HOST. - // DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4 - UseTunnelDeprecated bool `json:"useTunnel,omitempty"` -} - -// 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 -} - -// validPluginName is a regular expression that validates plugin names. -// -// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. -var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") - -// validatePluginData validates a plugin's YAML data. -func validatePluginData(plug *Plugin, filepath string) error { - if !validPluginName.MatchString(plug.Metadata.Name) { - return fmt.Errorf("invalid plugin name at %q", filepath) - } - plug.Metadata.Usage = sanitizeString(plug.Metadata.Usage) - - // We could also validate SemVer, executable, and other fields should we so choose. - return nil -} - -// sanitizeString normalize spaces and removes non-printable characters. -func sanitizeString(str string) string { - return strings.Map(func(r rune) rune { - if unicode.IsSpace(r) { - return ' ' - } - if unicode.IsPrint(r) { - return r - } - return -1 - }, str) -} - -func detectDuplicates(plugs []*Plugin) error { - names := map[string]string{} - - for _, plug := range plugs { - if oldpath, ok := names[plug.Metadata.Name]; ok { - return fmt.Errorf( - "two plugins claim the name %q at %q and %q", - plug.Metadata.Name, - oldpath, - plug.Dir, - ) - } - names[plug.Metadata.Name] = plug.Dir - } - - return nil -} - -// LoadDir loads a plugin from the given directory. -func LoadDir(dirname string) (*Plugin, error) { - pluginfile := filepath.Join(dirname, PluginFileName) - data, err := ioutil.ReadFile(pluginfile) - if err != nil { - return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile) - } - - plug := &Plugin{Dir: dirname} - if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil { - return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile) - } - return plug, validatePluginData(plug, pluginfile) -} - -// 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, errors.Wrapf(err, "failed to find plugins in %q", scanpath) - } - - 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, detectDuplicates(plugins) -} - -// 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) - } -} diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go deleted file mode 100644 index 3b44a6eb5..000000000 --- a/pkg/plugin/plugin_test.go +++ /dev/null @@ -1,378 +0,0 @@ -/* -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" - "os" - "path/filepath" - "reflect" - "runtime" - "testing" - - "helm.sh/helm/v3/pkg/cli" -) - -func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { - cmd, args, err := p.PrepareCommand(extraArgs) - if err != nil { - t.Fatal(err) - } - if cmd != "echo" { - t.Fatalf("Expected echo, got %q", cmd) - } - - if l := len(args); l != 5 { - t.Fatalf("expected 5 args, got %d", l) - } - - expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"} - for i := 0; i < len(args); i++ { - if expect[i] != args[i] { - t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) - } - } - - // Test with IgnoreFlags. This should omit --debug, --foo, bar - p.Metadata.IgnoreFlags = true - cmd, args, err = p.PrepareCommand(extraArgs) - if err != nil { - t.Fatal(err) - } - if cmd != "echo" { - t.Fatalf("Expected echo, got %q", cmd) - } - if l := len(args); l != 2 { - t.Fatalf("expected 2 args, got %d", l) - } - expect = []string{"-n", osStrCmp} - for i := 0; i < len(args); i++ { - if expect[i] != args[i] { - t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) - } - } -} - -func TestPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n foo", - }, - } - argv := []string{"--debug", "--foo", "bar"} - - checkCommand(p, argv, "foo", t) -} - -func TestPlatformPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n os-arch", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "linux", Architecture: "i386", Command: "echo -n linux-i386"}, - {OperatingSystem: "linux", Architecture: "amd64", Command: "echo -n linux-amd64"}, - {OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"}, - {OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"}, - {OperatingSystem: "linux", Architecture: "s390x", Command: "echo -n linux-s390x"}, - {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, - }, - }, - } - var osStrCmp string - os := runtime.GOOS - arch := runtime.GOARCH - if os == "linux" && arch == "i386" { - osStrCmp = "linux-i386" - } else if os == "linux" && arch == "amd64" { - osStrCmp = "linux-amd64" - } else if os == "linux" && arch == "arm64" { - osStrCmp = "linux-arm64" - } else if os == "linux" && arch == "ppc64le" { - osStrCmp = "linux-ppc64le" - } else if os == "linux" && arch == "s390x" { - osStrCmp = "linux-s390x" - } else if os == "windows" && arch == "amd64" { - osStrCmp = "win-64" - } else { - osStrCmp = "os-arch" - } - - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, osStrCmp, t) -} - -func TestPartialPlatformPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n os-arch", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "linux", Architecture: "i386", Command: "echo -n linux-i386"}, - {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, - }, - }, - } - var osStrCmp string - os := runtime.GOOS - arch := runtime.GOARCH - if os == "linux" { - osStrCmp = "linux-i386" - } else if os == "windows" && arch == "amd64" { - osStrCmp = "win-64" - } else { - osStrCmp = "os-arch" - } - - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, osStrCmp, t) -} - -func TestNoPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - }, - } - argv := []string{"--debug", "--foo", "bar"} - - _, _, err := p.PrepareCommand(argv) - if err == nil { - t.Fatalf("Expected error to be returned") - } -} - -func TestNoMatchPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "no-os", Architecture: "amd64", Command: "echo -n linux-i386"}, - }, - }, - } - argv := []string{"--debug", "--foo", "bar"} - - if _, _, err := p.PrepareCommand(argv); err == nil { - t.Fatalf("Expected error to be returned") - } -} - -func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/good/hello" - plug, err := LoadDir(dirname) - if err != nil { - t.Fatalf("error loading Hello plugin: %s", err) - } - - if plug.Dir != dirname { - t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir) - } - - expect := &Metadata{ - Name: "hello", - Version: "0.1.0", - Usage: "usage", - Description: "description", - Command: "$HELM_PLUGIN_DIR/hello.sh", - IgnoreFlags: true, - Hooks: map[string]string{ - Install: "echo installing...", - }, - } - - if !reflect.DeepEqual(expect, plug.Metadata) { - t.Fatalf("Expected plugin metadata %v, got %v", expect, plug.Metadata) - } -} - -func TestLoadDirDuplicateEntries(t *testing.T) { - dirname := "testdata/plugdir/bad/duplicate-entries" - if _, err := LoadDir(dirname); err == nil { - t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") - } -} - -func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/good/downloader" - plug, err := LoadDir(dirname) - if err != nil { - t.Fatalf("error loading Hello plugin: %s", err) - } - - if plug.Dir != dirname { - t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir) - } - - expect := &Metadata{ - Name: "downloader", - Version: "1.2.3", - Usage: "usage", - Description: "download something", - Command: "echo Hello", - Downloaders: []Downloaders{ - { - Protocols: []string{"myprotocol", "myprotocols"}, - Command: "echo Download", - }, - }, - } - - if !reflect.DeepEqual(expect, plug.Metadata) { - t.Fatalf("Expected metadata %v, got %v", expect, plug.Metadata) - } -} - -func TestLoadAll(t *testing.T) { - - // Verify that empty dir loads: - if plugs, err := LoadAll("testdata"); err != nil { - t.Fatalf("error loading dir with no plugins: %s", err) - } else if len(plugs) > 0 { - t.Fatalf("expected empty dir to have 0 plugins") - } - - basedir := "testdata/plugdir/good" - plugs, err := LoadAll(basedir) - if err != nil { - t.Fatalf("Could not load %q: %s", basedir, err) - } - - if l := len(plugs); l != 3 { - t.Fatalf("expected 3 plugins, found %d", l) - } - - if plugs[0].Metadata.Name != "downloader" { - t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name) - } - if plugs[1].Metadata.Name != "echo" { - t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name) - } - if plugs[2].Metadata.Name != "hello" { - t.Errorf("Expected second plugin to be hello, got %q", plugs[1].Metadata.Name) - } -} - -func TestFindPlugins(t *testing.T) { - cases := []struct { - name string - plugdirs string - expected int - }{ - { - name: "plugdirs is empty", - plugdirs: "", - expected: 0, - }, - { - name: "plugdirs isn't dir", - plugdirs: "./plugin_test.go", - expected: 0, - }, - { - name: "plugdirs doesn't have plugin", - plugdirs: ".", - expected: 0, - }, - { - name: "normal", - plugdirs: "./testdata/plugdir/good", - expected: 3, - }, - } - for _, c := range cases { - t.Run(t.Name(), func(t *testing.T) { - plugin, _ := FindPlugins(c.plugdirs) - if len(plugin) != c.expected { - t.Errorf("expected: %v, got: %v", c.expected, len(plugin)) - } - }) - } -} - -func TestSetupEnv(t *testing.T) { - name := "pequod" - base := filepath.Join("testdata/helmhome/helm/plugins", name) - - s := cli.New() - s.PluginsDirectory = "testdata/helmhome/helm/plugins" - - SetupPluginEnv(s, name, base) - for _, tt := range []struct { - name, expect string - }{ - {"HELM_PLUGIN_NAME", name}, - {"HELM_PLUGIN_DIR", base}, - } { - if got := os.Getenv(tt.name); got != tt.expect { - t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got) - } - } -} - -func TestValidatePluginData(t *testing.T) { - for i, item := range []struct { - pass bool - plug *Plugin - }{ - {true, mockPlugin("abcdefghijklmnopqrstuvwxyz0123456789_-ABC")}, - {true, mockPlugin("foo-bar-FOO-BAR_1234")}, - {false, mockPlugin("foo -bar")}, - {false, mockPlugin("$foo -bar")}, // Test leading chars - {false, mockPlugin("foo -bar ")}, // Test trailing chars - {false, mockPlugin("foo\nbar")}, // Test newline - } { - err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i)) - if item.pass && err != nil { - t.Errorf("failed to validate case %d: %s", i, err) - } else if !item.pass && err == nil { - t.Errorf("expected case %d to fail", i) - } - } -} - -func TestDetectDuplicates(t *testing.T) { - plugs := []*Plugin{ - mockPlugin("foo"), - mockPlugin("bar"), - } - if err := detectDuplicates(plugs); err != nil { - t.Error("no duplicates in the first set") - } - plugs = append(plugs, mockPlugin("foo")) - if err := detectDuplicates(plugs); err == nil { - t.Error("duplicates in the second set") - } -} - -func mockPlugin(name string) *Plugin { - return &Plugin{ - Metadata: &Metadata{ - Name: name, - Version: "v0.1.2", - Usage: "Mock plugin", - Description: "Mock plugin for testing", - Command: "echo mock plugin", - }, - Dir: "no-such-dir", - } -} diff --git a/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml b/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml deleted file mode 100644 index 66498be96..000000000 --- a/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: "duplicate-entries" -version: "0.1.0" -usage: "usage" -description: |- - description -command: "echo hello" -ignoreFlags: true -hooks: - install: "echo installing..." -hooks: - install: "echo installing something different" diff --git a/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml b/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml deleted file mode 100644 index c0b90379b..000000000 --- a/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: "downloader" -version: "1.2.3" -usage: "usage" -description: |- - download something -command: "echo Hello" -downloaders: - - protocols: - - "myprotocol" - - "myprotocols" - command: "echo Download" diff --git a/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml b/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml deleted file mode 100644 index 8baa35b6d..000000000 --- a/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: "echo" -version: "1.2.3" -usage: "echo something" -description: |- - This is a testing fixture. -command: "echo Hello" -hooks: - install: "echo Installing" diff --git a/pkg/plugin/testdata/plugdir/good/hello/hello.sh b/pkg/plugin/testdata/plugdir/good/hello/hello.sh deleted file mode 100755 index dcfd58876..000000000 --- a/pkg/plugin/testdata/plugdir/good/hello/hello.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -echo "Hello from a Helm plugin" - -echo "PARAMS" -echo $* - -$HELM_BIN ls --all - diff --git a/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml b/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml deleted file mode 100644 index b857b55ee..000000000 --- a/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: "hello" -version: "0.1.0" -usage: "usage" -description: |- - description -command: "$HELM_PLUGIN_DIR/hello.sh" -ignoreFlags: true -hooks: - install: "echo installing..." diff --git a/pkg/postrender/exec.go b/pkg/postrender/exec.go deleted file mode 100644 index 167e737d6..000000000 --- a/pkg/postrender/exec.go +++ /dev/null @@ -1,109 +0,0 @@ -/* -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 postrender - -import ( - "bytes" - "io" - "os/exec" - "path/filepath" - - "github.com/pkg/errors" -) - -type execRender struct { - binaryPath string - args []string -} - -// NewExec returns a PostRenderer implementation that calls the provided binary. -// It returns an error if the binary cannot be found. If the path does not -// contain any separators, it will search in $PATH, otherwise it will resolve -// any relative paths to a fully qualified path -func NewExec(binaryPath string, args ...string) (PostRenderer, error) { - fullPath, err := getFullPath(binaryPath) - if err != nil { - return nil, err - } - return &execRender{fullPath, args}, nil -} - -// Run the configured binary for the post render -func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { - cmd := exec.Command(p.binaryPath, p.args...) - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - var postRendered = &bytes.Buffer{} - var stderr = &bytes.Buffer{} - cmd.Stdout = postRendered - cmd.Stderr = stderr - - go func() { - defer stdin.Close() - io.Copy(stdin, renderedManifests) - }() - err = cmd.Run() - if err != nil { - return nil, errors.Wrapf(err, "error while running command %s. error output:\n%s", p.binaryPath, stderr.String()) - } - - return postRendered, nil -} - -// getFullPath returns the full filepath to the binary to execute. If the path -// does not contain any separators, it will search in $PATH, otherwise it will -// resolve any relative paths to a fully qualified path -func getFullPath(binaryPath string) (string, error) { - // NOTE(thomastaylor312): I am leaving this code commented out here. During - // the implementation of post-render, it was brought up that if we are - // relying on plugins, we should actually use the plugin system so it can - // properly handle multiple OSs. This will be a feature add in the future, - // so I left this code for reference. It can be deleted or reused once the - // feature is implemented - - // Manually check the plugin dir first - // if !strings.Contains(binaryPath, string(filepath.Separator)) { - // // First check the plugin dir - // pluginDir := helmpath.DataPath("plugins") // Default location - // // If location for plugins is explicitly set, check there - // if v, ok := os.LookupEnv("HELM_PLUGINS"); ok { - // pluginDir = v - // } - // // The plugins variable can actually contain multiple paths, so loop through those - // for _, p := range filepath.SplitList(pluginDir) { - // _, err := os.Stat(filepath.Join(p, binaryPath)) - // if err != nil && !os.IsNotExist(err) { - // return "", err - // } else if err == nil { - // binaryPath = filepath.Join(p, binaryPath) - // break - // } - // } - // } - - // Now check for the binary using the given path or check if it exists in - // the path and is executable - checkedPath, err := exec.LookPath(binaryPath) - if err != nil { - return "", errors.Wrapf(err, "unable to find binary at %s", binaryPath) - } - - return filepath.Abs(checkedPath) -} diff --git a/pkg/postrender/exec_test.go b/pkg/postrender/exec_test.go deleted file mode 100644 index 9788ed56e..000000000 --- a/pkg/postrender/exec_test.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -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 postrender - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "helm.sh/helm/v3/internal/test/ensure" -) - -const testingScript = `#!/bin/sh -if [ $# -eq 0 ]; then -sed s/FOOTEST/BARTEST/g <&0 -else -sed s/FOOTEST/"$*"/g <&0 -fi -` - -func TestGetFullPath(t *testing.T) { - is := assert.New(t) - t.Run("full path resolves correctly", func(t *testing.T) { - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - fullPath, err := getFullPath(testpath) - is.NoError(err) - is.Equal(testpath, fullPath) - }) - - t.Run("relative path resolves correctly", func(t *testing.T) { - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - currentDir, err := os.Getwd() - require.NoError(t, err) - relative, err := filepath.Rel(currentDir, testpath) - require.NoError(t, err) - fullPath, err := getFullPath(relative) - is.NoError(err) - is.Equal(testpath, fullPath) - }) - - t.Run("binary in PATH resolves correctly", func(t *testing.T) { - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - realPath := os.Getenv("PATH") - os.Setenv("PATH", filepath.Dir(testpath)) - defer func() { - os.Setenv("PATH", realPath) - }() - - fullPath, err := getFullPath(filepath.Base(testpath)) - is.NoError(err) - is.Equal(testpath, fullPath) - }) - - // NOTE(thomastaylor312): See note in getFullPath for more details why this - // is here - - // t.Run("binary in plugin path resolves correctly", func(t *testing.T) { - // testpath, cleanup := setupTestingScript(t) - // defer cleanup() - - // realPath := os.Getenv("HELM_PLUGINS") - // os.Setenv("HELM_PLUGINS", filepath.Dir(testpath)) - // defer func() { - // os.Setenv("HELM_PLUGINS", realPath) - // }() - - // fullPath, err := getFullPath(filepath.Base(testpath)) - // is.NoError(err) - // is.Equal(testpath, fullPath) - // }) - - // t.Run("binary in multiple plugin paths resolves correctly", func(t *testing.T) { - // testpath, cleanup := setupTestingScript(t) - // defer cleanup() - - // realPath := os.Getenv("HELM_PLUGINS") - // os.Setenv("HELM_PLUGINS", filepath.Dir(testpath)+string(os.PathListSeparator)+"/another/dir") - // defer func() { - // os.Setenv("HELM_PLUGINS", realPath) - // }() - - // fullPath, err := getFullPath(filepath.Base(testpath)) - // is.NoError(err) - // is.Equal(testpath, fullPath) - // }) -} - -func TestExecRun(t *testing.T) { - if runtime.GOOS == "windows" { - // the actual Run test uses a basic sed example, so skip this test on windows - t.Skip("skipping on windows") - } - is := assert.New(t) - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - renderer, err := NewExec(testpath) - require.NoError(t, err) - - output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) - is.Contains(output.String(), "BARTEST") -} - -func TestNewExecWithOneArgsRun(t *testing.T) { - if runtime.GOOS == "windows" { - // the actual Run test uses a basic sed example, so skip this test on windows - t.Skip("skipping on windows") - } - is := assert.New(t) - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - renderer, err := NewExec(testpath, "ARG1") - require.NoError(t, err) - - output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) - is.Contains(output.String(), "ARG1") -} - -func TestNewExecWithTwoArgsRun(t *testing.T) { - if runtime.GOOS == "windows" { - // the actual Run test uses a basic sed example, so skip this test on windows - t.Skip("skipping on windows") - } - is := assert.New(t) - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - renderer, err := NewExec(testpath, "ARG1", "ARG2") - require.NoError(t, err) - - output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) - is.Contains(output.String(), "ARG1 ARG2") -} - -func setupTestingScript(t *testing.T) (filepath string, cleanup func()) { - t.Helper() - - tempdir := ensure.TempDir(t) - - f, err := ioutil.TempFile(tempdir, "post-render-test.sh") - if err != nil { - t.Fatalf("unable to create tempfile for testing: %s", err) - } - - _, err = f.WriteString(testingScript) - if err != nil { - t.Fatalf("unable to write tempfile for testing: %s", err) - } - - err = f.Chmod(0755) - if err != nil { - t.Fatalf("unable to make tempfile executable for testing: %s", err) - } - - err = f.Close() - if err != nil { - t.Fatalf("unable to close tempfile after writing: %s", err) - } - - return f.Name(), func() { - os.RemoveAll(tempdir) - } -} diff --git a/pkg/postrender/postrender.go b/pkg/postrender/postrender.go deleted file mode 100644 index 3af384290..000000000 --- a/pkg/postrender/postrender.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -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 postrender contains an interface that can be implemented for custom -// post-renderers and an exec implementation that can be used for arbitrary -// binaries and scripts -package postrender - -import "bytes" - -type PostRenderer interface { - // Run expects a single buffer filled with Helm rendered manifests. It - // expects the modified results to be returned on a separate buffer or an - // error if there was an issue or failure while running the post render step - Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) -} diff --git a/pkg/provenance/doc.go b/pkg/provenance/doc.go deleted file mode 100644 index 3d2d0ea97..000000000 --- a/pkg/provenance/doc.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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.) " [ultimate] -*/ -package provenance // import "helm.sh/helm/v3/pkg/provenance" diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go deleted file mode 100644 index c41f90c61..000000000 --- a/pkg/provenance/sign.go +++ /dev/null @@ -1,424 +0,0 @@ -/* -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" //nolint - "golang.org/x/crypto/openpgp/clearsign" //nolint - "golang.org/x/crypto/openpgp/packet" //nolint - "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) -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 "", err - } - - // Sign the buffer - w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig) - if err != nil { - return "", err - } - - _, err = io.Copy(w, b) - - if err != nil { - // NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which - // actually does the PGP signing, and therefore is the part which uses the private key. - // In other words, if we call Close here, there's a risk that there's an attempt to use the - // private key to sign garbage data (since we know that io.Copy failed, `w` won't contain - // anything useful). - return "", errors.Wrap(err, "failed to write to clearsign encoder") - } - - err = w.Close() - if err != nil { - return "", errors.Wrap(err, "failed to either sign or armor message block") - } - - return out.String(), nil -} - -// 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 -} diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go deleted file mode 100644 index 93c169263..000000000 --- a/pkg/provenance/sign_test.go +++ /dev/null @@ -1,345 +0,0 @@ -/* -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 ( - "crypto" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - pgperrors "golang.org/x/crypto/openpgp/errors" //nolint -) - -const ( - // testKeyFile is the secret key. - // Generating keys should be done with `gpg --gen-key`. The current key - // was generated to match Go's defaults (RSA/RSA 2048). It has no pass - // phrase. Use `gpg --export-secret-keys helm-test` to export the secret. - testKeyfile = "testdata/helm-test-key.secret" - - // testPasswordKeyFile is a keyfile with a password. - testPasswordKeyfile = "testdata/helm-password-key.secret" - - // testPubfile is the public key file. - // Use `gpg --export helm-test` to export the public key. - testPubfile = "testdata/helm-test-key.pub" - - // Generated name for the PGP key in testKeyFile. - testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) ` - - testPasswordKeyName = `password key (fake) ` - - testChartfile = "testdata/hashtest-1.2.3.tgz" - - // testSigBlock points to a signature generated by an external tool. - // This file was generated with GnuPG: - // gpg --clearsign -u helm-test --openpgp testdata/msgblock.yaml - testSigBlock = "testdata/msgblock.yaml.asc" - - // testTamperedSigBlock is a tampered copy of msgblock.yaml.asc - testTamperedSigBlock = "testdata/msgblock.yaml.tampered" - - // testSumfile points to a SHA256 sum generated by an external tool. - // We always want to validate against an external tool's representation to - // verify that we haven't done something stupid. This file was generated - // with shasum. - // shasum -a 256 hashtest-1.2.3.tgz > testdata/hashtest.sha256 - testSumfile = "testdata/hashtest.sha256" -) - -// testMessageBlock represents the expected message block for the testdata/hashtest chart. -const testMessageBlock = `apiVersion: v1 -description: Test chart versioning -name: hashtest -version: 1.2.3 - -... -files: - hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888 -` - -func TestMessageBlock(t *testing.T) { - out, err := messageBlock(testChartfile) - if err != nil { - t.Fatal(err) - } - got := out.String() - - if got != testMessageBlock { - t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got) - } -} - -func TestParseMessageBlock(t *testing.T) { - md, sc, err := parseMessageBlock([]byte(testMessageBlock)) - if err != nil { - t.Fatal(err) - } - - if md.Name != "hashtest" { - t.Errorf("Expected name %q, got %q", "hashtest", md.Name) - } - - if lsc := len(sc.Files); lsc != 1 { - t.Errorf("Expected 1 file, got %d", lsc) - } - - if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok { - t.Errorf("hashtest file not found in Files") - } else if hash != "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888" { - t.Errorf("Unexpected hash: %q", hash) - } -} - -func TestLoadKey(t *testing.T) { - k, err := loadKey(testKeyfile) - if err != nil { - t.Fatal(err) - } - - if _, ok := k.Identities[testKeyName]; !ok { - t.Errorf("Expected to load a key for user %q", testKeyName) - } -} - -func TestLoadKeyRing(t *testing.T) { - k, err := loadKeyRing(testPubfile) - if err != nil { - t.Fatal(err) - } - - if len(k) > 1 { - t.Errorf("Expected 1, got %d", len(k)) - } - - for _, e := range k { - if ii, ok := e.Identities[testKeyName]; !ok { - t.Errorf("Expected %s in %v", testKeyName, ii) - } - } -} - -func TestDigest(t *testing.T) { - f, err := os.Open(testChartfile) - if err != nil { - t.Fatal(err) - } - defer f.Close() - - hash, err := Digest(f) - if err != nil { - t.Fatal(err) - } - - sig, err := readSumFile(testSumfile) - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(sig, hash) { - t.Errorf("Expected %s to be in %s", hash, sig) - } -} - -func TestNewFromFiles(t *testing.T) { - s, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - if _, ok := s.Entity.Identities[testKeyName]; !ok { - t.Errorf("Expected to load a key for user %q", testKeyName) - } -} - -func TestDigestFile(t *testing.T) { - hash, err := DigestFile(testChartfile) - if err != nil { - t.Fatal(err) - } - - sig, err := readSumFile(testSumfile) - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(sig, hash) { - t.Errorf("Expected %s to be in %s", hash, sig) - } -} - -func TestDecryptKey(t *testing.T) { - k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName) - if err != nil { - t.Fatal(err) - } - - if !k.Entity.PrivateKey.Encrypted { - t.Fatal("Key is not encrypted") - } - - // We give this a simple callback that returns the password. - if err := k.DecryptKey(func(s string) ([]byte, error) { - return []byte("secret"), nil - }); err != nil { - t.Fatal(err) - } - - // Re-read the key (since we already unlocked it) - k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName) - if err != nil { - t.Fatal(err) - } - // Now we give it a bogus password. - if err := k.DecryptKey(func(s string) ([]byte, error) { - return []byte("secrets_and_lies"), nil - }); err == nil { - t.Fatal("Expected an error when giving a bogus passphrase") - } -} - -func TestClearSign(t *testing.T) { - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - sig, err := signer.ClearSign(testChartfile) - if err != nil { - t.Fatal(err) - } - t.Logf("Sig:\n%s", sig) - - if !strings.Contains(sig, testMessageBlock) { - t.Errorf("expected message block to be in sig: %s", sig) - } -} - -// failSigner always fails to sign and returns an error -type failSigner struct{} - -func (s failSigner) Public() crypto.PublicKey { - return nil -} - -func (s failSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) { - return nil, fmt.Errorf("always fails") -} - -func TestClearSignError(t *testing.T) { - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - // ensure that signing always fails - signer.Entity.PrivateKey.PrivateKey = failSigner{} - - sig, err := signer.ClearSign(testChartfile) - if err == nil { - t.Fatal("didn't get an error from ClearSign but expected one") - } - - if sig != "" { - t.Fatalf("expected an empty signature after failed ClearSign but got %q", sig) - } -} - -func TestDecodeSignature(t *testing.T) { - // Unlike other tests, this does a round-trip test, ensuring that a signature - // generated by the library can also be verified by the library. - - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - sig, err := signer.ClearSign(testChartfile) - if err != nil { - t.Fatal(err) - } - - f, err := ioutil.TempFile("", "helm-test-sig-") - if err != nil { - t.Fatal(err) - } - - tname := f.Name() - defer func() { - os.Remove(tname) - }() - f.WriteString(sig) - f.Close() - - sig2, err := signer.decodeSignature(tname) - if err != nil { - t.Fatal(err) - } - - by, err := signer.verifySignature(sig2) - if err != nil { - t.Fatal(err) - } - - if _, ok := by.Identities[testKeyName]; !ok { - t.Errorf("Expected identity %q", testKeyName) - } -} - -func TestVerify(t *testing.T) { - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil { - t.Errorf("Failed to pass verify. Err: %s", err) - } else if len(ver.FileHash) == 0 { - t.Error("Verification is missing hash.") - } else if ver.SignedBy == nil { - t.Error("No SignedBy field") - } else if ver.FileName != filepath.Base(testChartfile) { - t.Errorf("FileName is unexpectedly %q", ver.FileName) - } - - if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil { - t.Errorf("Expected %s to fail.", testTamperedSigBlock) - } - - switch err.(type) { - case pgperrors.SignatureError: - t.Logf("Tampered sig block error: %s (%T)", err, err) - default: - t.Errorf("Expected invalid signature error, got %q (%T)", err, err) - } -} - -// readSumFile reads a file containing a sum generated by the UNIX shasum tool. -func readSumFile(sumfile string) (string, error) { - data, err := ioutil.ReadFile(sumfile) - if err != nil { - return "", err - } - - sig := string(data) - parts := strings.SplitN(sig, " ", 2) - return parts[0], nil -} diff --git a/pkg/provenance/testdata/hashtest-1.2.3.tgz b/pkg/provenance/testdata/hashtest-1.2.3.tgz deleted file mode 100644 index 7bbc533ca..000000000 Binary files a/pkg/provenance/testdata/hashtest-1.2.3.tgz and /dev/null differ diff --git a/pkg/provenance/testdata/hashtest-1.2.3.tgz.prov b/pkg/provenance/testdata/hashtest-1.2.3.tgz.prov deleted file mode 100755 index 3a788cd2e..000000000 --- a/pkg/provenance/testdata/hashtest-1.2.3.tgz.prov +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -apiVersion: v1 -description: Test chart versioning -name: hashtest -version: 1.2.3 - -... -files: - hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888 ------BEGIN PGP SIGNATURE----- - -wsBcBAEBCgAQBQJcon2ICRCEO7+YH8GHYgAASEAIAHD4Rad+LF47qNydI+k7x3aC -/qkdsqxE9kCUHtTJkZObE/Zmj2w3Opq0gcQftz4aJ2G9raqPDvwOzxnTxOkGfUdK -qIye48gFHzr2a7HnMTWr+HLQc4Gg+9kysIwkW4TM8wYV10osysYjBrhcafrHzFSK -791dBHhXP/aOrJQbFRob0GRFQ4pXdaSww1+kVaZLiKSPkkMKt9uk9Po1ggJYSIDX -uzXNcr78jTWACqkAtwx8+CJ8yzcGeuXSVNABDgbmAgpY0YT+Bz/UOWq4Q7tyuWnS -x9BKrvcb+Gc/6S0oK0Ffp8K4iSWYp79uH1bZ2oBS1yajA0c5h5i7qI3N4cabREw= -=YgnR ------END PGP SIGNATURE----- \ No newline at end of file diff --git a/pkg/provenance/testdata/hashtest.sha256 b/pkg/provenance/testdata/hashtest.sha256 deleted file mode 100644 index 05173edf8..000000000 --- a/pkg/provenance/testdata/hashtest.sha256 +++ /dev/null @@ -1 +0,0 @@ -c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888 hashtest-1.2.3.tgz diff --git a/pkg/provenance/testdata/hashtest/.helmignore b/pkg/provenance/testdata/hashtest/.helmignore deleted file mode 100644 index 435b756d8..000000000 --- a/pkg/provenance/testdata/hashtest/.helmignore +++ /dev/null @@ -1,5 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -.git diff --git a/pkg/provenance/testdata/hashtest/Chart.yaml b/pkg/provenance/testdata/hashtest/Chart.yaml deleted file mode 100644 index 6edf5f8b6..000000000 --- a/pkg/provenance/testdata/hashtest/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: Test chart versioning -name: hashtest -version: 1.2.3 diff --git a/pkg/provenance/testdata/hashtest/values.yaml b/pkg/provenance/testdata/hashtest/values.yaml deleted file mode 100644 index 0827a01fb..000000000 --- a/pkg/provenance/testdata/hashtest/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for hashtest. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name: value diff --git a/pkg/provenance/testdata/helm-password-key.secret b/pkg/provenance/testdata/helm-password-key.secret deleted file mode 100644 index 03c8aa583..000000000 Binary files a/pkg/provenance/testdata/helm-password-key.secret and /dev/null differ diff --git a/pkg/provenance/testdata/helm-test-key.pub b/pkg/provenance/testdata/helm-test-key.pub deleted file mode 100644 index 38714f25a..000000000 Binary files a/pkg/provenance/testdata/helm-test-key.pub and /dev/null differ diff --git a/pkg/provenance/testdata/helm-test-key.secret b/pkg/provenance/testdata/helm-test-key.secret deleted file mode 100644 index a966aef93..000000000 Binary files a/pkg/provenance/testdata/helm-test-key.secret and /dev/null differ diff --git a/pkg/provenance/testdata/msgblock.yaml b/pkg/provenance/testdata/msgblock.yaml deleted file mode 100644 index c16293ffc..000000000 --- a/pkg/provenance/testdata/msgblock.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -description: Test chart versioning -name: hashtest -version: 1.2.3 - -... -files: - hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888 diff --git a/pkg/provenance/testdata/msgblock.yaml.asc b/pkg/provenance/testdata/msgblock.yaml.asc deleted file mode 100644 index b4187b742..000000000 --- a/pkg/provenance/testdata/msgblock.yaml.asc +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -apiVersion: v1 -description: Test chart versioning -name: hashtest -version: 1.2.3 - -... -files: - hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888 ------BEGIN PGP SIGNATURE----- - -iQFJBAEBCgAzFiEEXmFTibU8o38O5gvThDu/mB/Bh2IFAlyiiDcVHGhlbG0tdGVz -dGluZ0BoZWxtLnNoAAoJEIQ7v5gfwYdiILAH/2f3GMVh+ZY5a+szOBudcuivjTcz -0Im1MwWQZfB1po3Yu7smWZbf5tJCzvVpYtvRlfa0nguuIh763MwOh9Q7dBXOLAxm -VCxqHm3svnNenBNfOpIygaMTgMZKxI4RrsKBgwPOTmlNtKg2lVaCiJAI30TXE6bB -/DwEYX0wmTssrAcSpTzOOSC+zHnPKew+5A3SY3ms+gAtVAcLepmJjI7RS7RhQxDl -AG+rWYis5gpDrk3U9OG1EOxqbftOAMqUl/kwI9eu5cPouN85rWwMe5pvHAvuyr/y -caYdlXDHTZsXmBuvfiUX6gqXtrpPCyKTCP+RzNf3+bXJM8m3u3gbMjGvKjU= -=vHcU ------END PGP SIGNATURE----- diff --git a/pkg/provenance/testdata/msgblock.yaml.tampered b/pkg/provenance/testdata/msgblock.yaml.tampered deleted file mode 100644 index f15811bb2..000000000 --- a/pkg/provenance/testdata/msgblock.yaml.tampered +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -description: Test chart versioning -name: hashtest -version: 1.2.3+tampered - -... -files: - hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 ------BEGIN PGP SIGNATURE----- -Comment: GPGTools - https://gpgtools.org - -iQEcBAEBCgAGBQJXlp8KAAoJEIQ7v5gfwYdiE7sIAJYDiza+asekeooSXLvQiK+G -PKnveqQpx49EZ6L7Y7UlW25SyH8EjXXHeJysDywCXF3w4luxN9n56ffU0KEW11IY -F+JSjmgIWLS6ti7ZAGEi6JInQ/30rOAIpTEBRBL2IueW3m63mezrGK6XkBlGqpor -C9WKeqLi+DWlMoBtsEy3Uk0XP6pn/qBFICYAbLQQU0sCCUT8CBA8f8aidxi7aw9t -i404yYF+Dvc6i4JlSG77SV0ZJBWllUvsWoCd9Jli0NAuaMqmE7mzcEt/dE+Fm2Ql -Bx3tr1WS4xTRiFQdcOttOl93H+OaHTh+Y0qqLTzzpCvqmttG0HfI6lMeCs7LeyA= -=vEK+ ------END PGP SIGNATURE----- diff --git a/pkg/provenance/testdata/regen-hashtest.sh b/pkg/provenance/testdata/regen-hashtest.sh deleted file mode 100755 index 4381fd0b1..000000000 --- a/pkg/provenance/testdata/regen-hashtest.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -helm package hashtest -shasum -a 256 hashtest-1.2.3.tgz > hashtest.sha256 diff --git a/pkg/pusher/doc.go b/pkg/pusher/doc.go deleted file mode 100644 index df89ab112..000000000 --- a/pkg/pusher/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -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 pusher provides a generalized tool for uploading data by scheme. -This provides a method by which the plugin system can load arbitrary protocol -handlers based upon a URL scheme. -*/ -package pusher diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go deleted file mode 100644 index 7c90e85a4..000000000 --- a/pkg/pusher/ocipusher.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -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 pusher - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/registry" -) - -// OCIPusher is the default OCI backend handler -type OCIPusher struct { - opts options -} - -// Push performs a Push from repo.Pusher. -func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error { - for _, opt := range options { - opt(&pusher.opts) - } - return pusher.push(chartRef, href) -} - -func (pusher *OCIPusher) push(chartRef, href string) error { - stat, err := os.Stat(chartRef) - if err != nil { - if os.IsNotExist(err) { - return errors.Errorf("%s: no such file", chartRef) - } - return err - } - if stat.IsDir() { - return errors.New("cannot push directory, must provide chart archive (.tgz)") - } - - meta, err := loader.Load(chartRef) - if err != nil { - return err - } - - client := pusher.opts.registryClient - - chartBytes, err := ioutil.ReadFile(chartRef) - if err != nil { - return err - } - - var pushOpts []registry.PushOption - provRef := fmt.Sprintf("%s.prov", chartRef) - if _, err := os.Stat(provRef); err == nil { - provBytes, err := ioutil.ReadFile(provRef) - if err != nil { - return err - } - pushOpts = append(pushOpts, registry.PushOptProvData(provBytes)) - } - - ref := fmt.Sprintf("%s:%s", - path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name), - meta.Metadata.Version) - - _, err = client.Push(chartBytes, ref, pushOpts...) - return err -} - -// NewOCIPusher constructs a valid OCI client as a Pusher -func NewOCIPusher(ops ...Option) (Pusher, error) { - registryClient, err := registry.NewClient( - registry.ClientOptEnableCache(true), - ) - if err != nil { - return nil, err - } - - client := OCIPusher{ - opts: options{ - registryClient: registryClient, - }, - } - - for _, opt := range ops { - opt(&client.opts) - } - - return &client, nil -} diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go deleted file mode 100644 index 27be15b5d..000000000 --- a/pkg/pusher/ocipusher_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -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 pusher - -import ( - "testing" -) - -func TestNewOCIPusher(t *testing.T) { - testfn := func(ops *options) { - if ops.registryClient == nil { - t.Fatalf("the OCIPusher's registryClient should not be null") - } - } - - p, err := NewOCIPusher(testfn) - if p == nil { - t.Error("NewOCIPusher returned nil") - } - if err != nil { - t.Error(err) - } -} diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go deleted file mode 100644 index 30c6af97c..000000000 --- a/pkg/pusher/pusher.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -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 pusher - -import ( - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -// options are generic parameters to be provided to the pusher during instantiation. -// -// Pushers may or may not ignore these parameters as they are passed in. -type options struct { - registryClient *registry.Client -} - -// Option allows specifying various settings configurable by the user for overriding the defaults -// used when performing Push operations with the Pusher. -type Option func(*options) - -// WithRegistryClient sets the registryClient option. -func WithRegistryClient(client *registry.Client) Option { - return func(opts *options) { - opts.registryClient = client - } -} - -// Pusher is an interface to support upload to the specified URL. -type Pusher interface { - // Push file content by url string - Push(chartRef, url string, options ...Option) error -} - -// Constructor is the function for every pusher which creates a specific instance -// according to the configuration -type Constructor func(options ...Option) (Pusher, error) - -// Provider represents any pusher and the schemes that it supports. -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) (Pusher, error) { - for _, pp := range p { - if pp.Provides(scheme) { - return pp.New() - } - } - return nil, errors.Errorf("scheme %q not supported", scheme) -} - -var ociProvider = Provider{ - Schemes: []string{registry.OCIScheme}, - New: NewOCIPusher, -} - -// All finds all of the registered pushers as a list of Provider instances. -// Currently, just the built-in pushers are collected. -func All(settings *cli.EnvSettings) Providers { - result := Providers{ociProvider} - return result -} diff --git a/pkg/pusher/pusher_test.go b/pkg/pusher/pusher_test.go deleted file mode 100644 index d43e6c9ec..000000000 --- a/pkg/pusher/pusher_test.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -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 pusher - -import ( - "testing" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -func TestProvider(t *testing.T) { - p := Provider{ - []string{"one", "three"}, - func(_ ...Option) (Pusher, error) { return nil, nil }, - } - - if !p.Provides("three") { - t.Error("Expected provider to provide three") - } -} - -func TestProviders(t *testing.T) { - ps := Providers{ - {[]string{"one", "three"}, func(_ ...Option) (Pusher, error) { return nil, nil }}, - {[]string{"two", "four"}, func(_ ...Option) (Pusher, error) { return nil, nil }}, - } - - if _, err := ps.ByScheme("one"); err != nil { - t.Error(err) - } - if _, err := ps.ByScheme("four"); err != nil { - t.Error(err) - } - - if _, err := ps.ByScheme("five"); err == nil { - t.Error("Did not expect handler for five") - } -} - -func TestAll(t *testing.T) { - env := cli.New() - all := All(env) - if len(all) != 1 { - t.Errorf("expected 1 provider (OCI), got %d", len(all)) - } -} - -func TestByScheme(t *testing.T) { - env := cli.New() - g := All(env) - if _, err := g.ByScheme(registry.OCIScheme); err != nil { - t.Error(err) - } -} diff --git a/pkg/registry/client.go b/pkg/registry/client.go deleted file mode 100644 index c1004f956..000000000 --- a/pkg/registry/client.go +++ /dev/null @@ -1,643 +0,0 @@ -/* -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 registry // import "helm.sh/helm/v3/pkg/registry" - -import ( - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "sort" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/containerd/containerd/remotes" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "oras.land/oras-go/pkg/auth" - dockerauth "oras.land/oras-go/pkg/auth/docker" - "oras.land/oras-go/pkg/content" - "oras.land/oras-go/pkg/oras" - "oras.land/oras-go/pkg/registry" - registryremote "oras.land/oras-go/pkg/registry/remote" - registryauth "oras.land/oras-go/pkg/registry/remote/auth" - - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/helmpath" -) - -// See https://github.com/helm/helm/issues/10166 -const registryUnderscoreMessage = ` -OCI artifact references (e.g. tags) do not support the plus sign (+). To support -storing semantic versions, Helm adopts the convention of changing plus (+) to -an underscore (_) in chart version tags when pushing to a registry and back to -a plus (+) when pulling from a registry.` - -type ( - // Client works with OCI-compliant registries - Client struct { - debug bool - enableCache bool - // path to repository config file e.g. ~/.docker/config.json - credentialsFile string - out io.Writer - authorizer auth.Client - registryAuthorizer *registryauth.Client - resolver remotes.Resolver - } - - // ClientOption allows specifying various settings configurable by the user for overriding the defaults - // used when creating a new default client - ClientOption func(*Client) -) - -// NewClient returns a new registry client with config -func NewClient(options ...ClientOption) (*Client, error) { - client := &Client{ - out: ioutil.Discard, - } - for _, option := range options { - option(client) - } - if client.credentialsFile == "" { - client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) - } - if client.authorizer == nil { - authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile) - if err != nil { - return nil, err - } - client.authorizer = authClient - } - if client.resolver == nil { - headers := http.Header{} - headers.Set("User-Agent", version.GetUserAgent()) - opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} - resolver, err := client.authorizer.ResolverWithOpts(opts...) - if err != nil { - return nil, err - } - client.resolver = resolver - } - - // allocate a cache if option is set - var cache registryauth.Cache - if client.enableCache { - cache = registryauth.DefaultCache - } - if client.registryAuthorizer == nil { - client.registryAuthorizer = ®istryauth.Client{ - Header: http.Header{ - "User-Agent": {version.GetUserAgent()}, - }, - Cache: cache, - Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) { - dockerClient, ok := client.authorizer.(*dockerauth.Client) - if !ok { - return registryauth.EmptyCredential, errors.New("unable to obtain docker client") - } - - username, password, err := dockerClient.Credential(reg) - if err != nil { - return registryauth.EmptyCredential, errors.New("unable to retrieve credentials") - } - - // A blank returned username and password value is a bearer token - if username == "" && password != "" { - return registryauth.Credential{ - RefreshToken: password, - }, nil - } - - return registryauth.Credential{ - Username: username, - Password: password, - }, nil - - }, - } - - } - return client, nil -} - -// ClientOptDebug returns a function that sets the debug setting on client options set -func ClientOptDebug(debug bool) ClientOption { - return func(client *Client) { - client.debug = debug - } -} - -// ClientOptEnableCache returns a function that sets the enableCache setting on a client options set -func ClientOptEnableCache(enableCache bool) ClientOption { - return func(client *Client) { - client.enableCache = enableCache - } -} - -// ClientOptWriter returns a function that sets the writer setting on client options set -func ClientOptWriter(out io.Writer) ClientOption { - return func(client *Client) { - client.out = out - } -} - -// ClientOptCredentialsFile returns a function that sets the credentialsFile setting on a client options set -func ClientOptCredentialsFile(credentialsFile string) ClientOption { - return func(client *Client) { - client.credentialsFile = credentialsFile - } -} - -type ( - // LoginOption allows specifying various settings on login - LoginOption func(*loginOperation) - - loginOperation struct { - username string - password string - insecure bool - } -) - -// Login logs into a registry -func (c *Client) Login(host string, options ...LoginOption) error { - operation := &loginOperation{} - for _, option := range options { - option(operation) - } - authorizerLoginOpts := []auth.LoginOption{ - auth.WithLoginContext(ctx(c.out, c.debug)), - auth.WithLoginHostname(host), - auth.WithLoginUsername(operation.username), - auth.WithLoginSecret(operation.password), - auth.WithLoginUserAgent(version.GetUserAgent()), - } - if operation.insecure { - authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure()) - } - if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil { - return err - } - fmt.Fprintln(c.out, "Login Succeeded") - return nil -} - -// LoginOptBasicAuth returns a function that sets the username/password settings on login -func LoginOptBasicAuth(username string, password string) LoginOption { - return func(operation *loginOperation) { - operation.username = username - operation.password = password - } -} - -// LoginOptInsecure returns a function that sets the insecure setting on login -func LoginOptInsecure(insecure bool) LoginOption { - return func(operation *loginOperation) { - operation.insecure = insecure - } -} - -type ( - // LogoutOption allows specifying various settings on logout - LogoutOption func(*logoutOperation) - - logoutOperation struct{} -) - -// Logout logs out of a registry -func (c *Client) Logout(host string, opts ...LogoutOption) error { - operation := &logoutOperation{} - for _, opt := range opts { - opt(operation) - } - if err := c.authorizer.Logout(ctx(c.out, c.debug), host); err != nil { - return err - } - fmt.Fprintf(c.out, "Removing login credentials for %s\n", host) - return nil -} - -type ( - // PullOption allows specifying various settings on pull - PullOption func(*pullOperation) - - // PullResult is the result returned upon successful pull. - PullResult struct { - Manifest *descriptorPullSummary `json:"manifest"` - Config *descriptorPullSummary `json:"config"` - Chart *descriptorPullSummaryWithMeta `json:"chart"` - Prov *descriptorPullSummary `json:"prov"` - Ref string `json:"ref"` - } - - descriptorPullSummary struct { - Data []byte `json:"-"` - Digest string `json:"digest"` - Size int64 `json:"size"` - } - - descriptorPullSummaryWithMeta struct { - descriptorPullSummary - Meta *chart.Metadata `json:"meta"` - } - - pullOperation struct { - withChart bool - withProv bool - ignoreMissingProv bool - } -) - -// Pull downloads a chart from a registry -func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { - parsedRef, err := parseReference(ref) - if err != nil { - return nil, err - } - - operation := &pullOperation{ - withChart: true, // By default, always download the chart layer - } - for _, option := range options { - option(operation) - } - if !operation.withChart && !operation.withProv { - return nil, errors.New( - "must specify at least one layer to pull (chart/prov)") - } - memoryStore := content.NewMemory() - allowedMediaTypes := []string{ - ConfigMediaType, - } - minNumDescriptors := 1 // 1 for the config - if operation.withChart { - minNumDescriptors++ - allowedMediaTypes = append(allowedMediaTypes, ChartLayerMediaType, LegacyChartLayerMediaType) - } - if operation.withProv { - if !operation.ignoreMissingProv { - minNumDescriptors++ - } - allowedMediaTypes = append(allowedMediaTypes, ProvLayerMediaType) - } - - var descriptors, layers []ocispec.Descriptor - registryStore := content.Registry{Resolver: c.resolver} - - manifest, err := oras.Copy(ctx(c.out, c.debug), registryStore, parsedRef.String(), memoryStore, "", - oras.WithPullEmptyNameAllowed(), - oras.WithAllowedMediaTypes(allowedMediaTypes), - oras.WithLayerDescriptors(func(l []ocispec.Descriptor) { - layers = l - })) - if err != nil { - return nil, err - } - - descriptors = append(descriptors, manifest) - descriptors = append(descriptors, layers...) - - numDescriptors := len(descriptors) - if numDescriptors < minNumDescriptors { - return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d", - minNumDescriptors, numDescriptors) - } - var configDescriptor *ocispec.Descriptor - var chartDescriptor *ocispec.Descriptor - var provDescriptor *ocispec.Descriptor - for _, descriptor := range descriptors { - d := descriptor - switch d.MediaType { - case ConfigMediaType: - configDescriptor = &d - case ChartLayerMediaType: - chartDescriptor = &d - case ProvLayerMediaType: - provDescriptor = &d - case LegacyChartLayerMediaType: - chartDescriptor = &d - fmt.Fprintf(c.out, "Warning: chart media type %s is deprecated\n", LegacyChartLayerMediaType) - } - } - if configDescriptor == nil { - return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType) - } - if operation.withChart && chartDescriptor == nil { - return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", - ChartLayerMediaType) - } - var provMissing bool - if operation.withProv && provDescriptor == nil { - if operation.ignoreMissingProv { - provMissing = true - } else { - return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", - ProvLayerMediaType) - } - } - result := &PullResult{ - Manifest: &descriptorPullSummary{ - Digest: manifest.Digest.String(), - Size: manifest.Size, - }, - Config: &descriptorPullSummary{ - Digest: configDescriptor.Digest.String(), - Size: configDescriptor.Size, - }, - Chart: &descriptorPullSummaryWithMeta{}, - Prov: &descriptorPullSummary{}, - Ref: parsedRef.String(), - } - var getManifestErr error - if _, manifestData, ok := memoryStore.Get(manifest); !ok { - getManifestErr = errors.Errorf("Unable to retrieve blob with digest %s", manifest.Digest) - } else { - result.Manifest.Data = manifestData - } - if getManifestErr != nil { - return nil, getManifestErr - } - var getConfigDescriptorErr error - if _, configData, ok := memoryStore.Get(*configDescriptor); !ok { - getConfigDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", configDescriptor.Digest) - } else { - result.Config.Data = configData - var meta *chart.Metadata - if err := json.Unmarshal(configData, &meta); err != nil { - return nil, err - } - result.Chart.Meta = meta - } - if getConfigDescriptorErr != nil { - return nil, getConfigDescriptorErr - } - if operation.withChart { - var getChartDescriptorErr error - if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok { - getChartDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest) - } else { - result.Chart.Data = chartData - result.Chart.Digest = chartDescriptor.Digest.String() - result.Chart.Size = chartDescriptor.Size - } - if getChartDescriptorErr != nil { - return nil, getChartDescriptorErr - } - } - if operation.withProv && !provMissing { - var getProvDescriptorErr error - if _, provData, ok := memoryStore.Get(*provDescriptor); !ok { - getProvDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest) - } else { - result.Prov.Data = provData - result.Prov.Digest = provDescriptor.Digest.String() - result.Prov.Size = provDescriptor.Size - } - if getProvDescriptorErr != nil { - return nil, getProvDescriptorErr - } - } - - fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref) - fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) - - if strings.Contains(result.Ref, "_") { - fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) - fmt.Fprint(c.out, registryUnderscoreMessage+"\n") - } - - return result, nil -} - -// PullOptWithChart returns a function that sets the withChart setting on pull -func PullOptWithChart(withChart bool) PullOption { - return func(operation *pullOperation) { - operation.withChart = withChart - } -} - -// PullOptWithProv returns a function that sets the withProv setting on pull -func PullOptWithProv(withProv bool) PullOption { - return func(operation *pullOperation) { - operation.withProv = withProv - } -} - -// PullOptIgnoreMissingProv returns a function that sets the ignoreMissingProv setting on pull -func PullOptIgnoreMissingProv(ignoreMissingProv bool) PullOption { - return func(operation *pullOperation) { - operation.ignoreMissingProv = ignoreMissingProv - } -} - -type ( - // PushOption allows specifying various settings on push - PushOption func(*pushOperation) - - // PushResult is the result returned upon successful push. - PushResult struct { - Manifest *descriptorPushSummary `json:"manifest"` - Config *descriptorPushSummary `json:"config"` - Chart *descriptorPushSummaryWithMeta `json:"chart"` - Prov *descriptorPushSummary `json:"prov"` - Ref string `json:"ref"` - } - - descriptorPushSummary struct { - Digest string `json:"digest"` - Size int64 `json:"size"` - } - - descriptorPushSummaryWithMeta struct { - descriptorPushSummary - Meta *chart.Metadata `json:"meta"` - } - - pushOperation struct { - provData []byte - strictMode bool - } -) - -// Push uploads a chart to a registry. -func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResult, error) { - parsedRef, err := parseReference(ref) - if err != nil { - return nil, err - } - - operation := &pushOperation{ - strictMode: true, // By default, enable strict mode - } - for _, option := range options { - option(operation) - } - meta, err := extractChartMeta(data) - if err != nil { - return nil, err - } - if operation.strictMode { - if !strings.HasSuffix(ref, fmt.Sprintf("/%s:%s", meta.Name, meta.Version)) { - return nil, errors.New( - "strict mode enabled, ref basename and tag must match the chart name and version") - } - } - memoryStore := content.NewMemory() - chartDescriptor, err := memoryStore.Add("", ChartLayerMediaType, data) - if err != nil { - return nil, err - } - - configData, err := json.Marshal(meta) - if err != nil { - return nil, err - } - - configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData) - if err != nil { - return nil, err - } - - descriptors := []ocispec.Descriptor{chartDescriptor} - var provDescriptor ocispec.Descriptor - if operation.provData != nil { - provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData) - if err != nil { - return nil, err - } - - descriptors = append(descriptors, provDescriptor) - } - - manifestData, manifest, err := content.GenerateManifest(&configDescriptor, nil, descriptors...) - if err != nil { - return nil, err - } - - if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil { - return nil, err - } - - registryStore := content.Registry{Resolver: c.resolver} - _, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.String(), registryStore, "", - oras.WithNameValidation(nil)) - if err != nil { - return nil, err - } - chartSummary := &descriptorPushSummaryWithMeta{ - Meta: meta, - } - chartSummary.Digest = chartDescriptor.Digest.String() - chartSummary.Size = chartDescriptor.Size - result := &PushResult{ - Manifest: &descriptorPushSummary{ - Digest: manifest.Digest.String(), - Size: manifest.Size, - }, - Config: &descriptorPushSummary{ - Digest: configDescriptor.Digest.String(), - Size: configDescriptor.Size, - }, - Chart: chartSummary, - Prov: &descriptorPushSummary{}, // prevent nil references - Ref: parsedRef.String(), - } - if operation.provData != nil { - result.Prov = &descriptorPushSummary{ - Digest: provDescriptor.Digest.String(), - Size: provDescriptor.Size, - } - } - fmt.Fprintf(c.out, "Pushed: %s\n", result.Ref) - fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) - if strings.Contains(parsedRef.Reference, "_") { - fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) - fmt.Fprint(c.out, registryUnderscoreMessage+"\n") - } - - return result, err -} - -// PushOptProvData returns a function that sets the prov bytes setting on push -func PushOptProvData(provData []byte) PushOption { - return func(operation *pushOperation) { - operation.provData = provData - } -} - -// PushOptStrictMode returns a function that sets the strictMode setting on push -func PushOptStrictMode(strictMode bool) PushOption { - return func(operation *pushOperation) { - operation.strictMode = strictMode - } -} - -// Tags provides a sorted list all semver compliant tags for a given repository -func (c *Client) Tags(ref string) ([]string, error) { - parsedReference, err := registry.ParseReference(ref) - if err != nil { - return nil, err - } - - repository := registryremote.Repository{ - Reference: parsedReference, - Client: c.registryAuthorizer, - } - - var registryTags []string - - for { - registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository) - if err != nil { - // Fallback to http based request - if !repository.PlainHTTP && strings.Contains(err.Error(), "server gave HTTP response") { - repository.PlainHTTP = true - continue - } - return nil, err - } - - break - - } - - var tagVersions []*semver.Version - for _, tag := range registryTags { - // Change underscore (_) back to plus (+) for Helm - // See https://github.com/helm/helm/issues/10166 - tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) - if err == nil { - tagVersions = append(tagVersions, tagVersion) - } - } - - // Sort the collection - sort.Sort(sort.Reverse(semver.Collection(tagVersions))) - - tags := make([]string, len(tagVersions)) - - for iTv, tv := range tagVersions { - tags[iTv] = tv.String() - } - - return tags, nil - -} diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go deleted file mode 100644 index 138dd4245..000000000 --- a/pkg/registry/client_test.go +++ /dev/null @@ -1,373 +0,0 @@ -/* -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 registry - -import ( - "bytes" - "context" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/containerd/containerd/errdefs" - "github.com/distribution/distribution/v3/configuration" - "github.com/distribution/distribution/v3/registry" - _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" - _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" - "github.com/phayes/freeport" - "github.com/stretchr/testify/suite" - "golang.org/x/crypto/bcrypt" -) - -var ( - testWorkspaceDir = "helm-registry-test" - testHtpasswdFileBasename = "authtest.htpasswd" - testUsername = "myuser" - testPassword = "mypass" -) - -type RegistryClientTestSuite struct { - suite.Suite - Out io.Writer - DockerRegistryHost string - CompromisedRegistryHost string - WorkspaceDir string - RegistryClient *Client -} - -func (suite *RegistryClientTestSuite) SetupSuite() { - suite.WorkspaceDir = testWorkspaceDir - os.RemoveAll(suite.WorkspaceDir) - os.Mkdir(suite.WorkspaceDir, 0700) - - var out bytes.Buffer - suite.Out = &out - credentialsFile := filepath.Join(suite.WorkspaceDir, CredentialsFileBasename) - - // init test client - var err error - suite.RegistryClient, err = NewClient( - ClientOptDebug(true), - ClientOptEnableCache(true), - ClientOptWriter(suite.Out), - ClientOptCredentialsFile(credentialsFile), - ) - suite.Nil(err, "no error creating registry client") - - // create htpasswd file (w BCrypt, which is required) - pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - suite.Nil(err, "no error generating bcrypt password for test htpasswd file") - htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename) - err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644) - suite.Nil(err, "no error creating test htpasswd file") - - // Registry config - config := &configuration.Configuration{} - port, err := freeport.GetFreePort() - suite.Nil(err, "no error finding free port for test registry") - suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port) - config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port) - config.HTTP.DrainTimeout = time.Duration(10) * time.Second - config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, - } - dockerRegistry, err := registry.NewRegistry(context.Background(), config) - suite.Nil(err, "no error creating test registry") - - suite.CompromisedRegistryHost = initCompromisedRegistryTestServer() - - // Start Docker registry - go dockerRegistry.ListenAndServe() -} - -func (suite *RegistryClientTestSuite) TearDownSuite() { - os.RemoveAll(suite.WorkspaceDir) -} - -func (suite *RegistryClientTestSuite) Test_0_Login() { - err := suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptInsecure(false)) - suite.NotNil(err, "error logging into registry with bad credentials") - - err = suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptInsecure(true)) - suite.NotNil(err, "error logging into registry with bad credentials, insecure mode") - - err = suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth(testUsername, testPassword), - LoginOptInsecure(false)) - suite.Nil(err, "no error logging into registry with good credentials") - - err = suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth(testUsername, testPassword), - LoginOptInsecure(true)) - suite.Nil(err, "no error logging into registry with good credentials, insecure mode") -} - -func (suite *RegistryClientTestSuite) Test_1_Push() { - // Bad bytes - ref := fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost) - _, err := suite.RegistryClient.Push([]byte("hello"), ref) - suite.NotNil(err, "error pushing non-chart bytes") - - // Load a test chart - chartData, err := ioutil.ReadFile("../repo/repotest/testdata/examplechart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - - // non-strict ref (chart name) - ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version) - _, err = suite.RegistryClient.Push(chartData, ref) - suite.NotNil(err, "error pushing non-strict ref (bad basename)") - - // non-strict ref (chart name), with strict mode disabled - _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false)) - suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") - - // non-strict ref (chart version) - ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name) - _, err = suite.RegistryClient.Push(chartData, ref) - suite.NotNil(err, "error pushing non-strict ref (bad tag)") - - // non-strict ref (chart version), with strict mode disabled - _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false)) - suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") - - // basic push, good ref - chartData, err = ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - _, err = suite.RegistryClient.Push(chartData, ref) - suite.Nil(err, "no error pushing good ref") - - _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") - - // Load another test chart - chartData, err = ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - - // Load prov file - provData, err := ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Nil(err, "no error loading test prov") - - // push with prov - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData)) - suite.Nil(err, "no error pushing good ref with prov") - - _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") - - // Validate the output - // Note: these digests/sizes etc may change if the test chart/prov files are modified, - // or if the format of the OCI manifest changes - suite.Equal(ref, result.Ref) - suite.Equal(meta.Name, result.Chart.Meta.Name) - suite.Equal(meta.Version, result.Chart.Meta.Version) - suite.Equal(int64(512), result.Manifest.Size) - suite.Equal(int64(99), result.Config.Size) - suite.Equal(int64(973), result.Chart.Size) - suite.Equal(int64(695), result.Prov.Size) - suite.Equal( - "sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83", - result.Manifest.Digest) - suite.Equal( - "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", - result.Config.Digest) - suite.Equal( - "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", - result.Chart.Digest) - suite.Equal( - "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", - result.Prov.Digest) -} - -func (suite *RegistryClientTestSuite) Test_2_Pull() { - // bad/missing ref - ref := fmt.Sprintf("%s/testrepo/no-existy:1.2.3", suite.DockerRegistryHost) - _, err := suite.RegistryClient.Pull(ref) - suite.NotNil(err, "error on bad/missing ref") - - // Load test chart (to build ref pushed in previous test) - chartData, err := ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - - // Simple pull, chart only - _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") - - // Simple pull with prov (no prov uploaded) - _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.NotNil(err, "error pulling a chart with prov when no prov exists") - - // Simple pull with prov, ignoring missing prov - _, err = suite.RegistryClient.Pull(ref, - PullOptWithProv(true), - PullOptIgnoreMissingProv(true)) - suite.Nil(err, - "no error pulling a chart with prov when no prov exists, ignoring missing") - - // Load test chart (to build ref pushed in previous test) - chartData, err = ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - - // Load prov file - provData, err := ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Nil(err, "no error loading test prov") - - // no chart and no prov causes error - _, err = suite.RegistryClient.Pull(ref, - PullOptWithChart(false), - PullOptWithProv(false)) - suite.NotNil(err, "error on both no chart and no prov") - - // full pull with chart and prov - result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Nil(err, "no error pulling a chart with prov") - - // Validate the output - // Note: these digests/sizes etc may change if the test chart/prov files are modified, - // or if the format of the OCI manifest changes - suite.Equal(ref, result.Ref) - suite.Equal(meta.Name, result.Chart.Meta.Name) - suite.Equal(meta.Version, result.Chart.Meta.Version) - suite.Equal(int64(512), result.Manifest.Size) - suite.Equal(int64(99), result.Config.Size) - suite.Equal(int64(973), result.Chart.Size) - suite.Equal(int64(695), result.Prov.Size) - suite.Equal( - "sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83", - result.Manifest.Digest) - suite.Equal( - "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", - result.Config.Digest) - suite.Equal( - "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", - result.Chart.Digest) - suite.Equal( - "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", - result.Prov.Digest) - suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}]}", - string(result.Manifest.Data)) - suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}", - string(result.Config.Data)) - suite.Equal(chartData, result.Chart.Data) - suite.Equal(provData, result.Prov.Data) -} - -func (suite *RegistryClientTestSuite) Test_3_Tags() { - - // Load test chart (to build ref pushed in previous test) - chartData, err := ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref := fmt.Sprintf("%s/testrepo/%s", suite.DockerRegistryHost, meta.Name) - - // Query for tags and validate length - tags, err := suite.RegistryClient.Tags(ref) - suite.Nil(err, "no error retrieving tags") - suite.Equal(1, len(tags)) - -} - -func (suite *RegistryClientTestSuite) Test_4_Logout() { - err := suite.RegistryClient.Logout("this-host-aint-real:5000") - suite.NotNil(err, "error logging out of registry that has no entry") - - err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Nil(err, "no error logging out of registry") -} - -func (suite *RegistryClientTestSuite) Test_5_ManInTheMiddle() { - ref := fmt.Sprintf("%s/testrepo/supposedlysafechart:9.9.9", suite.CompromisedRegistryHost) - - // returns content that does not match the expected digest - _, err := suite.RegistryClient.Pull(ref) - suite.NotNil(err) - suite.True(errdefs.IsFailedPrecondition(err)) -} - -func TestRegistryClientTestSuite(t *testing.T) { - suite.Run(t, new(RegistryClientTestSuite)) -} - -func initCompromisedRegistryTestServer() string { - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.URL.Path, "manifests") { - w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json") - w.WriteHeader(200) - - // layers[0] is the blob []byte("a") - w.Write([]byte( - fmt.Sprintf(`{ "schemaVersion": 2, "config": { - "mediaType": "%s", - "digest": "sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133", - "size": 181 - }, - "layers": [ - { - "mediaType": "%s", - "digest": "sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", - "size": 1 - } - ] -}`, ConfigMediaType, ChartLayerMediaType))) - } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133" { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - w.Write([]byte("{\"name\":\"mychart\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\\n" + - "an 'application' or a 'library' chart.\",\"apiVersion\":\"v2\",\"appVersion\":\"1.16.0\",\"type\":" + - "\"application\"}")) - } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" { - w.Header().Set("Content-Type", ChartLayerMediaType) - w.WriteHeader(200) - w.Write([]byte("b")) - } else { - w.WriteHeader(500) - } - })) - - u, _ := url.Parse(s.URL) - return fmt.Sprintf("localhost:%s", u.Port()) -} diff --git a/pkg/registry/constants.go b/pkg/registry/constants.go deleted file mode 100644 index 570b6f0d3..000000000 --- a/pkg/registry/constants.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 registry // import "helm.sh/helm/v3/pkg/registry" - -const ( - // OCIScheme is the URL scheme for OCI-based requests - OCIScheme = "oci" - - // CredentialsFileBasename is the filename for auth credentials file - CredentialsFileBasename = "registry/config.json" - - // ConfigMediaType is the reserved media type for the Helm chart manifest config - ConfigMediaType = "application/vnd.cncf.helm.config.v1+json" - - // ChartLayerMediaType is the reserved media type for Helm chart package content - ChartLayerMediaType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip" - - // ProvLayerMediaType is the reserved media type for Helm chart provenance files - ProvLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov" - - // LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content. - LegacyChartLayerMediaType = "application/tar+gzip" -) diff --git a/pkg/registry/util.go b/pkg/registry/util.go deleted file mode 100644 index 47eed267f..000000000 --- a/pkg/registry/util.go +++ /dev/null @@ -1,131 +0,0 @@ -/* -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 registry // import "helm.sh/helm/v3/pkg/registry" - -import ( - "bytes" - "context" - "fmt" - "io" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - orascontext "oras.land/oras-go/pkg/context" - "oras.land/oras-go/pkg/registry" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -// IsOCI determines whether or not a URL is to be treated as an OCI URL -func IsOCI(url string) bool { - return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) -} - -// ContainsTag determines whether a tag is found in a provided list of tags -func ContainsTag(tags []string, tag string) bool { - for _, t := range tags { - if tag == t { - return true - } - } - return false -} - -func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) { - var constraint *semver.Constraints - if versionString == "" { - // If string is empty, set wildcard constraint - constraint, _ = semver.NewConstraint("*") - } else { - // when customer input exact version, check whether have exact match - // one first - for _, v := range tags { - if versionString == v { - return v, nil - } - } - - // Otherwise set constraint to the string given - var err error - constraint, err = semver.NewConstraint(versionString) - if err != nil { - return "", err - } - } - - // Otherwise try to find the first available version matching the string, - // in case it is a constraint - for _, v := range tags { - test, err := semver.NewVersion(v) - if err != nil { - continue - } - if constraint.Check(test) { - return v, nil - } - } - - return "", errors.Errorf("Could not locate a version matching provided version string %s", versionString) -} - -// extractChartMeta is used to extract a chart metadata from a byte array -func extractChartMeta(chartData []byte) (*chart.Metadata, error) { - ch, err := loader.LoadArchive(bytes.NewReader(chartData)) - if err != nil { - return nil, err - } - return ch.Metadata, nil -} - -// ctx retrieves a fresh context. -// disable verbose logging coming from ORAS (unless debug is enabled) -func ctx(out io.Writer, debug bool) context.Context { - if !debug { - return orascontext.Background() - } - ctx := orascontext.WithLoggerFromWriter(context.Background(), out) - orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel) - return ctx -} - -// parseReference will parse and validate the reference, and clean tags when -// applicable tags are only cleaned when plus (+) signs are present, and are -// converted to underscores (_) before pushing -// See https://github.com/helm/helm/issues/10166 -func parseReference(raw string) (registry.Reference, error) { - // The sole possible reference modification is replacing plus (+) signs - // present in tags with underscores (_). To do this properly, we first - // need to identify a tag, and then pass it on to the reference parser - // NOTE: Passing immediately to the reference parser will fail since (+) - // signs are an invalid tag character, and simply replacing all plus (+) - // occurrences could invalidate other portions of the URI - parts := strings.Split(raw, ":") - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") { - tag := parts[len(parts)-1] - - if tag != "" { - // Replace any plus (+) signs with known underscore (_) conversion - newTag := strings.ReplaceAll(tag, "+", "_") - raw = strings.ReplaceAll(raw, tag, newTag) - } - } - - return registry.ParseReference(raw) -} diff --git a/pkg/release/hook.go b/pkg/release/hook.go deleted file mode 100644 index cb9955582..000000000 --- a/pkg/release/hook.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -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 release - -import ( - "helm.sh/helm/v3/pkg/time" -) - -// HookEvent specifies the hook event -type HookEvent string - -// Hook event types -const ( - HookPreInstall HookEvent = "pre-install" - HookPostInstall HookEvent = "post-install" - HookPreDelete HookEvent = "pre-delete" - HookPostDelete HookEvent = "post-delete" - HookPreUpgrade HookEvent = "pre-upgrade" - HookPostUpgrade HookEvent = "post-upgrade" - HookPreRollback HookEvent = "pre-rollback" - HookPostRollback HookEvent = "post-rollback" - HookTest HookEvent = "test" -) - -func (x HookEvent) String() string { return string(x) } - -// HookDeletePolicy specifies the hook delete policy -type HookDeletePolicy string - -// Hook delete policy types -const ( - HookSucceeded HookDeletePolicy = "hook-succeeded" - HookFailed HookDeletePolicy = "hook-failed" - HookBeforeHookCreation HookDeletePolicy = "before-hook-creation" -) - -func (x HookDeletePolicy) String() string { return string(x) } - -// HookAnnotation is the label name for a hook -const HookAnnotation = "helm.sh/hook" - -// HookWeightAnnotation is the label name for a hook weight -const HookWeightAnnotation = "helm.sh/hook-weight" - -// HookDeleteAnnotation is the label name for the delete policy for a hook -const HookDeleteAnnotation = "helm.sh/hook-delete-policy" - -// Hook defines a hook object. -type Hook struct { - Name string `json:"name,omitempty"` - // Kind is the Kubernetes kind. - Kind string `json:"kind,omitempty"` - // Path is the chart-relative path to the template. - Path string `json:"path,omitempty"` - // Manifest is the manifest contents. - Manifest string `json:"manifest,omitempty"` - // Events are the events that this hook fires on. - Events []HookEvent `json:"events,omitempty"` - // LastRun indicates the date/time this was last run. - LastRun HookExecution `json:"last_run,omitempty"` - // Weight indicates the sort order for execution among similar Hook type - Weight int `json:"weight,omitempty"` - // DeletePolicies are the policies that indicate when to delete the hook - DeletePolicies []HookDeletePolicy `json:"delete_policies,omitempty"` -} - -// A HookExecution records the result for the last execution of a hook for a given release. -type HookExecution struct { - // StartedAt indicates the date/time this hook was started - StartedAt time.Time `json:"started_at,omitempty"` - // CompletedAt indicates the date/time this hook was completed. - CompletedAt time.Time `json:"completed_at,omitempty"` - // Phase indicates whether the hook completed successfully - Phase HookPhase `json:"phase"` -} - -// A HookPhase indicates the state of a hook execution -type HookPhase string - -const ( - // HookPhaseUnknown indicates that a hook is in an unknown state - HookPhaseUnknown HookPhase = "Unknown" - // HookPhaseRunning indicates that a hook is currently executing - HookPhaseRunning HookPhase = "Running" - // HookPhaseSucceeded indicates that hook execution succeeded - HookPhaseSucceeded HookPhase = "Succeeded" - // HookPhaseFailed indicates that hook execution failed - HookPhaseFailed HookPhase = "Failed" -) - -// String converts a hook phase to a printable string -func (x HookPhase) String() string { return string(x) } diff --git a/pkg/release/info.go b/pkg/release/info.go deleted file mode 100644 index 0cb2bab64..000000000 --- a/pkg/release/info.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -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 release - -import ( - "helm.sh/helm/v3/pkg/time" -) - -// Info describes release information. -type Info struct { - // FirstDeployed is when the release was first deployed. - FirstDeployed time.Time `json:"first_deployed,omitempty"` - // LastDeployed is when the release was last deployed. - LastDeployed time.Time `json:"last_deployed,omitempty"` - // Deleted tracks when this object was deleted. - Deleted time.Time `json:"deleted"` - // Description is human-friendly "log entry" about this release. - Description string `json:"description,omitempty"` - // Status is the current state of the release - Status Status `json:"status,omitempty"` - // Contains the rendered templates/NOTES.txt if available - Notes string `json:"notes,omitempty"` -} diff --git a/pkg/release/mock.go b/pkg/release/mock.go deleted file mode 100644 index a28e1dc16..000000000 --- a/pkg/release/mock.go +++ /dev/null @@ -1,116 +0,0 @@ -/* -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 release - -import ( - "fmt" - "math/rand" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/time" -) - -// MockHookTemplate is the hook template used for all mock release objects. -var MockHookTemplate = `apiVersion: v1 -kind: Job -metadata: - annotations: - "helm.sh/hook": pre-install -` - -// MockManifest is the manifest used for all mock release objects. -var MockManifest = `apiVersion: v1 -kind: Secret -metadata: - name: fixture -` - -// MockReleaseOptions allows for user-configurable options on mock release objects. -type MockReleaseOptions struct { - Name string - Version int - Chart *chart.Chart - Status Status - Namespace string -} - -// Mock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. -func Mock(opts *MockReleaseOptions) *Release { - date := time.Unix(242085845, 0).UTC() - - name := opts.Name - if name == "" { - name = "testrelease-" + fmt.Sprint(rand.Intn(100)) - } - - version := 1 - if opts.Version != 0 { - version = opts.Version - } - - namespace := opts.Namespace - if namespace == "" { - namespace = "default" - } - - ch := opts.Chart - if opts.Chart == nil { - ch = &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "foo", - Version: "0.1.0-beta.1", - AppVersion: "1.0", - }, - Templates: []*chart.File{ - {Name: "templates/foo.tpl", Data: []byte(MockManifest)}, - }, - } - } - - scode := StatusDeployed - if len(opts.Status) > 0 { - scode = opts.Status - } - - info := &Info{ - FirstDeployed: date, - LastDeployed: date, - Status: scode, - Description: "Release mock", - Notes: "Some mock release notes!", - } - - return &Release{ - Name: name, - Info: info, - Chart: ch, - Config: map[string]interface{}{"name": "value"}, - Version: version, - Namespace: namespace, - Hooks: []*Hook{ - { - Name: "pre-install-hook", - Kind: "Job", - Path: "pre-install-hook.yaml", - Manifest: MockHookTemplate, - LastRun: HookExecution{}, - Events: []HookEvent{HookPreInstall}, - }, - }, - Manifest: MockManifest, - } -} diff --git a/pkg/release/release.go b/pkg/release/release.go deleted file mode 100644 index b90612873..000000000 --- a/pkg/release/release.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 release - -import "helm.sh/helm/v3/pkg/chart" - -// Release describes a deployment of a chart, together with the chart -// and the variables used to deploy that chart. -type Release struct { - // Name is the name of the release - Name string `json:"name,omitempty"` - // Info provides information about a release - Info *Info `json:"info,omitempty"` - // Chart is the chart that was released. - Chart *chart.Chart `json:"chart,omitempty"` - // Config is the set of extra Values added to the chart. - // These values override the default values inside of the chart. - Config map[string]interface{} `json:"config,omitempty"` - // Manifest is the string representation of the rendered template. - Manifest string `json:"manifest,omitempty"` - // Hooks are all of the hooks declared for this release. - Hooks []*Hook `json:"hooks,omitempty"` - // Version is an int which represents the revision of the release. - Version int `json:"version,omitempty"` - // Namespace is the kubernetes namespace of the release. - Namespace string `json:"namespace,omitempty"` - // Labels of the release. - // Disabled encoding into Json cause labels are stored in storage driver metadata field. - Labels map[string]string `json:"-"` -} - -// SetStatus is a helper for setting the status on a release. -func (r *Release) SetStatus(status Status, msg string) { - r.Info.Status = status - r.Info.Description = msg -} diff --git a/pkg/release/responses.go b/pkg/release/responses.go deleted file mode 100644 index 7ee1fc2ee..000000000 --- a/pkg/release/responses.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -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 release - -// UninstallReleaseResponse represents a successful response to an uninstall request. -type UninstallReleaseResponse struct { - // Release is the release that was marked deleted. - Release *Release `json:"release,omitempty"` - // Info is an uninstall message - Info string `json:"info,omitempty"` -} diff --git a/pkg/release/status.go b/pkg/release/status.go deleted file mode 100644 index e0e3ed62a..000000000 --- a/pkg/release/status.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 release - -// Status is the status of a release -type Status string - -// Describe the status of a release -// NOTE: Make sure to update cmd/helm/status.go when adding or modifying any of these statuses. -const ( - // StatusUnknown indicates that a release is in an uncertain state. - StatusUnknown Status = "unknown" - // StatusDeployed indicates that the release has been pushed to Kubernetes. - StatusDeployed Status = "deployed" - // StatusUninstalled indicates that a release has been uninstalled from Kubernetes. - StatusUninstalled Status = "uninstalled" - // StatusSuperseded indicates that this release object is outdated and a newer one exists. - StatusSuperseded Status = "superseded" - // StatusFailed indicates that the release was not successfully deployed. - StatusFailed Status = "failed" - // StatusUninstalling indicates that a uninstall operation is underway. - StatusUninstalling Status = "uninstalling" - // StatusPendingInstall indicates that an install operation is underway. - StatusPendingInstall Status = "pending-install" - // StatusPendingUpgrade indicates that an upgrade operation is underway. - StatusPendingUpgrade Status = "pending-upgrade" - // StatusPendingRollback indicates that an rollback operation is underway. - StatusPendingRollback Status = "pending-rollback" -) - -func (x Status) String() string { return string(x) } - -// IsPending determines if this status is a state or a transition. -func (x Status) IsPending() bool { - return x == StatusPendingInstall || x == StatusPendingUpgrade || x == StatusPendingRollback -} diff --git a/pkg/releaseutil/filter.go b/pkg/releaseutil/filter.go deleted file mode 100644 index dbd0df8e2..000000000 --- a/pkg/releaseutil/filter.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import rspb "helm.sh/helm/v3/pkg/release" - -// FilterFunc returns true if the release object satisfies -// the predicate of the underlying filter func. -type FilterFunc func(*rspb.Release) bool - -// Check applies the FilterFunc to the release object. -func (fn FilterFunc) Check(rls *rspb.Release) bool { - if rls == nil { - return false - } - return fn(rls) -} - -// Filter applies the filter(s) to the list of provided releases -// returning the list that satisfies the filtering predicate. -func (fn FilterFunc) Filter(rels []*rspb.Release) (rets []*rspb.Release) { - for _, rel := range rels { - if fn.Check(rel) { - rets = append(rets, rel) - } - } - return -} - -// Any returns a FilterFunc that filters a list of releases -// determined by the predicate 'f0 || f1 || ... || fn'. -func Any(filters ...FilterFunc) FilterFunc { - return func(rls *rspb.Release) bool { - for _, filter := range filters { - if filter(rls) { - return true - } - } - return false - } -} - -// All returns a FilterFunc that filters a list of releases -// determined by the predicate 'f0 && f1 && ... && fn'. -func All(filters ...FilterFunc) FilterFunc { - return func(rls *rspb.Release) bool { - for _, filter := range filters { - if !filter(rls) { - return false - } - } - return true - } -} - -// StatusFilter filters a set of releases by status code. -func StatusFilter(status rspb.Status) FilterFunc { - return FilterFunc(func(rls *rspb.Release) bool { - if rls == nil { - return true - } - return rls.Info.Status == status - }) -} diff --git a/pkg/releaseutil/filter_test.go b/pkg/releaseutil/filter_test.go deleted file mode 100644 index 31ac306f6..000000000 --- a/pkg/releaseutil/filter_test.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "testing" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestFilterAny(t *testing.T) { - ls := Any(StatusFilter(rspb.StatusUninstalled)).Filter(releases) - if len(ls) != 2 { - t.Fatalf("expected 2 results, got '%d'", len(ls)) - } - - r0, r1 := ls[0], ls[1] - switch { - case r0.Info.Status != rspb.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - case r1.Info.Status != rspb.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - } -} - -func TestFilterAll(t *testing.T) { - fn := FilterFunc(func(rls *rspb.Release) bool { - // true if not uninstalled and version < 4 - v0 := !StatusFilter(rspb.StatusUninstalled).Check(rls) - v1 := rls.Version < 4 - return v0 && v1 - }) - - ls := All(fn).Filter(releases) - if len(ls) != 1 { - t.Fatalf("expected 1 result, got '%d'", len(ls)) - } - - switch r0 := ls[0]; { - case r0.Version == 4: - t.Fatal("got release with status revision 4") - case r0.Info.Status == rspb.StatusUninstalled: - t.Fatal("got release with status UNINSTALLED") - } -} diff --git a/pkg/releaseutil/kind_sorter.go b/pkg/releaseutil/kind_sorter.go deleted file mode 100644 index 1d1874cfa..000000000 --- a/pkg/releaseutil/kind_sorter.go +++ /dev/null @@ -1,158 +0,0 @@ -/* -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 releaseutil - -import ( - "sort" - - "helm.sh/helm/v3/pkg/release" -) - -// KindSortOrder is an ordering of Kinds. -type KindSortOrder []string - -// InstallOrder is the order in which manifests should be installed (by Kind). -// -// Those occurring earlier in the list get installed before those occurring later in the list. -var InstallOrder KindSortOrder = []string{ - "Namespace", - "NetworkPolicy", - "ResourceQuota", - "LimitRange", - "PodSecurityPolicy", - "PodDisruptionBudget", - "ServiceAccount", - "Secret", - "SecretList", - "ConfigMap", - "StorageClass", - "PersistentVolume", - "PersistentVolumeClaim", - "CustomResourceDefinition", - "ClusterRole", - "ClusterRoleList", - "ClusterRoleBinding", - "ClusterRoleBindingList", - "Role", - "RoleList", - "RoleBinding", - "RoleBindingList", - "Service", - "DaemonSet", - "Pod", - "ReplicationController", - "ReplicaSet", - "Deployment", - "HorizontalPodAutoscaler", - "StatefulSet", - "Job", - "CronJob", - "IngressClass", - "Ingress", - "APIService", -} - -// UninstallOrder is the order in which manifests should be uninstalled (by Kind). -// -// Those occurring earlier in the list get uninstalled before those occurring later in the list. -var UninstallOrder KindSortOrder = []string{ - "APIService", - "Ingress", - "IngressClass", - "Service", - "CronJob", - "Job", - "StatefulSet", - "HorizontalPodAutoscaler", - "Deployment", - "ReplicaSet", - "ReplicationController", - "Pod", - "DaemonSet", - "RoleBindingList", - "RoleBinding", - "RoleList", - "Role", - "ClusterRoleBindingList", - "ClusterRoleBinding", - "ClusterRoleList", - "ClusterRole", - "CustomResourceDefinition", - "PersistentVolumeClaim", - "PersistentVolume", - "StorageClass", - "ConfigMap", - "SecretList", - "Secret", - "ServiceAccount", - "PodDisruptionBudget", - "PodSecurityPolicy", - "LimitRange", - "ResourceQuota", - "NetworkPolicy", - "Namespace", -} - -// sort manifests by kind. -// -// Results are sorted by 'ordering', keeping order of items with equal kind/priority -func sortManifestsByKind(manifests []Manifest, ordering KindSortOrder) []Manifest { - sort.SliceStable(manifests, func(i, j int) bool { - return lessByKind(manifests[i], manifests[j], manifests[i].Head.Kind, manifests[j].Head.Kind, ordering) - }) - - return manifests -} - -// sort hooks by kind, using an out-of-place sort to preserve the input parameters. -// -// Results are sorted by 'ordering', keeping order of items with equal kind/priority -func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.Hook { - h := hooks - sort.SliceStable(h, func(i, j int) bool { - return lessByKind(h[i], h[j], h[i].Kind, h[j].Kind, ordering) - }) - - return h -} - -func lessByKind(a interface{}, b interface{}, kindA string, kindB string, o KindSortOrder) bool { - ordering := make(map[string]int, len(o)) - for v, k := range o { - ordering[k] = v - } - - first, aok := ordering[kindA] - second, bok := ordering[kindB] - - if !aok && !bok { - // if both are unknown then sort alphabetically by kind, keep original order if same kind - if kindA != kindB { - return kindA < kindB - } - return first < second - } - // unknown kind is last - if !aok { - return false - } - if !bok { - return true - } - // sort different kinds, keep original order if same priority - return first < second -} diff --git a/pkg/releaseutil/kind_sorter_test.go b/pkg/releaseutil/kind_sorter_test.go deleted file mode 100644 index afcae6d16..000000000 --- a/pkg/releaseutil/kind_sorter_test.go +++ /dev/null @@ -1,335 +0,0 @@ -/* -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 releaseutil - -import ( - "bytes" - "testing" - - "helm.sh/helm/v3/pkg/release" -) - -func TestKindSorter(t *testing.T) { - manifests := []Manifest{ - { - Name: "U", - Head: &SimpleHead{Kind: "IngressClass"}, - }, - { - Name: "E", - Head: &SimpleHead{Kind: "SecretList"}, - }, - { - Name: "i", - Head: &SimpleHead{Kind: "ClusterRole"}, - }, - { - Name: "I", - Head: &SimpleHead{Kind: "ClusterRoleList"}, - }, - { - Name: "j", - Head: &SimpleHead{Kind: "ClusterRoleBinding"}, - }, - { - Name: "J", - Head: &SimpleHead{Kind: "ClusterRoleBindingList"}, - }, - { - Name: "f", - Head: &SimpleHead{Kind: "ConfigMap"}, - }, - { - Name: "u", - Head: &SimpleHead{Kind: "CronJob"}, - }, - { - Name: "2", - Head: &SimpleHead{Kind: "CustomResourceDefinition"}, - }, - { - Name: "n", - Head: &SimpleHead{Kind: "DaemonSet"}, - }, - { - Name: "r", - Head: &SimpleHead{Kind: "Deployment"}, - }, - { - Name: "!", - Head: &SimpleHead{Kind: "HonkyTonkSet"}, - }, - { - Name: "v", - Head: &SimpleHead{Kind: "Ingress"}, - }, - { - Name: "t", - Head: &SimpleHead{Kind: "Job"}, - }, - { - Name: "c", - Head: &SimpleHead{Kind: "LimitRange"}, - }, - { - Name: "a", - Head: &SimpleHead{Kind: "Namespace"}, - }, - { - Name: "A", - Head: &SimpleHead{Kind: "NetworkPolicy"}, - }, - { - Name: "g", - Head: &SimpleHead{Kind: "PersistentVolume"}, - }, - { - Name: "h", - Head: &SimpleHead{Kind: "PersistentVolumeClaim"}, - }, - { - Name: "o", - Head: &SimpleHead{Kind: "Pod"}, - }, - { - Name: "3", - Head: &SimpleHead{Kind: "PodDisruptionBudget"}, - }, - { - Name: "C", - Head: &SimpleHead{Kind: "PodSecurityPolicy"}, - }, - { - Name: "q", - Head: &SimpleHead{Kind: "ReplicaSet"}, - }, - { - Name: "p", - Head: &SimpleHead{Kind: "ReplicationController"}, - }, - { - Name: "b", - Head: &SimpleHead{Kind: "ResourceQuota"}, - }, - { - Name: "k", - Head: &SimpleHead{Kind: "Role"}, - }, - { - Name: "K", - Head: &SimpleHead{Kind: "RoleList"}, - }, - { - Name: "l", - Head: &SimpleHead{Kind: "RoleBinding"}, - }, - { - Name: "L", - Head: &SimpleHead{Kind: "RoleBindingList"}, - }, - { - Name: "e", - Head: &SimpleHead{Kind: "Secret"}, - }, - { - Name: "m", - Head: &SimpleHead{Kind: "Service"}, - }, - { - Name: "d", - Head: &SimpleHead{Kind: "ServiceAccount"}, - }, - { - Name: "s", - Head: &SimpleHead{Kind: "StatefulSet"}, - }, - { - Name: "1", - Head: &SimpleHead{Kind: "StorageClass"}, - }, - { - Name: "w", - Head: &SimpleHead{Kind: "APIService"}, - }, - { - Name: "x", - Head: &SimpleHead{Kind: "HorizontalPodAutoscaler"}, - }, - } - - for _, test := range []struct { - description string - order KindSortOrder - expected string - }{ - {"install", InstallOrder, "aAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvw!"}, - {"uninstall", UninstallOrder, "wvUmutsxrqponLlKkJjIi2hg1fEed3CcbAa!"}, - } { - var buf bytes.Buffer - t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(manifests); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } - defer buf.Reset() - orig := manifests - for _, r := range sortManifestsByKind(manifests, test.order) { - buf.WriteString(r.Name) - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } - for i, manifest := range orig { - if manifest != manifests[i] { - t.Fatal("Expected input to sortManifestsByKind to stay the same") - } - } - }) - } -} - -// TestKindSorterKeepOriginalOrder verifies manifests of same kind are kept in original order -func TestKindSorterKeepOriginalOrder(t *testing.T) { - manifests := []Manifest{ - { - Name: "a", - Head: &SimpleHead{Kind: "ClusterRole"}, - }, - { - Name: "A", - Head: &SimpleHead{Kind: "ClusterRole"}, - }, - { - Name: "0", - Head: &SimpleHead{Kind: "ConfigMap"}, - }, - { - Name: "1", - Head: &SimpleHead{Kind: "ConfigMap"}, - }, - { - Name: "z", - Head: &SimpleHead{Kind: "ClusterRoleBinding"}, - }, - { - Name: "!", - Head: &SimpleHead{Kind: "ClusterRoleBinding"}, - }, - { - Name: "u2", - Head: &SimpleHead{Kind: "Unknown"}, - }, - { - Name: "u1", - Head: &SimpleHead{Kind: "Unknown"}, - }, - { - Name: "t3", - Head: &SimpleHead{Kind: "Unknown2"}, - }, - } - for _, test := range []struct { - description string - order KindSortOrder - expected string - }{ - // expectation is sorted by kind (unknown is last) and within each group of same kind, the order is kept - {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01aAz!u2u1t3"}, - } { - var buf bytes.Buffer - t.Run(test.description, func(t *testing.T) { - defer buf.Reset() - for _, r := range sortManifestsByKind(manifests, test.order) { - buf.WriteString(r.Name) - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } - }) - } -} - -func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { - unknown := Manifest{ - Name: "a", - Head: &SimpleHead{Kind: "Unknown"}, - } - namespace := Manifest{ - Name: "b", - Head: &SimpleHead{Kind: "Namespace"}, - } - - manifests := []Manifest{unknown, namespace} - manifests = sortManifestsByKind(manifests, InstallOrder) - - expectedOrder := []Manifest{namespace, unknown} - for i, manifest := range manifests { - if expectedOrder[i].Name != manifest.Name { - t.Errorf("Expected %s, got %s", expectedOrder[i].Name, manifest.Name) - } - } -} - -// test hook sorting with a small subset of kinds, since it uses the same algorithm as sortManifestsByKind -func TestKindSorterForHooks(t *testing.T) { - hooks := []*release.Hook{ - { - Name: "i", - Kind: "ClusterRole", - }, - { - Name: "j", - Kind: "ClusterRoleBinding", - }, - { - Name: "c", - Kind: "LimitRange", - }, - { - Name: "a", - Kind: "Namespace", - }, - } - - for _, test := range []struct { - description string - order KindSortOrder - expected string - }{ - {"install", InstallOrder, "acij"}, - {"uninstall", UninstallOrder, "jica"}, - } { - var buf bytes.Buffer - t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(hooks); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } - defer buf.Reset() - orig := hooks - for _, r := range sortHooksByKind(hooks, test.order) { - buf.WriteString(r.Name) - } - for i, hook := range orig { - if hook != hooks[i] { - t.Fatal("Expected input to sortHooksByKind to stay the same") - } - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } - }) - } -} diff --git a/pkg/releaseutil/manifest.go b/pkg/releaseutil/manifest.go deleted file mode 100644 index 0b04a4599..000000000 --- a/pkg/releaseutil/manifest.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -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 releaseutil - -import ( - "fmt" - "regexp" - "strconv" - "strings" -) - -// SimpleHead defines what the structure of the head of a manifest file -type SimpleHead struct { - Version string `json:"apiVersion"` - Kind string `json:"kind,omitempty"` - Metadata *struct { - Name string `json:"name"` - Annotations map[string]string `json:"annotations"` - } `json:"metadata,omitempty"` -} - -var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") - -// SplitManifests takes a string of manifest and returns a map contains individual manifests -func SplitManifests(bigFile string) map[string]string { - // Basically, we're quickly splitting a stream of YAML documents into an - // array of YAML docs. The file name is just a place holder, but should be - // integer-sortable so that manifests get output in the same order as the - // input (see `BySplitManifestsOrder`). - tpl := "manifest-%d" - res := map[string]string{} - // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. - bigFileTmp := strings.TrimSpace(bigFile) - docs := sep.Split(bigFileTmp, -1) - var count int - for _, d := range docs { - if d == "" { - continue - } - - d = strings.TrimSpace(d) - res[fmt.Sprintf(tpl, count)] = d - count = count + 1 - } - return res -} - -// BySplitManifestsOrder sorts by in-file manifest order, as provided in function `SplitManifests` -type BySplitManifestsOrder []string - -func (a BySplitManifestsOrder) Len() int { return len(a) } -func (a BySplitManifestsOrder) Less(i, j int) bool { - // Split `manifest-%d` - anum, _ := strconv.ParseInt(a[i][len("manifest-"):], 10, 0) - bnum, _ := strconv.ParseInt(a[j][len("manifest-"):], 10, 0) - return anum < bnum -} -func (a BySplitManifestsOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/pkg/releaseutil/manifest_sorter.go b/pkg/releaseutil/manifest_sorter.go deleted file mode 100644 index e83414500..000000000 --- a/pkg/releaseutil/manifest_sorter.go +++ /dev/null @@ -1,233 +0,0 @@ -/* -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 releaseutil - -import ( - "log" - "path" - "sort" - "strconv" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -// Manifest represents a manifest file, which has a name and some content. -type Manifest struct { - Name string - Content string - Head *SimpleHead -} - -// manifestFile represents a file that contains a manifest. -type manifestFile struct { - entries map[string]string - path string - apis chartutil.VersionSet -} - -// result is an intermediate structure used during sorting. -type result struct { - hooks []*release.Hook - generic []Manifest -} - -// TODO: Refactor this out. It's here because naming conventions were not followed through. -// So fix the Test hook names and then remove this. -var events = map[string]release.HookEvent{ - release.HookPreInstall.String(): release.HookPreInstall, - release.HookPostInstall.String(): release.HookPostInstall, - release.HookPreDelete.String(): release.HookPreDelete, - release.HookPostDelete.String(): release.HookPostDelete, - release.HookPreUpgrade.String(): release.HookPreUpgrade, - release.HookPostUpgrade.String(): release.HookPostUpgrade, - release.HookPreRollback.String(): release.HookPreRollback, - release.HookPostRollback.String(): release.HookPostRollback, - release.HookTest.String(): release.HookTest, - // Support test-success for backward compatibility with Helm 2 tests - "test-success": release.HookTest, -} - -// SortManifests takes a map of filename/YAML contents, splits the file -// by manifest entries, and sorts the entries into hook types. -// -// The resulting hooks struct will be populated with all of the generated hooks. -// Any file that does not declare one of the hook types will be placed in the -// 'generic' bucket. -// -// Files that do not parse into the expected format are simply placed into a map and -// returned. -func SortManifests(files map[string]string, apis chartutil.VersionSet, ordering KindSortOrder) ([]*release.Hook, []Manifest, error) { - result := &result{} - - var sortedFilePaths []string - for filePath := range files { - sortedFilePaths = append(sortedFilePaths, filePath) - } - sort.Strings(sortedFilePaths) - - for _, filePath := range sortedFilePaths { - content := files[filePath] - - // Skip partials. We could return these as a separate map, but there doesn't - // seem to be any need for that at this time. - if strings.HasPrefix(path.Base(filePath), "_") { - continue - } - // Skip empty files and log this. - if strings.TrimSpace(content) == "" { - continue - } - - manifestFile := &manifestFile{ - entries: SplitManifests(content), - path: filePath, - apis: apis, - } - - if err := manifestFile.sort(result); err != nil { - return result.hooks, result.generic, err - } - } - - return sortHooksByKind(result.hooks, ordering), sortManifestsByKind(result.generic, ordering), nil -} - -// sort takes a manifestFile object which may contain multiple resource definition -// entries and sorts each entry by hook types, and saves the resulting hooks and -// generic manifests (or non-hooks) to the result struct. -// -// To determine hook type, it looks for a YAML structure like this: -// -// kind: SomeKind -// apiVersion: v1 -// metadata: -// annotations: -// helm.sh/hook: pre-install -// -// To determine the policy to delete the hook, it looks for a YAML structure like this: -// -// kind: SomeKind -// apiVersion: v1 -// metadata: -// annotations: -// helm.sh/hook-delete-policy: hook-succeeded -func (file *manifestFile) sort(result *result) error { - // Go through manifests in order found in file (function `SplitManifests` creates integer-sortable keys) - var sortedEntryKeys []string - for entryKey := range file.entries { - sortedEntryKeys = append(sortedEntryKeys, entryKey) - } - sort.Sort(BySplitManifestsOrder(sortedEntryKeys)) - - for _, entryKey := range sortedEntryKeys { - m := file.entries[entryKey] - - var entry SimpleHead - if err := yaml.Unmarshal([]byte(m), &entry); err != nil { - return errors.Wrapf(err, "YAML parse error on %s", file.path) - } - - if !hasAnyAnnotation(entry) { - result.generic = append(result.generic, Manifest{ - Name: file.path, - Content: m, - Head: &entry, - }) - continue - } - - hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation] - if !ok { - result.generic = append(result.generic, Manifest{ - Name: file.path, - Content: m, - Head: &entry, - }) - continue - } - - hw := calculateHookWeight(entry) - - h := &release.Hook{ - Name: entry.Metadata.Name, - Kind: entry.Kind, - Path: file.path, - Manifest: m, - Events: []release.HookEvent{}, - Weight: hw, - DeletePolicies: []release.HookDeletePolicy{}, - } - - isUnknownHook := false - for _, hookType := range strings.Split(hookTypes, ",") { - hookType = strings.ToLower(strings.TrimSpace(hookType)) - e, ok := events[hookType] - if !ok { - isUnknownHook = true - break - } - h.Events = append(h.Events, e) - } - - if isUnknownHook { - log.Printf("info: skipping unknown hook: %q", hookTypes) - continue - } - - result.hooks = append(result.hooks, h) - - operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) { - h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value)) - }) - } - - return nil -} - -// hasAnyAnnotation returns true if the given entry has any annotations at all. -func hasAnyAnnotation(entry SimpleHead) bool { - return entry.Metadata != nil && - entry.Metadata.Annotations != nil && - len(entry.Metadata.Annotations) != 0 -} - -// calculateHookWeight finds the weight in the hook weight annotation. -// -// If no weight is found, the assigned weight is 0 -func calculateHookWeight(entry SimpleHead) int { - hws := entry.Metadata.Annotations[release.HookWeightAnnotation] - hw, err := strconv.Atoi(hws) - if err != nil { - hw = 0 - } - return hw -} - -// operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation -func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) { - if dps, ok := entry.Metadata.Annotations[annotation]; ok { - for _, dp := range strings.Split(dps, ",") { - dp = strings.ToLower(strings.TrimSpace(dp)) - operate(dp) - } - } -} diff --git a/pkg/releaseutil/manifest_sorter_test.go b/pkg/releaseutil/manifest_sorter_test.go deleted file mode 100644 index 20d809317..000000000 --- a/pkg/releaseutil/manifest_sorter_test.go +++ /dev/null @@ -1,228 +0,0 @@ -/* -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 releaseutil - -import ( - "reflect" - "testing" - - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -func TestSortManifests(t *testing.T) { - - data := []struct { - name []string - path string - kind []string - hooks map[string][]release.HookEvent - manifest string - }{ - { - name: []string{"first"}, - path: "one", - kind: []string{"Job"}, - hooks: map[string][]release.HookEvent{"first": {release.HookPreInstall}}, - manifest: `apiVersion: v1 -kind: Job -metadata: - name: first - labels: - doesnot: matter - annotations: - "helm.sh/hook": pre-install -`, - }, - { - name: []string{"second"}, - path: "two", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"second": {release.HookPostInstall}}, - manifest: `kind: ReplicaSet -apiVersion: v1beta1 -metadata: - name: second - annotations: - "helm.sh/hook": post-install -`, - }, { - name: []string{"third"}, - path: "three", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"third": nil}, - manifest: `kind: ReplicaSet -apiVersion: v1beta1 -metadata: - name: third - annotations: - "helm.sh/hook": no-such-hook -`, - }, { - name: []string{"fourth"}, - path: "four", - kind: []string{"Pod"}, - hooks: map[string][]release.HookEvent{"fourth": nil}, - manifest: `kind: Pod -apiVersion: v1 -metadata: - name: fourth - annotations: - nothing: here`, - }, { - name: []string{"fifth"}, - path: "five", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"fifth": {release.HookPostDelete, release.HookPostInstall}}, - manifest: `kind: ReplicaSet -apiVersion: v1beta1 -metadata: - name: fifth - annotations: - "helm.sh/hook": post-delete, post-install -`, - }, { - // Regression test: files with an underscore in the base name should be skipped. - name: []string{"sixth"}, - path: "six/_six", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"sixth": nil}, - manifest: `invalid manifest`, // This will fail if partial is not skipped. - }, { - // Regression test: files with no content should be skipped. - name: []string{"seventh"}, - path: "seven", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"seventh": nil}, - manifest: "", - }, - { - name: []string{"eighth", "example-test"}, - path: "eight", - kind: []string{"ConfigMap", "Pod"}, - hooks: map[string][]release.HookEvent{"eighth": nil, "example-test": {release.HookTest}}, - manifest: `kind: ConfigMap -apiVersion: v1 -metadata: - name: eighth -data: - name: value ---- -apiVersion: v1 -kind: Pod -metadata: - name: example-test - annotations: - "helm.sh/hook": test -`, - }, - } - - manifests := make(map[string]string, len(data)) - for _, o := range data { - manifests[o.path] = o.manifest - } - - hs, generic, err := SortManifests(manifests, chartutil.VersionSet{"v1", "v1beta1"}, InstallOrder) - if err != nil { - t.Fatalf("Unexpected error: %s", err) - } - - // This test will fail if 'six' or 'seven' was added. - if len(generic) != 2 { - t.Errorf("Expected 2 generic manifests, got %d", len(generic)) - } - - if len(hs) != 4 { - t.Errorf("Expected 4 hooks, got %d", len(hs)) - } - - for _, out := range hs { - found := false - for _, expect := range data { - if out.Path == expect.path { - found = true - if out.Path != expect.path { - t.Errorf("Expected path %s, got %s", expect.path, out.Path) - } - nameFound := false - for _, expectedName := range expect.name { - if out.Name == expectedName { - nameFound = true - } - } - if !nameFound { - t.Errorf("Got unexpected name %s", out.Name) - } - kindFound := false - for _, expectedKind := range expect.kind { - if out.Kind == expectedKind { - kindFound = true - } - } - if !kindFound { - t.Errorf("Got unexpected kind %s", out.Kind) - } - - expectedHooks := expect.hooks[out.Name] - if !reflect.DeepEqual(expectedHooks, out.Events) { - t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) - } - - } - } - if !found { - t.Errorf("Result not found: %v", out) - } - } - - // Verify the sort order - sorted := []Manifest{} - for _, s := range data { - manifests := SplitManifests(s.manifest) - - for _, m := range manifests { - var sh SimpleHead - if err := yaml.Unmarshal([]byte(m), &sh); err != nil { - // This is expected for manifests that are corrupt or empty. - t.Log(err) - continue - } - - name := sh.Metadata.Name - - // only keep track of non-hook manifests - if s.hooks[name] == nil { - another := Manifest{ - Content: m, - Name: name, - Head: &sh, - } - sorted = append(sorted, another) - } - } - } - - sorted = sortManifestsByKind(sorted, InstallOrder) - for i, m := range generic { - if m.Content != sorted[i].Content { - t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) - } - } -} diff --git a/pkg/releaseutil/manifest_test.go b/pkg/releaseutil/manifest_test.go deleted file mode 100644 index 8664d20ef..000000000 --- a/pkg/releaseutil/manifest_test.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "reflect" - "testing" -) - -const mockManifestFile = ` - ---- -apiVersion: v1 -kind: Pod -metadata: - name: finding-nemo, - annotations: - "helm.sh/hook": test -spec: - containers: - - name: nemo-test - image: fake-image - cmd: fake-command -` - -const expectedManifest = `apiVersion: v1 -kind: Pod -metadata: - name: finding-nemo, - annotations: - "helm.sh/hook": test -spec: - containers: - - name: nemo-test - image: fake-image - cmd: fake-command` - -func TestSplitManifest(t *testing.T) { - manifests := SplitManifests(mockManifestFile) - if len(manifests) != 1 { - t.Errorf("Expected 1 manifest, got %v", len(manifests)) - } - expected := map[string]string{"manifest-0": expectedManifest} - if !reflect.DeepEqual(manifests, expected) { - t.Errorf("Expected %v, got %v", expected, manifests) - } -} diff --git a/pkg/releaseutil/sorter.go b/pkg/releaseutil/sorter.go deleted file mode 100644 index 1a8aa78a6..000000000 --- a/pkg/releaseutil/sorter.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "sort" - - rspb "helm.sh/helm/v3/pkg/release" -) - -type list []*rspb.Release - -func (s list) Len() int { return len(s) } -func (s list) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// ByName sorts releases by name -type ByName struct{ list } - -// Less compares to releases -func (s ByName) Less(i, j int) bool { return s.list[i].Name < s.list[j].Name } - -// ByDate sorts releases by date -type ByDate struct{ list } - -// Less compares to releases -func (s ByDate) Less(i, j int) bool { - ti := s.list[i].Info.LastDeployed.Unix() - tj := s.list[j].Info.LastDeployed.Unix() - return ti < tj -} - -// ByRevision sorts releases by revision number -type ByRevision struct{ list } - -// Less compares to releases -func (s ByRevision) Less(i, j int) bool { - return s.list[i].Version < s.list[j].Version -} - -// Reverse reverses the list of releases sorted by the sort func. -func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { - sortFn(list) - for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { - list[i], list[j] = list[j], list[i] - } -} - -// SortByName returns the list of releases sorted -// in lexicographical order. -func SortByName(list []*rspb.Release) { - sort.Sort(ByName{list}) -} - -// SortByDate returns the list of releases sorted by a -// release's last deployed time (in seconds). -func SortByDate(list []*rspb.Release) { - sort.Sort(ByDate{list}) -} - -// SortByRevision returns the list of releases sorted by a -// release's revision number (release.Version). -func SortByRevision(list []*rspb.Release) { - sort.Sort(ByRevision{list}) -} diff --git a/pkg/releaseutil/sorter_test.go b/pkg/releaseutil/sorter_test.go deleted file mode 100644 index 9544d2018..000000000 --- a/pkg/releaseutil/sorter_test.go +++ /dev/null @@ -1,108 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "testing" - "time" - - rspb "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// note: this test data is shared with filter_test.go. - -var releases = []*rspb.Release{ - tsRelease("quiet-bear", 2, 2000, rspb.StatusSuperseded), - tsRelease("angry-bird", 4, 3000, rspb.StatusDeployed), - tsRelease("happy-cats", 1, 4000, rspb.StatusUninstalled), - tsRelease("vocal-dogs", 3, 6000, rspb.StatusUninstalled), -} - -func tsRelease(name string, vers int, dur time.Duration, status rspb.Status) *rspb.Release { - info := &rspb.Info{Status: status, LastDeployed: helmtime.Now().Add(dur)} - return &rspb.Release{ - Name: name, - Version: vers, - Info: info, - } -} - -func check(t *testing.T, by string, fn func(int, int) bool) { - for i := len(releases) - 1; i > 0; i-- { - if fn(i, i-1) { - t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by) - } - } -} - -func TestSortByName(t *testing.T) { - SortByName(releases) - - check(t, "ByName", func(i, j int) bool { - ni := releases[i].Name - nj := releases[j].Name - return ni < nj - }) -} - -func TestSortByDate(t *testing.T) { - SortByDate(releases) - - check(t, "ByDate", func(i, j int) bool { - ti := releases[i].Info.LastDeployed.Second() - tj := releases[j].Info.LastDeployed.Second() - return ti < tj - }) -} - -func TestSortByRevision(t *testing.T) { - SortByRevision(releases) - - check(t, "ByRevision", func(i, j int) bool { - vi := releases[i].Version - vj := releases[j].Version - return vi < vj - }) -} - -func TestReverseSortByName(t *testing.T) { - Reverse(releases, SortByName) - check(t, "ByName", func(i, j int) bool { - ni := releases[i].Name - nj := releases[j].Name - return ni > nj - }) -} - -func TestReverseSortByDate(t *testing.T) { - Reverse(releases, SortByDate) - check(t, "ByDate", func(i, j int) bool { - ti := releases[i].Info.LastDeployed.Second() - tj := releases[j].Info.LastDeployed.Second() - return ti > tj - }) -} - -func TestReverseSortByRevision(t *testing.T) { - Reverse(releases, SortByRevision) - check(t, "ByRevision", func(i, j int) bool { - vi := releases[i].Version - vj := releases[j].Version - return vi > vj - }) -} diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go deleted file mode 100644 index fce947e4c..000000000 --- a/pkg/repo/chartrepo.go +++ /dev/null @@ -1,313 +0,0 @@ -/* -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"` - PassCredentialsAll bool `json:"pass_credentials_all"` -} - -// 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. -// -// Deprecated: remove in Helm 4. -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 err - } - 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), - getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), - ) - if err != nil { - return "", err - } - - index, err := ioutil.ReadAll(resp) - if err != nil { - return "", err - } - - indexFile, err := loadIndex(index, r.Config.URL) - 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) { - if err := r.IndexFile.MustAdd(ch.Metadata, path, r.Config.URL, digest); err != nil { - return errors.Wrapf(err, "failed adding to %s to index", path) - } - } - // 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) { - return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters) -} - -// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials and TLS verify flag for the chart repository. -// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL. -func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) { - return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, false, getters) -} - -// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials, TLS verify flag, and if credentials should -// be passed on to other domains. -// TODO Helm 4, FindChartInAuthAndTLSAndPassRepoURL should be integrated into FindChartInAuthRepoURL. -func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify, passCredentialsAll bool, 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, - PassCredentialsAll: passCredentialsAll, - CertFile: certFile, - KeyFile: keyFile, - CAFile: caFile, - Name: name, - InsecureSkipTLSverify: insecureSkipTLSverify, - } - 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) - } - defer func() { - os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))) - os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))) - }() - - // 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) { - // We need a trailing slash for ResolveReference to work, but make sure there isn't already one - parsedBaseURL, err := url.Parse(strings.TrimSuffix(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) - } - - 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) -} diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go deleted file mode 100644 index 3dae90391..000000000 --- a/pkg/repo/chartrepo_test.go +++ /dev/null @@ -1,419 +0,0 @@ -/* -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" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "reflect" - "runtime" - "strings" - "testing" - "time" - - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" -) - -const ( - testRepository = "testdata/repository" - testURL = "http://example-charts.com" -) - -func TestLoadChartRepository(t *testing.T) { - r, err := NewChartRepository(&Entry{ - Name: testRepository, - URL: testURL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) - } - - if err := r.Load(); err != nil { - t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) - } - - paths := []string{ - filepath.Join(testRepository, "frobnitz-1.2.3.tgz"), - filepath.Join(testRepository, "sprocket-1.1.0.tgz"), - filepath.Join(testRepository, "sprocket-1.2.0.tgz"), - filepath.Join(testRepository, "universe/zarthal-1.0.0.tgz"), - } - - if r.Config.Name != testRepository { - t.Errorf("Expected %s as Name but got %s", testRepository, r.Config.Name) - } - - if !reflect.DeepEqual(r.ChartPaths, paths) { - t.Errorf("Expected %#v but got %#v\n", paths, r.ChartPaths) - } - - if r.Config.URL != testURL { - t.Errorf("Expected url for chart repository to be %s but got %s", testURL, r.Config.URL) - } -} - -func TestIndex(t *testing.T) { - r, err := NewChartRepository(&Entry{ - Name: testRepository, - URL: testURL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) - } - - if err := r.Load(); err != nil { - t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) - } - - err = r.Index() - if err != nil { - t.Errorf("Error performing index: %v\n", err) - } - - tempIndexPath := filepath.Join(testRepository, indexPath) - actual, err := LoadIndexFile(tempIndexPath) - defer os.Remove(tempIndexPath) // clean up - if err != nil { - t.Errorf("Error loading index file %v", err) - } - verifyIndex(t, actual) - - // Re-index and test again. - err = r.Index() - if err != nil { - t.Errorf("Error performing re-index: %s\n", err) - } - second, err := LoadIndexFile(tempIndexPath) - if err != nil { - t.Errorf("Error re-loading index file %v", err) - } - verifyIndex(t, second) -} - -type CustomGetter struct { - repoUrls []string -} - -func (g *CustomGetter) Get(href string, options ...getter.Option) (*bytes.Buffer, error) { - index := &IndexFile{ - APIVersion: "v1", - Generated: time.Now(), - } - indexBytes, err := yaml.Marshal(index) - if err != nil { - return nil, err - } - g.repoUrls = append(g.repoUrls, href) - return bytes.NewBuffer(indexBytes), nil -} - -func TestIndexCustomSchemeDownload(t *testing.T) { - repoName := "gcs-repo" - repoURL := "gs://some-gcs-bucket" - myCustomGetter := &CustomGetter{} - customGetterConstructor := func(options ...getter.Option) (getter.Getter, error) { - return myCustomGetter, nil - } - providers := getter.Providers{{ - Schemes: []string{"gs"}, - New: customGetterConstructor, - }} - repo, err := NewChartRepository(&Entry{ - Name: repoName, - URL: repoURL, - }, providers) - if err != nil { - t.Fatalf("Problem loading chart repository from %s: %v", repoURL, err) - } - repo.CachePath = ensure.TempDir(t) - defer os.RemoveAll(repo.CachePath) - - tempIndexFile, err := ioutil.TempFile("", "test-repo") - if err != nil { - t.Fatalf("Failed to create temp index file: %v", err) - } - defer os.Remove(tempIndexFile.Name()) - - idx, err := repo.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %v", idx, err) - } - - if len(myCustomGetter.repoUrls) != 1 { - t.Fatalf("Custom Getter.Get should be called once") - } - - expectedRepoIndexURL := repoURL + "/index.yaml" - if myCustomGetter.repoUrls[0] != expectedRepoIndexURL { - t.Fatalf("Custom Getter.Get should be called with %s", expectedRepoIndexURL) - } -} - -func verifyIndex(t *testing.T, actual *IndexFile) { - var empty time.Time - if actual.Generated.Equal(empty) { - t.Errorf("Generated should be greater than 0: %s", actual.Generated) - } - - if actual.APIVersion != APIVersionV1 { - t.Error("Expected v1 API") - } - - entries := actual.Entries - if numEntries := len(entries); numEntries != 3 { - t.Errorf("Expected 3 charts to be listed in index file but got %v", numEntries) - } - - expects := map[string]ChartVersions{ - "frobnitz": { - { - Metadata: &chart.Metadata{ - Name: "frobnitz", - Version: "1.2.3", - }, - }, - }, - "sprocket": { - { - Metadata: &chart.Metadata{ - Name: "sprocket", - Version: "1.2.0", - }, - }, - { - Metadata: &chart.Metadata{ - Name: "sprocket", - Version: "1.1.0", - }, - }, - }, - "zarthal": { - { - Metadata: &chart.Metadata{ - Name: "zarthal", - Version: "1.0.0", - }, - }, - }, - } - - for name, versions := range expects { - got, ok := entries[name] - if !ok { - t.Errorf("Could not find %q entry", name) - continue - } - if len(versions) != len(got) { - t.Errorf("Expected %d versions, got %d", len(versions), len(got)) - continue - } - for i, e := range versions { - g := got[i] - if e.Name != g.Name { - t.Errorf("Expected %q, got %q", e.Name, g.Name) - } - if e.Version != g.Version { - t.Errorf("Expected %q, got %q", e.Version, g.Version) - } - if len(g.Keywords) != 3 { - t.Error("Expected 3 keywords.") - } - if len(g.Maintainers) != 2 { - t.Error("Expected 2 maintainers.") - } - if g.Created.Equal(empty) { - t.Error("Expected created to be non-empty") - } - if g.Description == "" { - t.Error("Expected description to be non-empty") - } - if g.Home == "" { - t.Error("Expected home to be non-empty") - } - if g.Digest == "" { - t.Error("Expected digest to be non-empty") - } - if len(g.URLs) != 1 { - t.Error("Expected exactly 1 URL") - } - } - } -} - -// startLocalServerForTests Start the local helm server -func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { - if handler == nil { - fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") - if err != nil { - return nil, err - } - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(fileBytes) - }) - } - - return httptest.NewServer(handler), nil -} - -// startLocalTLSServerForTests Start the local helm server with TLS -func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) { - if handler == nil { - fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") - if err != nil { - return nil, err - } - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(fileBytes) - }) - } - - return httptest.NewTLSServer(handler), nil -} - -func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { - srv, err := startLocalTLSServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Fatalf("%v", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } - - // If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority". - _, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{})) - // Go communicates with the platform and different platforms return different messages. Go itself tests darwin - // differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older - // versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test - // for both messages. - if runtime.GOOS == "darwin" { - if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) - } - } else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) - } -} - -func TestFindChartInRepoURL(t *testing.T) { - srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - chartURL, err := FindChartInRepoURL(srv.URL, "nginx", "", "", "", "", getter.All(&cli.EnvSettings{})) - if err != nil { - t.Fatalf("%v", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } - - chartURL, err = FindChartInRepoURL(srv.URL, "nginx", "0.1.0", "", "", "", getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.1.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } -} - -func TestErrorFindChartInRepoURL(t *testing.T) { - - g := getter.All(&cli.EnvSettings{ - RepositoryCache: ensure.TempDir(t), - }) - - if _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", g); err == nil { - t.Errorf("Expected error for bad chart URL, but did not get any errors") - } else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) { - t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err) - } - - srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - if _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", g); err == nil { - t.Errorf("Expected error for chart not found, but did not get any errors") - } else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } - - if _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", g); err == nil { - t.Errorf("Expected error for chart not found, but did not get any errors") - } else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } - - if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", g); err == nil { - t.Errorf("Expected error for no chart URLs available, but did not get any errors") - } else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } -} - -func TestResolveReferenceURL(t *testing.T) { - chartURL, err := ResolveReferenceURL("http://localhost:8123/charts/", "nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "http://localhost:8123/charts/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } - - chartURL, err = ResolveReferenceURL("http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } - - chartURL, err = ResolveReferenceURL("http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } - - chartURL, err = ResolveReferenceURL("http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } -} diff --git a/pkg/repo/doc.go b/pkg/repo/doc.go deleted file mode 100644 index 05650100b..000000000 --- a/pkg/repo/doc.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -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 diff --git a/pkg/repo/index.go b/pkg/repo/index.go deleted file mode 100644 index 1b65ac497..000000000 --- a/pkg/repo/index.go +++ /dev/null @@ -1,356 +0,0 @@ -/* -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" - "log" - "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") - // ErrEmptyIndexYaml indicates that the content of index.yaml is empty. - ErrEmptyIndexYaml = errors.New("empty index.yaml file") -) - -// 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 { - // This is used ONLY for validation against chartmuseum's index files and is discarded after validation. - ServerInfo map[string]interface{} `json:"serverInfo,omitempty"` - APIVersion string `json:"apiVersion"` - Generated time.Time `json:"generated"` - Entries map[string]ChartVersions `json:"entries"` - PublicKeys []string `json:"publicKeys,omitempty"` - - // Annotations are additional mappings uninterpreted by Helm. They are made available for - // other applications to add information to the index file. - Annotations map[string]string `json:"annotations,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 - } - i, err := loadIndex(b, path) - if err != nil { - return nil, errors.Wrapf(err, "error loading %s", path) - } - return i, nil -} - -// MustAdd adds a file to the index -// This can leave the index in an unsorted state -func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error { - if md.APIVersion == "" { - md.APIVersion = chart.APIVersionV1 - } - if err := md.Validate(); err != nil { - return errors.Wrapf(err, "validate failed for %s", filename) - } - - u := filename - if baseURL != "" { - _, file := filepath.Split(filename) - var err error - 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(), - } - ee := i.Entries[md.Name] - i.Entries[md.Name] = append(ee, cr) - return nil -} - -// Add adds a file to the index and logs an error. -// -// Deprecated: Use index.MustAdd instead. -func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { - if err := i.MustAdd(md, filename, baseURL, digest); err != nil { - log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) - } -} - -// 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"` - - // ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced - // this with Digest. However, with a strict YAML parser enabled, a field must be - // present on the struct for backwards compatibility. - ChecksumDeprecated string `json:"checksum,omitempty"` - - // EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. - EngineDeprecated string `json:"engine,omitempty"` - - // TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. - TillerVersionDeprecated string `json:"tillerVersion,omitempty"` - - // URLDeprecated is deprecated in Helm 3, superseded by URLs. It is ignored. However, - // with a strict YAML parser enabled, this must be present on the struct. - URLDeprecated string `json:"url,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 - } - if err := index.MustAdd(c.Metadata, fname, parentURL, hash); err != nil { - return index, errors.Wrapf(err, "failed adding to %s to index", fname) - } - } - return index, nil -} - -// loadIndex loads an index file and does minimal validity checking. -// -// The source parameter is only used for logging. -// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. -func loadIndex(data []byte, source string) (*IndexFile, error) { - i := &IndexFile{} - - if len(data) == 0 { - return i, ErrEmptyIndexYaml - } - - if err := yaml.UnmarshalStrict(data, i); err != nil { - return i, err - } - - for name, cvs := range i.Entries { - for idx := len(cvs) - 1; idx >= 0; idx-- { - if cvs[idx].APIVersion == "" { - cvs[idx].APIVersion = chart.APIVersionV1 - } - if err := cvs[idx].Validate(); err != nil { - log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err) - cvs = append(cvs[:idx], cvs[idx+1:]...) - } - } - } - i.SortEntries() - if i.APIVersion == "" { - return i, ErrNoAPIVersion - } - return i, nil -} diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go deleted file mode 100644 index a75a4177a..000000000 --- a/pkg/repo/index_test.go +++ /dev/null @@ -1,528 +0,0 @@ -/* -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 ( - "bufio" - "bytes" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "sort" - "strings" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" -) - -const ( - testfile = "testdata/local-index.yaml" - annotationstestfile = "testdata/local-index-annotations.yaml" - chartmuseumtestfile = "testdata/chartmuseum-index.yaml" - unorderedTestfile = "testdata/local-index-unordered.yaml" - testRepo = "test-repo" - indexWithDuplicates = ` -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - nginx: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" -` -) - -func TestIndexFile(t *testing.T) { - i := NewIndexFile() - for _, x := range []struct { - md *chart.Metadata - filename string - baseURL string - digest string - }{ - {&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, - {&chart.Metadata{APIVersion: "v2", Name: "cutter", Version: "0.1.1"}, "cutter-0.1.1.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "cutter", Version: "0.1.0"}, "cutter-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "cutter", Version: "0.2.0"}, "cutter-0.2.0.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "setter", Version: "0.1.9+alpha"}, "setter-0.1.9+alpha.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "setter", Version: "0.1.9+beta"}, "setter-0.1.9+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error adding to index: %s", err) - } - } - - i.SortEntries() - - if i.APIVersion != APIVersionV1 { - t.Error("Expected API version v1") - } - - if len(i.Entries) != 3 { - t.Errorf("Expected 3 charts. Got %d", len(i.Entries)) - } - - if i.Entries["clipper"][0].Name != "clipper" { - t.Errorf("Expected clipper, got %s", i.Entries["clipper"][0].Name) - } - - if len(i.Entries["cutter"]) != 3 { - t.Error("Expected three cutters.") - } - - // Test that the sort worked. 0.2 should be at the first index for Cutter. - if v := i.Entries["cutter"][0].Version; v != "0.2.0" { - t.Errorf("Unexpected first version: %s", v) - } - - cv, err := i.Get("setter", "0.1.9") - if err == nil && !strings.Contains(cv.Metadata.Version, "0.1.9") { - t.Errorf("Unexpected version: %s", cv.Metadata.Version) - } - - cv, err = i.Get("setter", "0.1.9+alpha") - if err != nil || cv.Metadata.Version != "0.1.9+alpha" { - t.Errorf("Expected version: 0.1.9+alpha") - } -} - -func TestLoadIndex(t *testing.T) { - - tests := []struct { - Name string - Filename string - }{ - { - Name: "regular index file", - Filename: testfile, - }, - { - Name: "chartmuseum index file", - Filename: chartmuseumtestfile, - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.Name, func(t *testing.T) { - t.Parallel() - i, err := LoadIndexFile(tc.Filename) - if err != nil { - t.Fatal(err) - } - verifyLocalIndex(t, i) - }) - } -} - -// TestLoadIndex_Duplicates is a regression to make sure that we don't non-deterministically allow duplicate packages. -func TestLoadIndex_Duplicates(t *testing.T) { - if _, err := loadIndex([]byte(indexWithDuplicates), "indexWithDuplicates"); err == nil { - t.Errorf("Expected an error when duplicate entries are present") - } -} - -func TestLoadIndex_Empty(t *testing.T) { - if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil { - t.Errorf("Expected an error when index.yaml is empty.") - } -} - -func TestLoadIndexFileAnnotations(t *testing.T) { - i, err := LoadIndexFile(annotationstestfile) - if err != nil { - t.Fatal(err) - } - verifyLocalIndex(t, i) - - if len(i.Annotations) != 1 { - t.Fatalf("Expected 1 annotation but got %d", len(i.Annotations)) - } - if i.Annotations["helm.sh/test"] != "foo bar" { - t.Error("Did not get expected value for helm.sh/test annotation") - } -} - -func TestLoadUnorderedIndex(t *testing.T) { - i, err := LoadIndexFile(unorderedTestfile) - if err != nil { - t.Fatal(err) - } - verifyLocalIndex(t, i) -} - -func TestMerge(t *testing.T) { - ind1 := NewIndexFile() - - if err := ind1.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.1.0"}, "dreadnought-0.1.0.tgz", "http://example.com", "aaaa"); err != nil { - t.Fatalf("unexpected error: %s", err) - } - - ind2 := NewIndexFile() - - for _, x := range []struct { - md *chart.Metadata - filename string - baseURL string - digest string - }{ - {&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.2.0"}, "dreadnought-0.2.0.tgz", "http://example.com", "aaaabbbb"}, - {&chart.Metadata{APIVersion: "v2", Name: "doughnut", Version: "0.2.0"}, "doughnut-0.2.0.tgz", "http://example.com", "ccccbbbb"}, - } { - if err := ind2.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error: %s", err) - } - } - - ind1.Merge(ind2) - - if len(ind1.Entries) != 2 { - t.Errorf("Expected 2 entries, got %d", len(ind1.Entries)) - } - - vs := ind1.Entries["dreadnought"] - if len(vs) != 2 { - t.Errorf("Expected 2 versions, got %d", len(vs)) - } - - if v := vs[1]; v.Version != "0.2.0" { - t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version) - } - -} - -func TestDownloadIndexFile(t *testing.T) { - t.Run("should download index file", func(t *testing.T) { - srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - r, err := NewChartRepository(&Entry{ - Name: testRepo, - URL: srv.URL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) - } - - idx, err := r.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %#v", idx, err) - } - - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created index file: %#v", err) - } - - i, err := LoadIndexFile(idx) - if err != nil { - t.Fatalf("Index %q failed to parse: %s", testfile, err) - } - verifyLocalIndex(t, i) - - // Check that charts file is also created - idx = filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created charts file: %#v", err) - } - - b, err := ioutil.ReadFile(idx) - if err != nil { - t.Fatalf("error reading charts file: %#v", err) - } - verifyLocalChartsFile(t, b, i) - }) - - t.Run("should not decode the path in the repo url while downloading index", func(t *testing.T) { - chartRepoURLPath := "/some%2Fpath/test" - fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") - if err != nil { - t.Fatal(err) - } - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.RawPath == chartRepoURLPath+"/index.yaml" { - w.Write(fileBytes) - } - }) - srv, err := startLocalServerForTests(handler) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - r, err := NewChartRepository(&Entry{ - Name: testRepo, - URL: srv.URL + chartRepoURLPath, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) - } - - idx, err := r.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %#v", idx, err) - } - - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created index file: %#v", err) - } - - i, err := LoadIndexFile(idx) - if err != nil { - t.Fatalf("Index %q failed to parse: %s", testfile, err) - } - verifyLocalIndex(t, i) - - // Check that charts file is also created - idx = filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created charts file: %#v", err) - } - - b, err := ioutil.ReadFile(idx) - if err != nil { - t.Fatalf("error reading charts file: %#v", err) - } - verifyLocalChartsFile(t, b, i) - }) -} - -func verifyLocalIndex(t *testing.T, i *IndexFile) { - numEntries := len(i.Entries) - if numEntries != 3 { - t.Errorf("Expected 3 entries in index file but got %d", numEntries) - } - - alpine, ok := i.Entries["alpine"] - if !ok { - t.Fatalf("'alpine' section not found.") - } - - if l := len(alpine); l != 1 { - t.Fatalf("'alpine' should have 1 chart, got %d", l) - } - - nginx, ok := i.Entries["nginx"] - if !ok || len(nginx) != 2 { - t.Fatalf("Expected 2 nginx entries") - } - - expects := []*ChartVersion{ - { - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "alpine", - Description: "string", - Version: "1.0.0", - Keywords: []string{"linux", "alpine", "small", "sumtin"}, - Home: "https://github.com/something", - }, - URLs: []string{ - "https://charts.helm.sh/stable/alpine-1.0.0.tgz", - "http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz", - }, - Digest: "sha256:1234567890abcdef", - }, - { - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "nginx", - Description: "string", - Version: "0.2.0", - Keywords: []string{"popular", "web server", "proxy"}, - Home: "https://github.com/something/else", - }, - URLs: []string{ - "https://charts.helm.sh/stable/nginx-0.2.0.tgz", - }, - Digest: "sha256:1234567890abcdef", - }, - { - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "nginx", - Description: "string", - Version: "0.1.0", - Keywords: []string{"popular", "web server", "proxy"}, - Home: "https://github.com/something", - }, - URLs: []string{ - "https://charts.helm.sh/stable/nginx-0.1.0.tgz", - }, - Digest: "sha256:1234567890abcdef", - }, - } - tests := []*ChartVersion{alpine[0], nginx[0], nginx[1]} - - for i, tt := range tests { - expect := expects[i] - if tt.Name != expect.Name { - t.Errorf("Expected name %q, got %q", expect.Name, tt.Name) - } - if tt.Description != expect.Description { - t.Errorf("Expected description %q, got %q", expect.Description, tt.Description) - } - if tt.Version != expect.Version { - t.Errorf("Expected version %q, got %q", expect.Version, tt.Version) - } - if tt.Digest != expect.Digest { - t.Errorf("Expected digest %q, got %q", expect.Digest, tt.Digest) - } - if tt.Home != expect.Home { - t.Errorf("Expected home %q, got %q", expect.Home, tt.Home) - } - - for i, url := range tt.URLs { - if url != expect.URLs[i] { - t.Errorf("Expected URL %q, got %q", expect.URLs[i], url) - } - } - for i, kw := range tt.Keywords { - if kw != expect.Keywords[i] { - t.Errorf("Expected keywords %q, got %q", expect.Keywords[i], kw) - } - } - } -} - -func verifyLocalChartsFile(t *testing.T, chartsContent []byte, indexContent *IndexFile) { - var expected, real []string - for chart := range indexContent.Entries { - expected = append(expected, chart) - } - sort.Strings(expected) - - scanner := bufio.NewScanner(bytes.NewReader(chartsContent)) - for scanner.Scan() { - real = append(real, scanner.Text()) - } - sort.Strings(real) - - if strings.Join(expected, " ") != strings.Join(real, " ") { - t.Errorf("Cached charts file content unexpected. Expected:\n%s\ngot:\n%s", expected, real) - } -} - -func TestIndexDirectory(t *testing.T) { - dir := "testdata/repository" - index, err := IndexDirectory(dir, "http://localhost:8080") - if err != nil { - t.Fatal(err) - } - - if l := len(index.Entries); l != 3 { - t.Fatalf("Expected 3 entries, got %d", l) - } - - // Other things test the entry generation more thoroughly. We just test a - // few fields. - - corpus := []struct{ chartName, downloadLink string }{ - {"frobnitz", "http://localhost:8080/frobnitz-1.2.3.tgz"}, - {"zarthal", "http://localhost:8080/universe/zarthal-1.0.0.tgz"}, - } - - for _, test := range corpus { - cname := test.chartName - frobs, ok := index.Entries[cname] - if !ok { - t.Fatalf("Could not read chart %s", cname) - } - - frob := frobs[0] - if frob.Digest == "" { - t.Errorf("Missing digest of file %s.", frob.Name) - } - if frob.URLs[0] != test.downloadLink { - t.Errorf("Unexpected URLs: %v", frob.URLs) - } - if frob.Name != cname { - t.Errorf("Expected %q, got %q", cname, frob.Name) - } - } -} - -func TestIndexAdd(t *testing.T) { - i := NewIndexFile() - - for _, x := range []struct { - md *chart.Metadata - filename string - baseURL string - digest string - }{ - - {&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, - {&chart.Metadata{APIVersion: "v2", Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, - {&chart.Metadata{APIVersion: "v2", Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890"}, - } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error adding to index: %s", err) - } - } - - if i.Entries["clipper"][0].URLs[0] != "http://example.com/charts/clipper-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/clipper-0.1.0.tgz, got %s", i.Entries["clipper"][0].URLs[0]) - } - if i.Entries["alpine"][0].URLs[0] != "http://example.com/charts/alpine-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/alpine-0.1.0.tgz, got %s", i.Entries["alpine"][0].URLs[0]) - } - if i.Entries["deis"][0].URLs[0] != "http://example.com/charts/deis-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) - } - - // test error condition - if err := i.MustAdd(&chart.Metadata{}, "error-0.1.0.tgz", "", ""); err == nil { - t.Fatal("expected error adding to index") - } -} - -func TestIndexWrite(t *testing.T) { - i := NewIndexFile() - if err := i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"); err != nil { - t.Fatalf("unexpected error: %s", err) - } - dir := t.TempDir() - testpath := filepath.Join(dir, "test") - i.WriteFile(testpath, 0600) - - got, err := ioutil.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(got), "clipper-0.1.0.tgz") { - t.Fatal("Index files doesn't contain expected content") - } -} diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go deleted file mode 100644 index 6f1e90dad..000000000 --- a/pkg/repo/repo.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -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) -} diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go deleted file mode 100644 index f87d2c202..000000000 --- a/pkg/repo/repo_test.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -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 ( - "io/ioutil" - "os" - "strings" - "testing" -) - -const testRepositoriesFile = "testdata/repositories.yaml" - -func TestFile(t *testing.T) { - rf := NewFile() - rf.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - if len(rf.Repositories) != 2 { - t.Fatal("Expected 2 repositories") - } - - if rf.Has("nosuchrepo") { - t.Error("Found nonexistent repo") - } - if !rf.Has("incubator") { - t.Error("incubator repo is missing") - } - - stable := rf.Repositories[0] - if stable.Name != "stable" { - t.Error("stable is not named stable") - } - if stable.URL != "https://example.com/stable/charts" { - t.Error("Wrong URL for stable") - } -} - -func TestNewFile(t *testing.T) { - expects := NewFile() - expects.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - file, err := LoadFile(testRepositoriesFile) - if err != nil { - t.Errorf("%q could not be loaded: %s", testRepositoriesFile, err) - } - - if len(expects.Repositories) != len(file.Repositories) { - t.Fatalf("Unexpected repo data: %#v", file.Repositories) - } - - for i, expect := range expects.Repositories { - got := file.Repositories[i] - if expect.Name != got.Name { - t.Errorf("Expected name %q, got %q", expect.Name, got.Name) - } - if expect.URL != got.URL { - t.Errorf("Expected url %q, got %q", expect.URL, got.URL) - } - } -} - -func TestRepoFile_Get(t *testing.T) { - repo := NewFile() - repo.Add( - &Entry{ - Name: "first", - URL: "https://example.com/first", - }, - &Entry{ - Name: "second", - URL: "https://example.com/second", - }, - &Entry{ - Name: "third", - URL: "https://example.com/third", - }, - &Entry{ - Name: "fourth", - URL: "https://example.com/fourth", - }, - ) - - name := "second" - - entry := repo.Get(name) - if entry == nil { - t.Fatalf("Expected repo entry %q to be found", name) - } - - if entry.URL != "https://example.com/second" { - t.Errorf("Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL) - } - - entry = repo.Get("nonexistent") - if entry != nil { - t.Errorf("Got unexpected entry %+v", entry) - } -} - -func TestRemoveRepository(t *testing.T) { - sampleRepository := NewFile() - sampleRepository.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - removeRepository := "stable" - found := sampleRepository.Remove(removeRepository) - if !found { - t.Errorf("expected repository %s not found", removeRepository) - } - - found = sampleRepository.Has(removeRepository) - if found { - t.Errorf("repository %s not deleted", removeRepository) - } -} - -func TestUpdateRepository(t *testing.T) { - sampleRepository := NewFile() - sampleRepository.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - newRepoName := "sample" - sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", - }) - - if !sampleRepository.Has(newRepoName) { - t.Errorf("expected repository %s not found", newRepoName) - } - repoCount := len(sampleRepository.Repositories) - - sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", - }) - - if repoCount != len(sampleRepository.Repositories) { - t.Errorf("invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) - } -} - -func TestWriteFile(t *testing.T) { - sampleRepository := NewFile() - sampleRepository.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - file, err := ioutil.TempFile("", "helm-repo") - if err != nil { - t.Errorf("failed to create test-file (%v)", err) - } - defer os.Remove(file.Name()) - if err := sampleRepository.WriteFile(file.Name(), 0644); err != nil { - t.Errorf("failed to write file (%v)", err) - } - - repos, err := LoadFile(file.Name()) - if err != nil { - t.Errorf("failed to load file (%v)", err) - } - for _, repo := range sampleRepository.Repositories { - if !repos.Has(repo.Name) { - t.Errorf("expected repository %s not found", repo.Name) - } - } -} - -func TestRepoNotExists(t *testing.T) { - if _, err := LoadFile("/this/path/does/not/exist.yaml"); err == nil { - t.Errorf("expected err to be non-nil when path does not exist") - } else if !strings.Contains(err.Error(), "couldn't load repositories file") { - t.Errorf("expected prompt `couldn't load repositories file`") - } -} diff --git a/pkg/repo/repotest/doc.go b/pkg/repo/repotest/doc.go deleted file mode 100644 index 3bf98aa7e..000000000 --- a/pkg/repo/repotest/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 repotest provides utilities for testing. - -The server provides a testing server that can be set up and torn down quickly. -*/ -package repotest diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go deleted file mode 100644 index 90ad3d856..000000000 --- a/pkg/repo/repotest/server.go +++ /dev/null @@ -1,425 +0,0 @@ -/* -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 repotest - -import ( - "context" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - "time" - - "github.com/distribution/distribution/v3/configuration" - "github.com/distribution/distribution/v3/registry" - _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" // used for docker test registry - _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" // used for docker test registry - "github.com/phayes/freeport" - "golang.org/x/crypto/bcrypt" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - ociRegistry "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// NewTempServerWithCleanup creates a server inside of a temp dir. -// -// If the passed in string is not "", it will be treated as a shell glob, and files -// will be copied from that path to the server's docroot. -// -// The caller is responsible for stopping the server. -// The temp dir will be removed by testing package automatically when test finished. -func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) { - srv, err := NewTempServer(glob) - t.Cleanup(func() { os.RemoveAll(srv.docroot) }) - return srv, err -} - -// Set up a fake repo with basic auth enabled -func NewTempServerWithCleanupAndBasicAuth(t *testing.T, glob string) *Server { - srv, err := NewTempServerWithCleanup(t, glob) - srv.Stop() - if err != nil { - t.Fatal(err) - } - srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - })) - srv.Start() - return srv -} - -type OCIServer struct { - *registry.Registry - RegistryURL string - Dir string - TestUsername string - TestPassword string - Client *ociRegistry.Client -} - -type OCIServerRunConfig struct { - DependingChart *chart.Chart -} - -type OCIServerOpt func(config *OCIServerRunConfig) - -func WithDependingChart(c *chart.Chart) OCIServerOpt { - return func(config *OCIServerRunConfig) { - config.DependingChart = c - } -} - -func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { - testHtpasswdFileBasename := "authtest.htpasswd" - testUsername, testPassword := "username", "password" - - pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - if err != nil { - t.Fatal("error generating bcrypt password for test htpasswd file") - } - htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename) - err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644) - if err != nil { - t.Fatalf("error creating test htpasswd file") - } - - // Registry config - config := &configuration.Configuration{} - port, err := freeport.GetFreePort() - if err != nil { - t.Fatalf("error finding free port for test registry") - } - - config.HTTP.Addr = fmt.Sprintf(":%d", port) - config.HTTP.DrainTimeout = time.Duration(10) * time.Second - config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, - } - - registryURL := fmt.Sprintf("localhost:%d", port) - - r, err := registry.NewRegistry(context.Background(), config) - if err != nil { - t.Fatal(err) - } - - return &OCIServer{ - Registry: r, - RegistryURL: registryURL, - TestUsername: testUsername, - TestPassword: testPassword, - Dir: dir, - }, nil -} - -func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { - cfg := &OCIServerRunConfig{} - for _, fn := range opts { - fn(cfg) - } - - go srv.ListenAndServe() - - credentialsFile := filepath.Join(srv.Dir, "config.json") - - // init test client - registryClient, err := ociRegistry.NewClient( - ociRegistry.ClientOptDebug(true), - ociRegistry.ClientOptEnableCache(true), - ociRegistry.ClientOptWriter(os.Stdout), - ociRegistry.ClientOptCredentialsFile(credentialsFile), - ) - if err != nil { - t.Fatalf("error creating registry client") - } - - err = registryClient.Login( - srv.RegistryURL, - ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword), - ociRegistry.LoginOptInsecure(false)) - if err != nil { - t.Fatalf("error logging into registry with good credentials") - } - - ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL) - - err = chartutil.ExpandFile(srv.Dir, filepath.Join(srv.Dir, "oci-dependent-chart-0.1.0.tgz")) - if err != nil { - t.Fatal(err) - } - - // valid chart - ch, err := loader.LoadDir(filepath.Join(srv.Dir, "oci-dependent-chart")) - if err != nil { - t.Fatal("error loading chart") - } - - err = os.RemoveAll(filepath.Join(srv.Dir, "oci-dependent-chart")) - if err != nil { - t.Fatal("error removing chart before push") - } - - // save it back to disk.. - absPath, err := chartutil.Save(ch, srv.Dir) - if err != nil { - t.Fatal("could not create chart archive") - } - - // load it into memory... - contentBytes, err := ioutil.ReadFile(absPath) - if err != nil { - t.Fatal("could not load chart into memory") - } - - result, err := registryClient.Push(contentBytes, ref) - if err != nil { - t.Fatalf("error pushing dependent chart: %s", err) - } - t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+ - "Config.Digest: %s, Config.Size: %d, "+ - "Chart.Digest: %s, Chart.Size: %d", - result.Manifest.Digest, result.Manifest.Size, - result.Config.Digest, result.Config.Size, - result.Chart.Digest, result.Chart.Size) - - srv.Client = registryClient - c := cfg.DependingChart - if c == nil { - return - } - - dependingRef := fmt.Sprintf("%s/u/ocitestuser/%s:%s", - srv.RegistryURL, c.Metadata.Name, c.Metadata.Version) - - // load it into memory... - absPath = filepath.Join(srv.Dir, - fmt.Sprintf("%s-%s.tgz", c.Metadata.Name, c.Metadata.Version)) - contentBytes, err = ioutil.ReadFile(absPath) - if err != nil { - t.Fatal("could not load chart into memory") - } - - result, err = registryClient.Push(contentBytes, dependingRef) - if err != nil { - t.Fatalf("error pushing depending chart: %s", err) - } - t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+ - "Config.Digest: %s, Config.Size: %d, "+ - "Chart.Digest: %s, Chart.Size: %d", - result.Manifest.Digest, result.Manifest.Size, - result.Config.Digest, result.Config.Size, - result.Chart.Digest, result.Chart.Size) -} - -// NewTempServer creates a server inside of a temp dir. -// -// If the passed in string is not "", it will be treated as a shell glob, and files -// will be copied from that path to the server's docroot. -// -// The caller is responsible for destroying the temp directory as well as stopping -// the server. -// -// Deprecated: use NewTempServerWithCleanup -func NewTempServer(glob string) (*Server, error) { - tdir, err := ioutil.TempDir("", "helm-repotest-") - if err != nil { - return nil, err - } - srv := NewServer(tdir) - - if glob != "" { - if _, err := srv.CopyCharts(glob); err != nil { - srv.Stop() - return srv, err - } - } - - return srv, nil -} - -// NewServer creates a repository server for testing. -// -// docroot should be a temp dir managed by the caller. -// -// This will start the server, serving files off of the docroot. -// -// Use CopyCharts to move charts into the repository and then index them -// for service. -func NewServer(docroot string) *Server { - root, err := filepath.Abs(docroot) - if err != nil { - panic(err) - } - srv := &Server{ - docroot: root, - } - srv.Start() - // Add the testing repository as the only repo. - if err := setTestingRepository(srv.URL(), filepath.Join(root, "repositories.yaml")); err != nil { - panic(err) - } - return srv -} - -// Server is an implementation of a repository server for testing. -type Server struct { - docroot string - srv *httptest.Server - middleware http.HandlerFunc -} - -// WithMiddleware injects middleware in front of the server. This can be used to inject -// additional functionality like layering in an authentication frontend. -func (s *Server) WithMiddleware(middleware http.HandlerFunc) { - s.middleware = middleware -} - -// Root gets the docroot for the server. -func (s *Server) Root() string { - return s.docroot -} - -// CopyCharts takes a glob expression and copies those charts to the server root. -func (s *Server) CopyCharts(origin string) ([]string, error) { - files, err := filepath.Glob(origin) - if err != nil { - return []string{}, err - } - copied := make([]string, len(files)) - for i, f := range files { - base := filepath.Base(f) - newname := filepath.Join(s.docroot, base) - data, err := ioutil.ReadFile(f) - if err != nil { - return []string{}, err - } - if err := ioutil.WriteFile(newname, data, 0644); err != nil { - return []string{}, err - } - copied[i] = newname - } - - err = s.CreateIndex() - return copied, err -} - -// CreateIndex will read docroot and generate an index.yaml file. -func (s *Server) CreateIndex() error { - // generate the index - index, err := repo.IndexDirectory(s.docroot, s.URL()) - if err != nil { - return err - } - - d, err := yaml.Marshal(index) - if err != nil { - return err - } - - ifile := filepath.Join(s.docroot, "index.yaml") - return ioutil.WriteFile(ifile, d, 0644) -} - -func (s *Server) Start() { - s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.middleware != nil { - s.middleware.ServeHTTP(w, r) - } - http.FileServer(http.Dir(s.docroot)).ServeHTTP(w, r) - })) -} - -func (s *Server) StartTLS() { - cd := "../../testdata" - ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") - - s.srv = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.middleware != nil { - s.middleware.ServeHTTP(w, r) - } - http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r) - })) - tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca) - if err != nil { - panic(err) - } - tlsConf.ServerName = "helm.sh" - s.srv.TLS = tlsConf - s.srv.StartTLS() - - // Set up repositories config with ca file - repoConfig := filepath.Join(s.Root(), "repositories.yaml") - - r := repo.NewFile() - r.Add(&repo.Entry{ - Name: "test", - URL: s.URL(), - CAFile: filepath.Join("../../testdata", "rootca.crt"), - }) - - if err := r.WriteFile(repoConfig, 0644); err != nil { - panic(err) - } -} - -// Stop stops the server and closes all connections. -// -// It should be called explicitly. -func (s *Server) Stop() { - s.srv.Close() -} - -// URL returns the URL of the server. -// -// Example: -// http://localhost:1776 -func (s *Server) URL() string { - return s.srv.URL -} - -// LinkIndices links the index created with CreateIndex and makes a symbolic link to the cache index. -// -// This makes it possible to simulate a local cache of a repository. -func (s *Server) LinkIndices() error { - lstart := filepath.Join(s.docroot, "index.yaml") - ldest := filepath.Join(s.docroot, "test-index.yaml") - return os.Symlink(lstart, ldest) -} - -// setTestingRepository sets up a testing repository.yaml with only the given URL. -func setTestingRepository(url, fname string) error { - r := repo.NewFile() - r.Add(&repo.Entry{ - Name: "test", - URL: url, - }) - return r.WriteFile(fname, 0644) -} diff --git a/pkg/repo/repotest/server_test.go b/pkg/repo/repotest/server_test.go deleted file mode 100644 index 1ad979fdc..000000000 --- a/pkg/repo/repotest/server_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -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 repotest - -import ( - "io/ioutil" - "net/http" - "os" - "path/filepath" - "testing" - - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/repo" -) - -// Young'n, in these here parts, we test our tests. - -func TestServer(t *testing.T) { - defer ensure.HelmHome(t)() - - rootDir := ensure.TempDir(t) - defer os.RemoveAll(rootDir) - - srv := NewServer(rootDir) - defer srv.Stop() - - c, err := srv.CopyCharts("testdata/*.tgz") - if err != nil { - // Some versions of Go don't correctly fire defer on Fatal. - t.Fatal(err) - } - - if len(c) != 1 { - t.Errorf("Unexpected chart count: %d", len(c)) - } - - if filepath.Base(c[0]) != "examplechart-0.1.0.tgz" { - t.Errorf("Unexpected chart: %s", c[0]) - } - - res, err := http.Get(srv.URL() + "/examplechart-0.1.0.tgz") - res.Body.Close() - if err != nil { - t.Fatal(err) - } - - if res.ContentLength < 500 { - t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) - } - - res, err = http.Get(srv.URL() + "/index.yaml") - if err != nil { - t.Fatal(err) - } - - data, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - - m := repo.NewIndexFile() - if err := yaml.Unmarshal(data, m); err != nil { - t.Fatal(err) - } - - if l := len(m.Entries); l != 1 { - t.Fatalf("Expected 1 entry, got %d", l) - } - - expect := "examplechart" - if !m.Has(expect, "0.1.0") { - t.Errorf("missing %q", expect) - } - - res, err = http.Get(srv.URL() + "/index.yaml-nosuchthing") - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 404 { - t.Fatalf("Expected 404, got %d", res.StatusCode) - } -} - -func TestNewTempServer(t *testing.T) { - defer ensure.HelmHome(t)() - - srv, err := NewTempServerWithCleanup(t, "testdata/examplechart-0.1.0.tgz") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - - res, err := http.Head(srv.URL() + "/examplechart-0.1.0.tgz") - res.Body.Close() - if err != nil { - t.Error(err) - } - if res.StatusCode != 200 { - t.Errorf("Expected 200, got %d", res.StatusCode) - } -} diff --git a/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz b/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz deleted file mode 100644 index c5ea741eb..000000000 Binary files a/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz and /dev/null differ diff --git a/pkg/repo/repotest/testdata/examplechart/.helmignore b/pkg/repo/repotest/testdata/examplechart/.helmignore deleted file mode 100644 index f0c131944..000000000 --- a/pkg/repo/repotest/testdata/examplechart/.helmignore +++ /dev/null @@ -1,21 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*~ -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/pkg/repo/repotest/testdata/examplechart/Chart.yaml b/pkg/repo/repotest/testdata/examplechart/Chart.yaml deleted file mode 100644 index a7d297285..000000000 --- a/pkg/repo/repotest/testdata/examplechart/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: examplechart -version: 0.1.0 diff --git a/pkg/repo/repotest/testdata/examplechart/values.yaml b/pkg/repo/repotest/testdata/examplechart/values.yaml deleted file mode 100644 index 5170c61e3..000000000 --- a/pkg/repo/repotest/testdata/examplechart/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for examplechart. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name: value diff --git a/pkg/repo/testdata/chartmuseum-index.yaml b/pkg/repo/testdata/chartmuseum-index.yaml deleted file mode 100644 index 349a529aa..000000000 --- a/pkg/repo/testdata/chartmuseum-index.yaml +++ /dev/null @@ -1,54 +0,0 @@ -serverInfo: - contextPath: /v1/helm -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 diff --git a/pkg/repo/testdata/local-index-annotations.yaml b/pkg/repo/testdata/local-index-annotations.yaml deleted file mode 100644 index 833ab854b..000000000 --- a/pkg/repo/testdata/local-index-annotations.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 -annotations: - helm.sh/test: foo bar diff --git a/pkg/repo/testdata/local-index-unordered.yaml b/pkg/repo/testdata/local-index-unordered.yaml deleted file mode 100644 index cdfaa7f24..000000000 --- a/pkg/repo/testdata/local-index-unordered.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 diff --git a/pkg/repo/testdata/local-index.yaml b/pkg/repo/testdata/local-index.yaml deleted file mode 100644 index d61f40dda..000000000 --- a/pkg/repo/testdata/local-index.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 diff --git a/pkg/repo/testdata/old-repositories.yaml b/pkg/repo/testdata/old-repositories.yaml deleted file mode 100644 index 3fb55b060..000000000 --- a/pkg/repo/testdata/old-repositories.yaml +++ /dev/null @@ -1,3 +0,0 @@ -best-charts-ever: http://best-charts-ever.com -okay-charts: http://okay-charts.org -example123: http://examplecharts.net/charts/123 diff --git a/pkg/repo/testdata/repositories.yaml b/pkg/repo/testdata/repositories.yaml deleted file mode 100644 index a28c48eab..000000000 --- a/pkg/repo/testdata/repositories.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -repositories: - - name: stable - url: https://example.com/stable/charts - cache: stable-index.yaml - - name: incubator - url: https://example.com/incubator - cache: incubator-index.yaml diff --git a/pkg/repo/testdata/repository/frobnitz-1.2.3.tgz b/pkg/repo/testdata/repository/frobnitz-1.2.3.tgz deleted file mode 100644 index 8731dce02..000000000 Binary files a/pkg/repo/testdata/repository/frobnitz-1.2.3.tgz and /dev/null differ diff --git a/pkg/repo/testdata/repository/sprocket-1.1.0.tgz b/pkg/repo/testdata/repository/sprocket-1.1.0.tgz deleted file mode 100644 index 48d65f491..000000000 Binary files a/pkg/repo/testdata/repository/sprocket-1.1.0.tgz and /dev/null differ diff --git a/pkg/repo/testdata/repository/sprocket-1.2.0.tgz b/pkg/repo/testdata/repository/sprocket-1.2.0.tgz deleted file mode 100644 index 6fdc73c2b..000000000 Binary files a/pkg/repo/testdata/repository/sprocket-1.2.0.tgz and /dev/null differ diff --git a/pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz b/pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz deleted file mode 100644 index 6f1e8564c..000000000 Binary files a/pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz and /dev/null differ diff --git a/pkg/repo/testdata/server/index.yaml b/pkg/repo/testdata/server/index.yaml deleted file mode 100644 index d627928b2..000000000 --- a/pkg/repo/testdata/server/index.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" diff --git a/pkg/repo/testdata/server/test.txt b/pkg/repo/testdata/server/test.txt deleted file mode 100644 index 557db03de..000000000 --- a/pkg/repo/testdata/server/test.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go deleted file mode 100644 index 94c278875..000000000 --- a/pkg/storage/driver/cfgmaps.go +++ /dev/null @@ -1,257 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "context" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kblabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/validation" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*ConfigMaps)(nil) - -// ConfigMapsDriverName is the string name of the driver. -const ConfigMapsDriverName = "ConfigMap" - -// ConfigMaps is a wrapper around an implementation of a kubernetes -// ConfigMapsInterface. -type ConfigMaps struct { - impl corev1.ConfigMapInterface - Log func(string, ...interface{}) -} - -// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of -// the kubernetes ConfigMapsInterface. -func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { - return &ConfigMaps{ - impl: impl, - Log: func(_ string, _ ...interface{}) {}, - } -} - -// Name returns the name of the driver. -func (cfgmaps *ConfigMaps) Name() string { - return ConfigMapsDriverName -} - -// Get fetches the release named by key. The corresponding release is returned -// or error if not found. -func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { - // fetch the configmap holding the release named by key - obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - return nil, ErrReleaseNotFound - } - - cfgmaps.Log("get: failed to get %q: %s", key, err) - return nil, err - } - // found the configmap, decode the base64 data string - r, err := decodeRelease(obj.Data["release"]) - if err != nil { - cfgmaps.Log("get: failed to decode data %q: %s", key, err) - return nil, err - } - // return the release object - return r, nil -} - -// List fetches all releases and returns the list releases such -// that filter(release) == true. An error is returned if the -// configmap fails to retrieve the releases. -func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - lsel := kblabels.Set{"owner": "helm"}.AsSelector() - opts := metav1.ListOptions{LabelSelector: lsel.String()} - - list, err := cfgmaps.impl.List(context.Background(), opts) - if err != nil { - cfgmaps.Log("list: failed to list: %s", err) - return nil, err - } - - var results []*rspb.Release - - // iterate over the configmaps object list - // and decode each release - for _, item := range list.Items { - rls, err := decodeRelease(item.Data["release"]) - if err != nil { - cfgmaps.Log("list: failed to decode release: %v: %s", item, err) - continue - } - - rls.Labels = item.ObjectMeta.Labels - - if filter(rls) { - results = append(results, rls) - } - } - return results, nil -} - -// Query fetches all releases that match the provided map of labels. -// An error is returned if the configmap fails to retrieve the releases. -func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) { - ls := kblabels.Set{} - for k, v := range labels { - if errs := validation.IsValidLabelValue(v); len(errs) != 0 { - return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) - } - ls[k] = v - } - - opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} - - list, err := cfgmaps.impl.List(context.Background(), opts) - if err != nil { - cfgmaps.Log("query: failed to query with labels: %s", err) - return nil, err - } - - if len(list.Items) == 0 { - return nil, ErrReleaseNotFound - } - - var results []*rspb.Release - for _, item := range list.Items { - rls, err := decodeRelease(item.Data["release"]) - if err != nil { - cfgmaps.Log("query: failed to decode release: %s", err) - continue - } - results = append(results, rls) - } - return results, nil -} - -// Create creates a new ConfigMap holding the release. If the -// ConfigMap already exists, ErrReleaseExists is returned. -func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { - // set labels for configmaps object meta data - var lbs labels - - lbs.init() - lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new configmap to hold the release - obj, err := newConfigMapsObject(key, rls, lbs) - if err != nil { - cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) - return err - } - // push the configmap object out into the kubiverse - if _, err := cfgmaps.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { - if apierrors.IsAlreadyExists(err) { - return ErrReleaseExists - } - - cfgmaps.Log("create: failed to create: %s", err) - return err - } - return nil -} - -// Update updates the ConfigMap holding the release. If not found -// the ConfigMap is created to hold the release. -func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { - // set labels for configmaps object meta data - var lbs labels - - lbs.init() - lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new configmap object to hold the release - obj, err := newConfigMapsObject(key, rls, lbs) - if err != nil { - cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) - return err - } - // push the configmap object out into the kubiverse - _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) - if err != nil { - cfgmaps.Log("update: failed to update: %s", err) - return err - } - return nil -} - -// Delete deletes the ConfigMap holding the release named by key. -func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) { - // fetch the release to check existence - if rls, err = cfgmaps.Get(key); err != nil { - return nil, err - } - // delete the release - if err = cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil { - return rls, err - } - return rls, nil -} - -// newConfigMapsObject constructs a kubernetes ConfigMap object -// to store a release. Each configmap data entry is the base64 -// encoded gzipped string of a release. -// -// The following labels are used within each configmap: -// -// "modifiedAt" - timestamp indicating when this configmap was last modified. (set in Update) -// "createdAt" - timestamp indicating when this configmap was created. (set in Create) -// "version" - version of the release. -// "status" - status of the release (see pkg/release/status.go for variants) -// "owner" - owner of the configmap, currently "helm". -// "name" - name of the release. -// -func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) { - const owner = "helm" - - // encode the release - s, err := encodeRelease(rls) - if err != nil { - return nil, err - } - - if lbs == nil { - lbs.init() - } - - // apply labels - lbs.set("name", rls.Name) - lbs.set("owner", owner) - lbs.set("status", rls.Info.Status.String()) - lbs.set("version", strconv.Itoa(rls.Version)) - - // create and return configmap object - return &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: key, - Labels: lbs.toMap(), - }, - Data: map[string]string{"release": s}, - }, nil -} diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go deleted file mode 100644 index 626c36cb9..000000000 --- a/pkg/storage/driver/cfgmaps_test.go +++ /dev/null @@ -1,241 +0,0 @@ -/* -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 driver - -import ( - "encoding/base64" - "encoding/json" - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestConfigMapName(t *testing.T) { - c := newTestFixtureCfgMaps(t) - if c.Name() != ConfigMapsDriverName { - t.Errorf("Expected name to be %q, got %q", ConfigMapsDriverName, c.Name()) - } -} - -func TestConfigMapGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) - - // get release with key - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestUncompressedConfigMapGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // Create a test fixture which contains an uncompressed release - cfgmap, err := newConfigMapsObject(key, rel, nil) - if err != nil { - t.Fatalf("Failed to create configmap: %s", err) - } - b, err := json.Marshal(rel) - if err != nil { - t.Fatalf("Failed to marshal release: %s", err) - } - cfgmap.Data["release"] = base64.StdEncoding.EncodeToString(b) - var mock MockConfigMapsInterface - mock.objects = map[string]*v1.ConfigMap{key: cfgmap} - cfgmaps := NewConfigMaps(&mock) - - // get release with key - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestConfigMapList(t *testing.T) { - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - // list all deleted releases - del, err := cfgmaps.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted: %s", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } - - // list all deployed releases - dpl, err := cfgmaps.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } - - // list all superseded releases - ssd, err := cfgmaps.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded: %s", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d", len(ssd)) - } -} - -func TestConfigMapQuery(t *testing.T) { - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - rls, err := cfgmaps.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Failed to query: %s", err) - } - if len(rls) != 2 { - t.Errorf("Expected 2 results, got %d", len(rls)) - } - - _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} - -func TestConfigMapCreate(t *testing.T) { - cfgmaps := newTestFixtureCfgMaps(t) - - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // store the release in a configmap - if err := cfgmaps.Create(key, rel); err != nil { - t.Fatalf("Failed to create release with key %q: %s", key, err) - } - - // get the release back - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // compare created release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestConfigMapUpdate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) - - // modify release status code - rel.Info.Status = rspb.StatusSuperseded - - // perform the update - if err := cfgmaps.Update(key, rel); err != nil { - t.Fatalf("Failed to update release: %s", err) - } - - // fetch the updated release - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // check release has actually been updated by comparing modified fields - if rel.Info.Status != got.Info.Status { - t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) - } -} - -func TestConfigMapDelete(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) - - // perform the delete on a non-existent release - _, err := cfgmaps.Delete("nonexistent") - if err != ErrReleaseNotFound { - t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err) - } - - // perform the delete - rls, err := cfgmaps.Delete(key) - if err != nil { - t.Fatalf("Failed to delete release with key %q: %s", key, err) - } - if !reflect.DeepEqual(rel, rls) { - t.Errorf("Expected {%v}, got {%v}", rel, rls) - } - - // fetch the deleted release - _, err = cfgmaps.Get(key) - if !reflect.DeepEqual(ErrReleaseNotFound, err) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go deleted file mode 100644 index 9c01f3766..000000000 --- a/pkg/storage/driver/driver.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "fmt" - - "github.com/pkg/errors" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var ( - // ErrReleaseNotFound indicates that a release is not found. - ErrReleaseNotFound = errors.New("release: not found") - // ErrReleaseExists indicates that a release already exists. - ErrReleaseExists = errors.New("release: already exists") - // ErrInvalidKey indicates that a release key could not be parsed. - ErrInvalidKey = errors.New("release: invalid key") - // ErrNoDeployedReleases indicates that there are no releases with the given key in the deployed state - ErrNoDeployedReleases = errors.New("has no deployed releases") -) - -// StorageDriverError records an error and the release name that caused it -type StorageDriverError struct { - ReleaseName string - Err error -} - -func (e *StorageDriverError) Error() string { - return fmt.Sprintf("%q %s", e.ReleaseName, e.Err.Error()) -} - -func (e *StorageDriverError) Unwrap() error { return e.Err } - -func NewErrNoDeployedReleases(releaseName string) error { - return &StorageDriverError{ - ReleaseName: releaseName, - Err: ErrNoDeployedReleases, - } -} - -// Creator is the interface that wraps the Create method. -// -// Create stores the release or returns ErrReleaseExists -// if an identical release already exists. -type Creator interface { - Create(key string, rls *rspb.Release) error -} - -// Updator is the interface that wraps the Update method. -// -// Update updates an existing release or returns -// ErrReleaseNotFound if the release does not exist. -type Updator interface { - Update(key string, rls *rspb.Release) error -} - -// Deletor is the interface that wraps the Delete method. -// -// Delete deletes the release named by key or returns -// ErrReleaseNotFound if the release does not exist. -type Deletor interface { - Delete(key string) (*rspb.Release, error) -} - -// Queryor is the interface that wraps the Get and List methods. -// -// Get returns the release named by key or returns ErrReleaseNotFound -// if the release does not exist. -// -// List returns the set of all releases that satisfy the filter predicate. -// -// Query returns the set of all releases that match the provided label set. -type Queryor interface { - Get(key string) (*rspb.Release, error) - List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) - Query(labels map[string]string) ([]*rspb.Release, error) -} - -// Driver is the interface composed of Creator, Updator, Deletor, and Queryor -// interfaces. It defines the behavior for storing, updating, deleted, -// and retrieving Helm releases from some underlying storage mechanism, -// e.g. memory, configmaps. -type Driver interface { - Creator - Updator - Deletor - Queryor - Name() string -} diff --git a/pkg/storage/driver/labels.go b/pkg/storage/driver/labels.go deleted file mode 100644 index eb7118fe5..000000000 --- a/pkg/storage/driver/labels.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 driver - -// labels is a map of key value pairs to be included as metadata in a configmap object. -type labels map[string]string - -func (lbs *labels) init() { *lbs = labels(make(map[string]string)) } -func (lbs labels) get(key string) string { return lbs[key] } -func (lbs labels) set(key, val string) { lbs[key] = val } - -func (lbs labels) keys() (ls []string) { - for key := range lbs { - ls = append(ls, key) - } - return -} - -func (lbs labels) match(set labels) bool { - for _, key := range set.keys() { - if lbs.get(key) != set.get(key) { - return false - } - } - return true -} - -func (lbs labels) toMap() map[string]string { return lbs } - -func (lbs *labels) fromMap(kvs map[string]string) { - for k, v := range kvs { - lbs.set(k, v) - } -} diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go deleted file mode 100644 index bfd80911b..000000000 --- a/pkg/storage/driver/labels_test.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "testing" -) - -func TestLabelsMatch(t *testing.T) { - var tests = []struct { - desc string - set1 labels - set2 labels - expect bool - }{ - { - "equal labels sets", - labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), - labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), - true, - }, - { - "disjoint label sets", - labels(map[string]string{"KEY_C": "VAL_C", "KEY_D": "VAL_D"}), - labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), - false, - }, - } - - for _, tt := range tests { - if !tt.set1.match(tt.set2) && tt.expect { - t.Fatalf("Expected match '%s'\n", tt.desc) - } - } -} diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go deleted file mode 100644 index 91378f588..000000000 --- a/pkg/storage/driver/memory.go +++ /dev/null @@ -1,240 +0,0 @@ -/* -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 driver - -import ( - "strconv" - "strings" - "sync" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*Memory)(nil) - -const ( - // MemoryDriverName is the string name of this driver. - MemoryDriverName = "Memory" - - defaultNamespace = "default" -) - -// A map of release names to list of release records -type memReleases map[string]records - -// Memory is the in-memory storage driver implementation. -type Memory struct { - sync.RWMutex - namespace string - // A map of namespaces to releases - cache map[string]memReleases -} - -// NewMemory initializes a new memory driver. -func NewMemory() *Memory { - return &Memory{cache: map[string]memReleases{}, namespace: "default"} -} - -// SetNamespace sets a specific namespace in which releases will be accessed. -// An empty string indicates all namespaces (for the list operation) -func (mem *Memory) SetNamespace(ns string) { - mem.namespace = ns -} - -// Name returns the name of the driver. -func (mem *Memory) Name() string { - return MemoryDriverName -} - -// Get returns the release named by key or returns ErrReleaseNotFound. -func (mem *Memory) Get(key string) (*rspb.Release, error) { - defer unlock(mem.rlock()) - - keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") - switch elems := strings.Split(keyWithoutPrefix, ".v"); len(elems) { - case 2: - name, ver := elems[0], elems[1] - if _, err := strconv.Atoi(ver); err != nil { - return nil, ErrInvalidKey - } - if recs, ok := mem.cache[mem.namespace][name]; ok { - if r := recs.Get(key); r != nil { - return r.rls, nil - } - } - return nil, ErrReleaseNotFound - default: - return nil, ErrInvalidKey - } -} - -// List returns the list of all releases such that filter(release) == true -func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - defer unlock(mem.rlock()) - - var ls []*rspb.Release - for namespace := range mem.cache { - if mem.namespace != "" { - // Should only list releases of this namespace - namespace = mem.namespace - } - for _, recs := range mem.cache[namespace] { - recs.Iter(func(_ int, rec *record) bool { - if filter(rec.rls) { - ls = append(ls, rec.rls) - } - return true - }) - } - if mem.namespace != "" { - // Should only list releases of this namespace - break - } - } - return ls, nil -} - -// Query returns the set of releases that match the provided set of labels -func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { - defer unlock(mem.rlock()) - - var lbs labels - - lbs.init() - lbs.fromMap(keyvals) - - var ls []*rspb.Release - for namespace := range mem.cache { - if mem.namespace != "" { - // Should only query releases of this namespace - namespace = mem.namespace - } - for _, recs := range mem.cache[namespace] { - recs.Iter(func(_ int, rec *record) bool { - // A query for a release name that doesn't exist (has been deleted) - // can cause rec to be nil. - if rec == nil { - return false - } - if rec.lbs.match(lbs) { - ls = append(ls, rec.rls) - } - return true - }) - } - if mem.namespace != "" { - // Should only query releases of this namespace - break - } - } - - if len(ls) == 0 { - return nil, ErrReleaseNotFound - } - - return ls, nil -} - -// Create creates a new release or returns ErrReleaseExists. -func (mem *Memory) Create(key string, rls *rspb.Release) error { - defer unlock(mem.wlock()) - - // For backwards compatibility, we protect against an unset namespace - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - mem.SetNamespace(namespace) - - if _, ok := mem.cache[namespace]; !ok { - mem.cache[namespace] = memReleases{} - } - - if recs, ok := mem.cache[namespace][rls.Name]; ok { - if err := recs.Add(newRecord(key, rls)); err != nil { - return err - } - mem.cache[namespace][rls.Name] = recs - return nil - } - mem.cache[namespace][rls.Name] = records{newRecord(key, rls)} - return nil -} - -// Update updates a release or returns ErrReleaseNotFound. -func (mem *Memory) Update(key string, rls *rspb.Release) error { - defer unlock(mem.wlock()) - - // For backwards compatibility, we protect against an unset namespace - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - mem.SetNamespace(namespace) - - if _, ok := mem.cache[namespace]; ok { - if rs, ok := mem.cache[namespace][rls.Name]; ok && rs.Exists(key) { - rs.Replace(key, newRecord(key, rls)) - return nil - } - } - return ErrReleaseNotFound -} - -// Delete deletes a release or returns ErrReleaseNotFound. -func (mem *Memory) Delete(key string) (*rspb.Release, error) { - defer unlock(mem.wlock()) - - keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") - elems := strings.Split(keyWithoutPrefix, ".v") - - if len(elems) != 2 { - return nil, ErrInvalidKey - } - - name, ver := elems[0], elems[1] - if _, err := strconv.Atoi(ver); err != nil { - return nil, ErrInvalidKey - } - if _, ok := mem.cache[mem.namespace]; ok { - if recs, ok := mem.cache[mem.namespace][name]; ok { - if r := recs.Remove(key); r != nil { - // recs.Remove changes the slice reference, so we have to re-assign it. - mem.cache[mem.namespace][name] = recs - return r.rls, nil - } - } - } - return nil, ErrReleaseNotFound -} - -// wlock locks mem for writing -func (mem *Memory) wlock() func() { - mem.Lock() - return func() { mem.Unlock() } -} - -// rlock locks mem for reading -func (mem *Memory) rlock() func() { - mem.RLock() - return func() { mem.RUnlock() } -} - -// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g: -// ```defer unlock(mem.rlock())```, locks mem for reading at the -// call point of defer and unlocks upon exiting the block. -func unlock(fn func()) { fn() } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go deleted file mode 100644 index 7a2e8578e..000000000 --- a/pkg/storage/driver/memory_test.go +++ /dev/null @@ -1,289 +0,0 @@ -/* -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 driver - -import ( - "fmt" - "reflect" - "testing" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestMemoryName(t *testing.T) { - if mem := NewMemory(); mem.Name() != MemoryDriverName { - t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name()) - } -} - -func TestMemoryCreate(t *testing.T) { - var tests = []struct { - desc string - rls *rspb.Release - err bool - }{ - { - "create should succeed", - releaseStub("rls-c", 1, "default", rspb.StatusDeployed), - false, - }, - { - "create should fail (release already exists)", - releaseStub("rls-a", 1, "default", rspb.StatusDeployed), - true, - }, - { - "create in namespace should succeed", - releaseStub("rls-a", 1, "mynamespace", rspb.StatusDeployed), - false, - }, - { - "create in other namespace should fail (release already exists)", - releaseStub("rls-c", 1, "mynamespace", rspb.StatusDeployed), - true, - }, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - key := testKey(tt.rls.Name, tt.rls.Version) - rls := tt.rls - - if err := ts.Create(key, rls); err != nil { - if !tt.err { - t.Fatalf("failed to create %q: %s", tt.desc, err) - } - } else if tt.err { - t.Fatalf("Did not get expected error for %q\n", tt.desc) - } - } -} - -func TestMemoryGet(t *testing.T) { - var tests = []struct { - desc string - key string - namespace string - err bool - }{ - {"release key should exist", "rls-a.v1", "default", false}, - {"release key should not exist", "rls-a.v5", "default", true}, - {"release key in namespace should exist", "rls-c.v1", "mynamespace", false}, - {"release key in namespace should not exist", "rls-a.v1", "mynamespace", true}, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - ts.SetNamespace(tt.namespace) - if _, err := ts.Get(tt.key); err != nil { - if !tt.err { - t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) - } - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } - } -} - -func TestMemoryList(t *testing.T) { - ts := tsFixtureMemory(t) - ts.SetNamespace("default") - - // list all deployed releases - dpl, err := ts.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed releases: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } - - // list all superseded releases - ssd, err := ts.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded releases: %s", err) - } - if len(ssd) != 6 { - t.Errorf("Expected 6 superseded, got %d", len(ssd)) - } - - // list all deleted releases - del, err := ts.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted releases: %s", err) - } - if len(del) != 0 { - t.Errorf("Expected 0 deleted, got %d", len(del)) - } -} - -func TestMemoryQuery(t *testing.T) { - var tests = []struct { - desc string - xlen int - namespace string - lbs map[string]string - }{ - { - "should be 2 query results", - 2, - "default", - map[string]string{"status": "deployed"}, - }, - { - "should be 1 query result", - 1, - "mynamespace", - map[string]string{"status": "deployed"}, - }, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - ts.SetNamespace(tt.namespace) - l, err := ts.Query(tt.lbs) - if err != nil { - t.Fatalf("Failed to query: %s\n", err) - } - - if tt.xlen != len(l) { - t.Fatalf("Expected %d results, actual %d\n", tt.xlen, len(l)) - } - } -} - -func TestMemoryUpdate(t *testing.T) { - var tests = []struct { - desc string - key string - rls *rspb.Release - err bool - }{ - { - "update release status", - "rls-a.v4", - releaseStub("rls-a", 4, "default", rspb.StatusSuperseded), - false, - }, - { - "update release does not exist", - "rls-c.v1", - releaseStub("rls-c", 1, "default", rspb.StatusUninstalled), - true, - }, - { - "update release status in namespace", - "rls-c.v4", - releaseStub("rls-c", 4, "mynamespace", rspb.StatusSuperseded), - false, - }, - { - "update release in namespace does not exist", - "rls-a.v1", - releaseStub("rls-a", 1, "mynamespace", rspb.StatusUninstalled), - true, - }, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - if err := ts.Update(tt.key, tt.rls); err != nil { - if !tt.err { - t.Fatalf("Failed %q: %s\n", tt.desc, err) - } - continue - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } - - ts.SetNamespace(tt.rls.Namespace) - r, err := ts.Get(tt.key) - if err != nil { - t.Fatalf("Failed to get: %s\n", err) - } - - if !reflect.DeepEqual(r, tt.rls) { - t.Fatalf("Expected %v, actual %v\n", tt.rls, r) - } - } -} - -func TestMemoryDelete(t *testing.T) { - var tests = []struct { - desc string - key string - namespace string - err bool - }{ - {"release key should exist", "rls-a.v4", "default", false}, - {"release key should not exist", "rls-a.v5", "default", true}, - {"release key from other namespace should not exist", "rls-c.v4", "default", true}, - {"release key from namespace should exist", "rls-c.v4", "mynamespace", false}, - {"release key from namespace should not exist", "rls-c.v5", "mynamespace", true}, - {"release key from namespace2 should not exist", "rls-a.v4", "mynamespace", true}, - } - - ts := tsFixtureMemory(t) - ts.SetNamespace("") - start, err := ts.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Query failed: %s", err) - } - startLen := len(start) - for _, tt := range tests { - ts.SetNamespace(tt.namespace) - if rel, err := ts.Delete(tt.key); err != nil { - if !tt.err { - t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) - } - continue - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } else if fmt.Sprintf("%s.v%d", rel.Name, rel.Version) != tt.key { - t.Fatalf("Asked for delete on %s, but deleted %d", tt.key, rel.Version) - } - _, err := ts.Get(tt.key) - if err == nil { - t.Errorf("Expected an error when asking for a deleted key") - } - } - - // Make sure that the deleted records are gone. - ts.SetNamespace("") - end, err := ts.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Query failed: %s", err) - } - endLen := len(end) - - if startLen-2 != endLen { - t.Errorf("expected end to be %d instead of %d", startLen-2, endLen) - for _, ee := range end { - t.Logf("Name: %s, Version: %d", ee.Name, ee.Version) - } - } - -} diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go deleted file mode 100644 index c0236ece8..000000000 --- a/pkg/storage/driver/mock_test.go +++ /dev/null @@ -1,265 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "context" - "fmt" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - sq "github.com/Masterminds/squirrel" - "github.com/jmoiron/sqlx" - - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kblabels "k8s.io/apimachinery/pkg/labels" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func releaseStub(name string, vers int, namespace string, status rspb.Status) *rspb.Release { - return &rspb.Release{ - Name: name, - Version: vers, - Namespace: namespace, - Info: &rspb.Info{Status: status}, - } -} - -func testKey(name string, vers int) string { - return fmt.Sprintf("%s.v%d", name, vers) -} - -func tsFixtureMemory(t *testing.T) *Memory { - hs := []*rspb.Release{ - // rls-a - releaseStub("rls-a", 4, "default", rspb.StatusDeployed), - releaseStub("rls-a", 1, "default", rspb.StatusSuperseded), - releaseStub("rls-a", 3, "default", rspb.StatusSuperseded), - releaseStub("rls-a", 2, "default", rspb.StatusSuperseded), - // rls-b - releaseStub("rls-b", 4, "default", rspb.StatusDeployed), - releaseStub("rls-b", 1, "default", rspb.StatusSuperseded), - releaseStub("rls-b", 3, "default", rspb.StatusSuperseded), - releaseStub("rls-b", 2, "default", rspb.StatusSuperseded), - // rls-c in other namespace - releaseStub("rls-c", 4, "mynamespace", rspb.StatusDeployed), - releaseStub("rls-c", 1, "mynamespace", rspb.StatusSuperseded), - releaseStub("rls-c", 3, "mynamespace", rspb.StatusSuperseded), - releaseStub("rls-c", 2, "mynamespace", rspb.StatusSuperseded), - } - - mem := NewMemory() - for _, tt := range hs { - err := mem.Create(testKey(tt.Name, tt.Version), tt) - if err != nil { - t.Fatalf("Test setup failed to create: %s\n", err) - } - } - return mem -} - -// newTestFixture initializes a MockConfigMapsInterface. -// ConfigMaps are created for each release provided. -func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps { - var mock MockConfigMapsInterface - mock.Init(t, releases...) - - return NewConfigMaps(&mock) -} - -// MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface -type MockConfigMapsInterface struct { - corev1.ConfigMapInterface - - objects map[string]*v1.ConfigMap -} - -// Init initializes the MockConfigMapsInterface with the set of releases. -func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) { - mock.objects = map[string]*v1.ConfigMap{} - - for _, rls := range releases { - objkey := testKey(rls.Name, rls.Version) - - cfgmap, err := newConfigMapsObject(objkey, rls, nil) - if err != nil { - t.Fatalf("Failed to create configmap: %s", err) - } - mock.objects[objkey] = cfgmap - } -} - -// Get returns the ConfigMap by name. -func (mock *MockConfigMapsInterface) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.ConfigMap, error) { - object, ok := mock.objects[name] - if !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - return object, nil -} - -// List returns the a of ConfigMaps. -func (mock *MockConfigMapsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { - var list v1.ConfigMapList - - labelSelector, err := kblabels.Parse(opts.LabelSelector) - if err != nil { - return nil, err - } - - for _, cfgmap := range mock.objects { - if labelSelector.Matches(kblabels.Set(cfgmap.ObjectMeta.Labels)) { - list.Items = append(list.Items, *cfgmap) - } - } - return &list, nil -} - -// Create creates a new ConfigMap. -func (mock *MockConfigMapsInterface) Create(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.CreateOptions) (*v1.ConfigMap, error) { - name := cfgmap.ObjectMeta.Name - if object, ok := mock.objects[name]; ok { - return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) - } - mock.objects[name] = cfgmap - return cfgmap, nil -} - -// Update updates a ConfigMap. -func (mock *MockConfigMapsInterface) Update(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.UpdateOptions) (*v1.ConfigMap, error) { - name := cfgmap.ObjectMeta.Name - if _, ok := mock.objects[name]; !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - mock.objects[name] = cfgmap - return cfgmap, nil -} - -// Delete deletes a ConfigMap by name. -func (mock *MockConfigMapsInterface) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error { - if _, ok := mock.objects[name]; !ok { - return apierrors.NewNotFound(v1.Resource("tests"), name) - } - delete(mock.objects, name) - return nil -} - -// newTestFixture initializes a MockSecretsInterface. -// Secrets are created for each release provided. -func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets { - var mock MockSecretsInterface - mock.Init(t, releases...) - - return NewSecrets(&mock) -} - -// MockSecretsInterface mocks a kubernetes SecretsInterface -type MockSecretsInterface struct { - corev1.SecretInterface - - objects map[string]*v1.Secret -} - -// Init initializes the MockSecretsInterface with the set of releases. -func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) { - mock.objects = map[string]*v1.Secret{} - - for _, rls := range releases { - objkey := testKey(rls.Name, rls.Version) - - secret, err := newSecretsObject(objkey, rls, nil) - if err != nil { - t.Fatalf("Failed to create secret: %s", err) - } - mock.objects[objkey] = secret - } -} - -// Get returns the Secret by name. -func (mock *MockSecretsInterface) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.Secret, error) { - object, ok := mock.objects[name] - if !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - return object, nil -} - -// List returns the a of Secret. -func (mock *MockSecretsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { - var list v1.SecretList - - labelSelector, err := kblabels.Parse(opts.LabelSelector) - if err != nil { - return nil, err - } - - for _, secret := range mock.objects { - if labelSelector.Matches(kblabels.Set(secret.ObjectMeta.Labels)) { - list.Items = append(list.Items, *secret) - } - } - return &list, nil -} - -// Create creates a new Secret. -func (mock *MockSecretsInterface) Create(_ context.Context, secret *v1.Secret, _ metav1.CreateOptions) (*v1.Secret, error) { - name := secret.ObjectMeta.Name - if object, ok := mock.objects[name]; ok { - return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) - } - mock.objects[name] = secret - return secret, nil -} - -// Update updates a Secret. -func (mock *MockSecretsInterface) Update(_ context.Context, secret *v1.Secret, _ metav1.UpdateOptions) (*v1.Secret, error) { - name := secret.ObjectMeta.Name - if _, ok := mock.objects[name]; !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - mock.objects[name] = secret - return secret, nil -} - -// Delete deletes a Secret by name. -func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error { - if _, ok := mock.objects[name]; !ok { - return apierrors.NewNotFound(v1.Resource("tests"), name) - } - delete(mock.objects, name) - return nil -} - -// newTestFixtureSQL mocks the SQL database (for testing purposes) -func newTestFixtureSQL(t *testing.T, releases ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { - sqlDB, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("error when opening stub database connection: %v", err) - } - - sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") - return &SQL{ - db: sqlxDB, - Log: func(a string, b ...interface{}) {}, - namespace: "default", - statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), - }, mock -} diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go deleted file mode 100644 index 9df173384..000000000 --- a/pkg/storage/driver/records.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "sort" - "strconv" - - rspb "helm.sh/helm/v3/pkg/release" -) - -// records holds a list of in-memory release records -type records []*record - -func (rs records) Len() int { return len(rs) } -func (rs records) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } -func (rs records) Less(i, j int) bool { return rs[i].rls.Version < rs[j].rls.Version } - -func (rs *records) Add(r *record) error { - if r == nil { - return nil - } - - if rs.Exists(r.key) { - return ErrReleaseExists - } - - *rs = append(*rs, r) - sort.Sort(*rs) - - return nil -} - -func (rs records) Get(key string) *record { - if i, ok := rs.Index(key); ok { - return rs[i] - } - return nil -} - -func (rs *records) Iter(fn func(int, *record) bool) { - cp := make([]*record, len(*rs)) - copy(cp, *rs) - - for i, r := range cp { - if !fn(i, r) { - return - } - } -} - -func (rs *records) Index(key string) (int, bool) { - for i, r := range *rs { - if r.key == key { - return i, true - } - } - return -1, false -} - -func (rs records) Exists(key string) bool { - _, ok := rs.Index(key) - return ok -} - -func (rs *records) Remove(key string) (r *record) { - if i, ok := rs.Index(key); ok { - return rs.removeAt(i) - } - return nil -} - -func (rs *records) Replace(key string, rec *record) *record { - if i, ok := rs.Index(key); ok { - old := (*rs)[i] - (*rs)[i] = rec - return old - } - return nil -} - -func (rs *records) removeAt(index int) *record { - r := (*rs)[index] - (*rs)[index] = nil - copy((*rs)[index:], (*rs)[index+1:]) - *rs = (*rs)[:len(*rs)-1] - return r -} - -// record is the data structure used to cache releases -// for the in-memory storage driver -type record struct { - key string - lbs labels - rls *rspb.Release -} - -// newRecord creates a new in-memory release record -func newRecord(key string, rls *rspb.Release) *record { - var lbs labels - - lbs.init() - lbs.set("name", rls.Name) - lbs.set("owner", "helm") - lbs.set("status", rls.Info.Status.String()) - lbs.set("version", strconv.Itoa(rls.Version)) - - // return &record{key: key, lbs: lbs, rls: proto.Clone(rls).(*rspb.Release)} - return &record{key: key, lbs: lbs, rls: rls} -} diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go deleted file mode 100644 index 0a27839cc..000000000 --- a/pkg/storage/driver/records_test.go +++ /dev/null @@ -1,240 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "reflect" - "testing" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestRecordsAdd(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - ok bool - rec *record - }{ - { - "add valid key", - "rls-a.v3", - false, - newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)), - }, - { - "add already existing key", - "rls-a.v1", - true, - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusDeployed)), - }, - } - - for _, tt := range tests { - if err := rs.Add(tt.rec); err != nil { - if !tt.ok { - t.Fatalf("failed: %q: %s\n", tt.desc, err) - } - } - } -} - -func TestRecordsRemove(t *testing.T) { - var tests = []struct { - desc string - key string - ok bool - }{ - {"remove valid key", "rls-a.v1", false}, - {"remove invalid key", "rls-a.v", true}, - {"remove non-existent key", "rls-z.v1", true}, - } - - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - startLen := rs.Len() - - for _, tt := range tests { - if r := rs.Remove(tt.key); r == nil { - if !tt.ok { - t.Fatalf("Failed to %q (key = %s). Expected nil, got %v", - tt.desc, - tt.key, - r, - ) - } - } - } - - // We expect the total number of records will be less now than there were - // when we started. - endLen := rs.Len() - if endLen >= startLen { - t.Errorf("expected ending length %d to be less than starting length %d", endLen, startLen) - } -} - -func TestRecordsRemoveAt(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - if len(rs) != 2 { - t.Fatal("Expected len=2 for mock") - } - - rs.Remove("rls-a.v1") - if len(rs) != 1 { - t.Fatalf("Expected length of rs to be 1, got %d", len(rs)) - } -} - -func TestRecordsGet(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - rec *record - }{ - { - "get valid key", - "rls-a.v1", - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - }, - { - "get invalid key", - "rls-a.v3", - nil, - }, - } - - for _, tt := range tests { - got := rs.Get(tt.key) - if !reflect.DeepEqual(tt.rec, got) { - t.Fatalf("Expected %v, got %v", tt.rec, got) - } - } -} - -func TestRecordsIndex(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - sort int - }{ - { - "get valid key", - "rls-a.v1", - 0, - }, - { - "get invalid key", - "rls-a.v3", - -1, - }, - } - - for _, tt := range tests { - got, _ := rs.Index(tt.key) - if got != tt.sort { - t.Fatalf("Expected %d, got %d", tt.sort, got) - } - } -} - -func TestRecordsExists(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - ok bool - }{ - { - "get valid key", - "rls-a.v1", - true, - }, - { - "get invalid key", - "rls-a.v3", - false, - }, - } - - for _, tt := range tests { - got := rs.Exists(tt.key) - if got != tt.ok { - t.Fatalf("Expected %t, got %t", tt.ok, got) - } - } -} - -func TestRecordsReplace(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - rec *record - expected *record - }{ - { - "replace with existing key", - "rls-a.v2", - newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }, - { - "replace with non existing key", - "rls-a.v4", - newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", rspb.StatusDeployed)), - nil, - }, - } - - for _, tt := range tests { - got := rs.Replace(tt.key, tt.rec) - if !reflect.DeepEqual(tt.expected, got) { - t.Fatalf("Expected %v, got %v", tt.expected, got) - } - } -} diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go deleted file mode 100644 index 2e8530d0c..000000000 --- a/pkg/storage/driver/secrets.go +++ /dev/null @@ -1,250 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "context" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kblabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/validation" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*Secrets)(nil) - -// SecretsDriverName is the string name of the driver. -const SecretsDriverName = "Secret" - -// Secrets is a wrapper around an implementation of a kubernetes -// SecretsInterface. -type Secrets struct { - impl corev1.SecretInterface - Log func(string, ...interface{}) -} - -// NewSecrets initializes a new Secrets wrapping an implementation of -// the kubernetes SecretsInterface. -func NewSecrets(impl corev1.SecretInterface) *Secrets { - return &Secrets{ - impl: impl, - Log: func(_ string, _ ...interface{}) {}, - } -} - -// Name returns the name of the driver. -func (secrets *Secrets) Name() string { - return SecretsDriverName -} - -// Get fetches the release named by key. The corresponding release is returned -// or error if not found. -func (secrets *Secrets) Get(key string) (*rspb.Release, error) { - // fetch the secret holding the release named by key - obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - return nil, ErrReleaseNotFound - } - return nil, errors.Wrapf(err, "get: failed to get %q", key) - } - // found the secret, decode the base64 data string - r, err := decodeRelease(string(obj.Data["release"])) - return r, errors.Wrapf(err, "get: failed to decode data %q", key) -} - -// List fetches all releases and returns the list releases such -// that filter(release) == true. An error is returned if the -// secret fails to retrieve the releases. -func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - lsel := kblabels.Set{"owner": "helm"}.AsSelector() - opts := metav1.ListOptions{LabelSelector: lsel.String()} - - list, err := secrets.impl.List(context.Background(), opts) - if err != nil { - return nil, errors.Wrap(err, "list: failed to list") - } - - var results []*rspb.Release - - // iterate over the secrets object list - // and decode each release - for _, item := range list.Items { - rls, err := decodeRelease(string(item.Data["release"])) - if err != nil { - secrets.Log("list: failed to decode release: %v: %s", item, err) - continue - } - - rls.Labels = item.ObjectMeta.Labels - - if filter(rls) { - results = append(results, rls) - } - } - return results, nil -} - -// Query fetches all releases that match the provided map of labels. -// An error is returned if the secret fails to retrieve the releases. -func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) { - ls := kblabels.Set{} - for k, v := range labels { - if errs := validation.IsValidLabelValue(v); len(errs) != 0 { - return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) - } - ls[k] = v - } - - opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} - - list, err := secrets.impl.List(context.Background(), opts) - if err != nil { - return nil, errors.Wrap(err, "query: failed to query with labels") - } - - if len(list.Items) == 0 { - return nil, ErrReleaseNotFound - } - - var results []*rspb.Release - for _, item := range list.Items { - rls, err := decodeRelease(string(item.Data["release"])) - if err != nil { - secrets.Log("query: failed to decode release: %s", err) - continue - } - results = append(results, rls) - } - return results, nil -} - -// Create creates a new Secret holding the release. If the -// Secret already exists, ErrReleaseExists is returned. -func (secrets *Secrets) Create(key string, rls *rspb.Release) error { - // set labels for secrets object meta data - var lbs labels - - lbs.init() - lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new secret to hold the release - obj, err := newSecretsObject(key, rls, lbs) - if err != nil { - return errors.Wrapf(err, "create: failed to encode release %q", rls.Name) - } - // push the secret object out into the kubiverse - if _, err := secrets.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { - if apierrors.IsAlreadyExists(err) { - return ErrReleaseExists - } - - return errors.Wrap(err, "create: failed to create") - } - return nil -} - -// Update updates the Secret holding the release. If not found -// the Secret is created to hold the release. -func (secrets *Secrets) Update(key string, rls *rspb.Release) error { - // set labels for secrets object meta data - var lbs labels - - lbs.init() - lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new secret object to hold the release - obj, err := newSecretsObject(key, rls, lbs) - if err != nil { - return errors.Wrapf(err, "update: failed to encode release %q", rls.Name) - } - // push the secret object out into the kubiverse - _, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) - return errors.Wrap(err, "update: failed to update") -} - -// Delete deletes the Secret holding the release named by key. -func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) { - // fetch the release to check existence - if rls, err = secrets.Get(key); err != nil { - return nil, err - } - // delete the release - err = secrets.impl.Delete(context.Background(), key, metav1.DeleteOptions{}) - return rls, err -} - -// newSecretsObject constructs a kubernetes Secret object -// to store a release. Each secret data entry is the base64 -// encoded gzipped string of a release. -// -// The following labels are used within each secret: -// -// "modifiedAt" - timestamp indicating when this secret was last modified. (set in Update) -// "createdAt" - timestamp indicating when this secret was created. (set in Create) -// "version" - version of the release. -// "status" - status of the release (see pkg/release/status.go for variants) -// "owner" - owner of the secret, currently "helm". -// "name" - name of the release. -// -func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, error) { - const owner = "helm" - - // encode the release - s, err := encodeRelease(rls) - if err != nil { - return nil, err - } - - if lbs == nil { - lbs.init() - } - - // apply labels - lbs.set("name", rls.Name) - lbs.set("owner", owner) - lbs.set("status", rls.Info.Status.String()) - lbs.set("version", strconv.Itoa(rls.Version)) - - // create and return secret object. - // Helm 3 introduced setting the 'Type' field - // in the Kubernetes storage object. - // Helm defines the field content as follows: - // /.v - // Type field for Helm 3: helm.sh/release.v1 - // Note: Version starts at 'v1' for Helm 3 and - // should be incremented if the release object - // metadata is modified. - // This would potentially be a breaking change - // and should only happen between major versions. - return &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: key, - Labels: lbs.toMap(), - }, - Type: "helm.sh/release.v1", - Data: map[string][]byte{"release": []byte(s)}, - }, nil -} diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go deleted file mode 100644 index d509c7b3a..000000000 --- a/pkg/storage/driver/secrets_test.go +++ /dev/null @@ -1,241 +0,0 @@ -/* -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 driver - -import ( - "encoding/base64" - "encoding/json" - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestSecretName(t *testing.T) { - c := newTestFixtureSecrets(t) - if c.Name() != SecretsDriverName { - t.Errorf("Expected name to be %q, got %q", SecretsDriverName, c.Name()) - } -} - -func TestSecretGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) - - // get release with key - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestUNcompressedSecretGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // Create a test fixture which contains an uncompressed release - secret, err := newSecretsObject(key, rel, nil) - if err != nil { - t.Fatalf("Failed to create secret: %s", err) - } - b, err := json.Marshal(rel) - if err != nil { - t.Fatalf("Failed to marshal release: %s", err) - } - secret.Data["release"] = []byte(base64.StdEncoding.EncodeToString(b)) - var mock MockSecretsInterface - mock.objects = map[string]*v1.Secret{key: secret} - secrets := NewSecrets(&mock) - - // get release with key - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestSecretList(t *testing.T) { - secrets := newTestFixtureSecrets(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - // list all deleted releases - del, err := secrets.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted: %s", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } - - // list all deployed releases - dpl, err := secrets.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } - - // list all superseded releases - ssd, err := secrets.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded: %s", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d", len(ssd)) - } -} - -func TestSecretQuery(t *testing.T) { - secrets := newTestFixtureSecrets(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - rls, err := secrets.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Fatalf("Failed to query: %s", err) - } - if len(rls) != 2 { - t.Fatalf("Expected 2 results, actual %d", len(rls)) - } - - _, err = secrets.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} - -func TestSecretCreate(t *testing.T) { - secrets := newTestFixtureSecrets(t) - - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // store the release in a secret - if err := secrets.Create(key, rel); err != nil { - t.Fatalf("Failed to create release with key %q: %s", key, err) - } - - // get the release back - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // compare created release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestSecretUpdate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) - - // modify release status code - rel.Info.Status = rspb.StatusSuperseded - - // perform the update - if err := secrets.Update(key, rel); err != nil { - t.Fatalf("Failed to update release: %s", err) - } - - // fetch the updated release - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // check release has actually been updated by comparing modified fields - if rel.Info.Status != got.Info.Status { - t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) - } -} - -func TestSecretDelete(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) - - // perform the delete on a non-existing release - _, err := secrets.Delete("nonexistent") - if err != ErrReleaseNotFound { - t.Fatalf("Expected ErrReleaseNotFound, got: {%v}", err) - } - - // perform the delete - rls, err := secrets.Delete(key) - if err != nil { - t.Fatalf("Failed to delete release with key %q: %s", key, err) - } - if !reflect.DeepEqual(rel, rls) { - t.Errorf("Expected {%v}, got {%v}", rel, rls) - } - - // fetch the deleted release - _, err = secrets.Get(key) - if !reflect.DeepEqual(ErrReleaseNotFound, err) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go deleted file mode 100644 index c8a6ae04f..000000000 --- a/pkg/storage/driver/sql.go +++ /dev/null @@ -1,496 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "fmt" - "sort" - "time" - - "github.com/jmoiron/sqlx" - migrate "github.com/rubenv/sql-migrate" - - sq "github.com/Masterminds/squirrel" - - // Import pq for postgres dialect - _ "github.com/lib/pq" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*SQL)(nil) - -var labelMap = map[string]struct{}{ - "modifiedAt": {}, - "createdAt": {}, - "version": {}, - "status": {}, - "owner": {}, - "name": {}, -} - -const postgreSQLDialect = "postgres" - -// SQLDriverName is the string name of this driver. -const SQLDriverName = "SQL" - -const sqlReleaseTableName = "releases_v1" - -const ( - sqlReleaseTableKeyColumn = "key" - sqlReleaseTableTypeColumn = "type" - sqlReleaseTableBodyColumn = "body" - sqlReleaseTableNameColumn = "name" - sqlReleaseTableNamespaceColumn = "namespace" - sqlReleaseTableVersionColumn = "version" - sqlReleaseTableStatusColumn = "status" - sqlReleaseTableOwnerColumn = "owner" - sqlReleaseTableCreatedAtColumn = "createdAt" - sqlReleaseTableModifiedAtColumn = "modifiedAt" -) - -const ( - sqlReleaseDefaultOwner = "helm" - sqlReleaseDefaultType = "helm.sh/release.v1" -) - -// SQL is the sql storage driver implementation. -type SQL struct { - db *sqlx.DB - namespace string - statementBuilder sq.StatementBuilderType - - Log func(string, ...interface{}) -} - -// Name returns the name of the driver. -func (s *SQL) Name() string { - return SQLDriverName -} - -func (s *SQL) ensureDBSetup() error { - // Populate the database with the relations we need if they don't exist yet - migrations := &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "init", - Up: []string{ - fmt.Sprintf(` - CREATE TABLE %s ( - %s VARCHAR(67), - %s VARCHAR(64) NOT NULL, - %s TEXT NOT NULL, - %s VARCHAR(64) NOT NULL, - %s VARCHAR(64) NOT NULL, - %s INTEGER NOT NULL, - %s TEXT NOT NULL, - %s TEXT NOT NULL, - %s INTEGER NOT NULL, - %s INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY(%s, %s) - ); - CREATE INDEX ON %s (%s, %s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - - GRANT ALL ON %s TO PUBLIC; - - ALTER TABLE %s ENABLE ROW LEVEL SECURITY; - `, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - sqlReleaseTableModifiedAtColumn, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableName, - sqlReleaseTableVersionColumn, - sqlReleaseTableName, - sqlReleaseTableStatusColumn, - sqlReleaseTableName, - sqlReleaseTableOwnerColumn, - sqlReleaseTableName, - sqlReleaseTableCreatedAtColumn, - sqlReleaseTableName, - sqlReleaseTableModifiedAtColumn, - sqlReleaseTableName, - sqlReleaseTableName, - ), - }, - Down: []string{ - fmt.Sprintf(` - DROP TABLE %s; - `, sqlReleaseTableName), - }, - }, - }, - } - - _, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up) - return err -} - -// SQLReleaseWrapper describes how Helm releases are stored in an SQL database -type SQLReleaseWrapper struct { - // The primary key, made of {release-name}.{release-version} - Key string `db:"key"` - - // See https://github.com/helm/helm/blob/c9fe3d118caec699eb2565df9838673af379ce12/pkg/storage/driver/secrets.go#L231 - Type string `db:"type"` - - // The rspb.Release body, as a base64-encoded string - Body string `db:"body"` - - // Release "labels" that can be used as filters in the storage.Query(labels map[string]string) - // we implemented. Note that allowing Helm users to filter against new dimensions will require a - // new migration to be added, and the Create and/or update functions to be updated accordingly. - Name string `db:"name"` - Namespace string `db:"namespace"` - Version int `db:"version"` - Status string `db:"status"` - Owner string `db:"owner"` - CreatedAt int `db:"createdAt"` - ModifiedAt int `db:"modifiedAt"` -} - -// NewSQL initializes a new sql driver. -func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { - db, err := sqlx.Connect(postgreSQLDialect, connectionString) - if err != nil { - return nil, err - } - - driver := &SQL{ - db: db, - Log: logger, - statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), - } - - if err := driver.ensureDBSetup(); err != nil { - return nil, err - } - - driver.namespace = namespace - - return driver, nil -} - -// Get returns the release named by key. -func (s *SQL) Get(key string) (*rspb.Release, error) { - var record SQLReleaseWrapper - - qb := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) - - query, args, err := qb.ToSql() - if err != nil { - s.Log("failed to build query: %v", err) - return nil, err - } - - // Get will return an error if the result is empty - if err := s.db.Get(&record, query, args...); err != nil { - s.Log("got SQL error when getting release %s: %v", key, err) - return nil, ErrReleaseNotFound - } - - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("get: failed to decode data %q: %v", key, err) - return nil, err - } - - return release, nil -} - -// List returns the list of all releases such that filter(release) == true -func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - sb := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner}) - - // If a namespace was specified, we only list releases from that namespace - if s.namespace != "" { - sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) - } - - query, args, err := sb.ToSql() - if err != nil { - s.Log("failed to build query: %v", err) - return nil, err - } - - var records = []SQLReleaseWrapper{} - if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to list: %v", err) - return nil, err - } - - var releases []*rspb.Release - for _, record := range records { - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) - continue - } - if filter(release) { - releases = append(releases, release) - } - } - - return releases, nil -} - -// Query returns the set of releases that match the provided set of labels. -func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { - sb := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName) - - keys := make([]string, 0, len(labels)) - for key := range labels { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { - if _, ok := labelMap[key]; ok { - sb = sb.Where(sq.Eq{key: labels[key]}) - } else { - s.Log("unknown label %s", key) - return nil, fmt.Errorf("unknown label %s", key) - } - } - - // If a namespace was specified, we only list releases from that namespace - if s.namespace != "" { - sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) - } - - // Build our query - query, args, err := sb.ToSql() - if err != nil { - s.Log("failed to build query: %v", err) - return nil, err - } - - var records = []SQLReleaseWrapper{} - if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to query with labels: %v", err) - return nil, err - } - - if len(records) == 0 { - return nil, ErrReleaseNotFound - } - - var releases []*rspb.Release - for _, record := range records { - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) - continue - } - releases = append(releases, release) - } - - if len(releases) == 0 { - return nil, ErrReleaseNotFound - } - - return releases, nil -} - -// Create creates a new release. -func (s *SQL) Create(key string, rls *rspb.Release) error { - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - s.namespace = namespace - - body, err := encodeRelease(rls) - if err != nil { - s.Log("failed to encode release: %v", err) - return err - } - - transaction, err := s.db.Beginx() - if err != nil { - s.Log("failed to start SQL transaction: %v", err) - return fmt.Errorf("error beginning transaction: %v", err) - } - - insertQuery, args, err := s.statementBuilder. - Insert(sqlReleaseTableName). - Columns( - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - ). - Values( - key, - sqlReleaseDefaultType, - body, - rls.Name, - namespace, - int(rls.Version), - rls.Info.Status.String(), - sqlReleaseDefaultOwner, - int(time.Now().Unix()), - ).ToSql() - if err != nil { - s.Log("failed to build insert query: %v", err) - return err - } - - if _, err := transaction.Exec(insertQuery, args...); err != nil { - defer transaction.Rollback() - - selectQuery, args, buildErr := s.statementBuilder. - Select(sqlReleaseTableKeyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). - ToSql() - if buildErr != nil { - s.Log("failed to build select query: %v", buildErr) - return err - } - - var record SQLReleaseWrapper - if err := transaction.Get(&record, selectQuery, args...); err == nil { - s.Log("release %s already exists", key) - return ErrReleaseExists - } - - s.Log("failed to store release %s in SQL database: %v", key, err) - return err - } - defer transaction.Commit() - - return nil -} - -// Update updates a release. -func (s *SQL) Update(key string, rls *rspb.Release) error { - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - s.namespace = namespace - - body, err := encodeRelease(rls) - if err != nil { - s.Log("failed to encode release: %v", err) - return err - } - - query, args, err := s.statementBuilder. - Update(sqlReleaseTableName). - Set(sqlReleaseTableBodyColumn, body). - Set(sqlReleaseTableNameColumn, rls.Name). - Set(sqlReleaseTableVersionColumn, int(rls.Version)). - Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()). - Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner). - Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). - ToSql() - - if err != nil { - s.Log("failed to build update query: %v", err) - return err - } - - if _, err := s.db.Exec(query, args...); err != nil { - s.Log("failed to update release %s in SQL database: %v", key, err) - return err - } - - return nil -} - -// Delete deletes a release or returns ErrReleaseNotFound. -func (s *SQL) Delete(key string) (*rspb.Release, error) { - transaction, err := s.db.Beginx() - if err != nil { - s.Log("failed to start SQL transaction: %v", err) - return nil, fmt.Errorf("error beginning transaction: %v", err) - } - - selectQuery, args, err := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). - ToSql() - if err != nil { - s.Log("failed to build select query: %v", err) - return nil, err - } - - var record SQLReleaseWrapper - err = transaction.Get(&record, selectQuery, args...) - if err != nil { - s.Log("release %s not found: %v", key, err) - return nil, ErrReleaseNotFound - } - - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("failed to decode release %s: %v", key, err) - transaction.Rollback() - return nil, err - } - defer transaction.Commit() - - deleteQuery, args, err := s.statementBuilder. - Delete(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). - ToSql() - if err != nil { - s.Log("failed to build select query: %v", err) - return nil, err - } - - _, err = transaction.Exec(deleteQuery, args...) - return release, err -} diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go deleted file mode 100644 index 87b6315b8..000000000 --- a/pkg/storage/driver/sql_test.go +++ /dev/null @@ -1,463 +0,0 @@ -/* -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 driver - -import ( - "fmt" - "reflect" - "regexp" - "testing" - "time" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestSQLName(t *testing.T) { - sqlDriver, _ := newTestFixtureSQL(t) - if sqlDriver.Name() != SQLDriverName { - t.Errorf("Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name()) - } -} - -func TestSQLGet(t *testing.T) { - vers := int(1) - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - body, _ := encodeRelease(rel) - - sqlDriver, mock := newTestFixtureSQL(t) - - query := fmt.Sprintf( - regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(query). - WithArgs(key, namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - body, - ), - ).RowsWillBeClosed() - - got, err := sqlDriver.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %v", err) - } - - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected release {%v}, got {%v}", rel, got) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSQLList(t *testing.T) { - body1, _ := encodeRelease(releaseStub("key-1", 1, "default", rspb.StatusUninstalled)) - body2, _ := encodeRelease(releaseStub("key-2", 1, "default", rspb.StatusUninstalled)) - body3, _ := encodeRelease(releaseStub("key-3", 1, "default", rspb.StatusDeployed)) - body4, _ := encodeRelease(releaseStub("key-4", 1, "default", rspb.StatusDeployed)) - body5, _ := encodeRelease(releaseStub("key-5", 1, "default", rspb.StatusSuperseded)) - body6, _ := encodeRelease(releaseStub("key-6", 1, "default", rspb.StatusSuperseded)) - - sqlDriver, mock := newTestFixtureSQL(t) - - for i := 0; i < 3; i++ { - query := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableOwnerColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }). - AddRow(body1). - AddRow(body2). - AddRow(body3). - AddRow(body4). - AddRow(body5). - AddRow(body6), - ).RowsWillBeClosed() - } - - // list all deleted releases - del, err := sqlDriver.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted: %v", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } - - // list all deployed releases - dpl, err := sqlDriver.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed: %v", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl) - } - - // list all superseded releases - ssd, err := sqlDriver.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded: %v", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlCreate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - sqlDriver, mock := newTestFixtureSQL(t) - body, _ := encodeRelease(rel) - - query := fmt.Sprintf( - "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - ) - - mock.ExpectBegin() - mock. - ExpectExec(regexp.QuoteMeta(query)). - WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - - if err := sqlDriver.Create(key, rel); err != nil { - t.Fatalf("failed to create release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlCreateAlreadyExists(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - sqlDriver, mock := newTestFixtureSQL(t) - body, _ := encodeRelease(rel) - - insertQuery := fmt.Sprintf( - "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - ) - - // Insert fails (primary key already exists) - mock.ExpectBegin() - mock. - ExpectExec(regexp.QuoteMeta(insertQuery)). - WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). - WillReturnError(fmt.Errorf("dialect dependent SQL error")) - - selectQuery := fmt.Sprintf( - regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), - sqlReleaseTableKeyColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - // Let's check that we do make sure the error is due to a release already existing - mock. - ExpectQuery(selectQuery). - WithArgs(key, namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableKeyColumn, - }).AddRow( - key, - ), - ).RowsWillBeClosed() - mock.ExpectRollback() - - if err := sqlDriver.Create(key, rel); err == nil { - t.Fatalf("failed to create release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlUpdate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - sqlDriver, mock := newTestFixtureSQL(t) - body, _ := encodeRelease(rel) - - query := fmt.Sprintf( - "UPDATE %s SET %s = $1, %s = $2, %s = $3, %s = $4, %s = $5, %s = $6 WHERE %s = $7 AND %s = $8", - sqlReleaseTableName, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableModifiedAtColumn, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectExec(regexp.QuoteMeta(query)). - WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix()), key, namespace). - WillReturnResult(sqlmock.NewResult(0, 1)) - - if err := sqlDriver.Update(key, rel); err != nil { - t.Fatalf("failed to update release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlQuery(t *testing.T) { - // Reflect actual use cases in ../storage.go - labelSetUnknown := map[string]string{ - "name": "smug-pigeon", - "owner": sqlReleaseDefaultOwner, - "status": "unknown", - } - labelSetDeployed := map[string]string{ - "name": "smug-pigeon", - "owner": sqlReleaseDefaultOwner, - "status": "deployed", - } - labelSetAll := map[string]string{ - "name": "smug-pigeon", - "owner": sqlReleaseDefaultOwner, - } - - supersededRelease := releaseStub("smug-pigeon", 1, "default", rspb.StatusSuperseded) - supersededReleaseBody, _ := encodeRelease(supersededRelease) - deployedRelease := releaseStub("smug-pigeon", 2, "default", rspb.StatusDeployed) - deployedReleaseBody, _ := encodeRelease(deployedRelease) - - // Let's actually start our test - sqlDriver, mock := newTestFixtureSQL(t) - - query := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableNameColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "unknown", "default"). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }), - ).RowsWillBeClosed() - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default"). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - deployedReleaseBody, - ), - ).RowsWillBeClosed() - - query = fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableNameColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "default"). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - supersededReleaseBody, - ).AddRow( - deployedReleaseBody, - ), - ).RowsWillBeClosed() - - _, err := sqlDriver.Query(labelSetUnknown) - if err == nil { - t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound) - } else if err != ErrReleaseNotFound { - t.Fatalf("failed to query for unknown smug-pigeon release: %v", err) - } - - results, err := sqlDriver.Query(labelSetDeployed) - if err != nil { - t.Fatalf("failed to query for deployed smug-pigeon release: %v", err) - } - - for _, res := range results { - if !reflect.DeepEqual(res, deployedRelease) { - t.Errorf("Expected release {%v}, got {%v}", deployedRelease, res) - } - } - - results, err = sqlDriver.Query(labelSetAll) - if err != nil { - t.Fatalf("failed to query release history for smug-pigeon: %v", err) - } - - if len(results) != 2 { - t.Errorf("expected a resultset of size 2, got %d", len(results)) - } - - for _, res := range results { - if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) { - t.Errorf("Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res) - } - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlDelete(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - body, _ := encodeRelease(rel) - - sqlDriver, mock := newTestFixtureSQL(t) - - selectQuery := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock.ExpectBegin() - mock. - ExpectQuery(regexp.QuoteMeta(selectQuery)). - WithArgs(key, namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - body, - ), - ).RowsWillBeClosed() - - deleteQuery := fmt.Sprintf( - "DELETE FROM %s WHERE %s = $1 AND %s = $2", - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectExec(regexp.QuoteMeta(deleteQuery)). - WithArgs(key, namespace). - WillReturnResult(sqlmock.NewResult(0, 1)) - mock.ExpectCommit() - - deletedRelease, err := sqlDriver.Delete(key) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } - if err != nil { - t.Fatalf("failed to delete release with key %q: %v", key, err) - } - - if !reflect.DeepEqual(rel, deletedRelease) { - t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease) - } -} diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go deleted file mode 100644 index b5908e508..000000000 --- a/pkg/storage/driver/util.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "encoding/json" - "io/ioutil" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var b64 = base64.StdEncoding - -var magicGzip = []byte{0x1f, 0x8b, 0x08} - -// encodeRelease encodes a release returning a base64 encoded -// gzipped string representation, or error. -func encodeRelease(rls *rspb.Release) (string, error) { - b, err := json.Marshal(rls) - if err != nil { - return "", err - } - var buf bytes.Buffer - w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) - if err != nil { - return "", err - } - if _, err = w.Write(b); err != nil { - return "", err - } - w.Close() - - return b64.EncodeToString(buf.Bytes()), nil -} - -// decodeRelease decodes the bytes of data into a release -// type. Data must contain a base64 encoded gzipped string of a -// valid release, otherwise an error is returned. -func decodeRelease(data string) (*rspb.Release, error) { - // base64 decode string - b, err := b64.DecodeString(data) - if err != nil { - return nil, err - } - - // For backwards compatibility with releases that were stored before - // compression was introduced we skip decompression if the - // gzip magic header is not found - if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) { - r, err := gzip.NewReader(bytes.NewReader(b)) - if err != nil { - return nil, err - } - defer r.Close() - b2, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - b = b2 - } - - var rls rspb.Release - // unmarshal release object bytes - if err := json.Unmarshal(b, &rls); err != nil { - return nil, err - } - return &rls, nil -} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go deleted file mode 100644 index 0a18b34a0..000000000 --- a/pkg/storage/storage.go +++ /dev/null @@ -1,266 +0,0 @@ -/* -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 storage // import "helm.sh/helm/v3/pkg/storage" - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - - rspb "helm.sh/helm/v3/pkg/release" - relutil "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" -) - -// HelmStorageType is the type field of the Kubernetes storage object which stores the Helm release -// version. It is modified slightly replacing the '/': sh.helm/release.v1 -// Note: The version 'v1' is incremented if the release object metadata is -// modified between major releases. -// This constant is used as a prefix for the Kubernetes storage object name. -const HelmStorageType = "sh.helm.release.v1" - -// Storage represents a storage engine for a Release. -type Storage struct { - driver.Driver - - // MaxHistory specifies the maximum number of historical releases that will - // be retained, including the most recent release. Values of 0 or less are - // ignored (meaning no limits are imposed). - MaxHistory int - - Log func(string, ...interface{}) -} - -// Get retrieves the release from storage. An error is returned -// if the storage driver failed to fetch the release, or the -// release identified by the key, version pair does not exist. -func (s *Storage) Get(name string, version int) (*rspb.Release, error) { - s.Log("getting release %q", makeKey(name, version)) - return s.Driver.Get(makeKey(name, version)) -} - -// Create creates a new storage entry holding the release. An -// error is returned if the storage driver fails to store the -// release, or a release with an identical key already exists. -func (s *Storage) Create(rls *rspb.Release) error { - s.Log("creating release %q", makeKey(rls.Name, rls.Version)) - if s.MaxHistory > 0 { - // Want to make space for one more release. - if err := s.removeLeastRecent(rls.Name, s.MaxHistory-1); err != nil && - !errors.Is(err, driver.ErrReleaseNotFound) { - return err - } - } - return s.Driver.Create(makeKey(rls.Name, rls.Version), rls) -} - -// Update updates the release in storage. An error is returned if the -// storage backend fails to update the release or if the release -// does not exist. -func (s *Storage) Update(rls *rspb.Release) error { - s.Log("updating release %q", makeKey(rls.Name, rls.Version)) - return s.Driver.Update(makeKey(rls.Name, rls.Version), rls) -} - -// Delete deletes the release from storage. An error is returned if -// the storage backend fails to delete the release or if the release -// does not exist. -func (s *Storage) Delete(name string, version int) (*rspb.Release, error) { - s.Log("deleting release %q", makeKey(name, version)) - return s.Driver.Delete(makeKey(name, version)) -} - -// ListReleases returns all releases from storage. An error is returned if the -// storage backend fails to retrieve the releases. -func (s *Storage) ListReleases() ([]*rspb.Release, error) { - s.Log("listing all releases in storage") - return s.Driver.List(func(_ *rspb.Release) bool { return true }) -} - -// ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned -// if the storage backend fails to retrieve the releases. -func (s *Storage) ListUninstalled() ([]*rspb.Release, error) { - s.Log("listing uninstalled releases in storage") - return s.Driver.List(func(rls *rspb.Release) bool { - return relutil.StatusFilter(rspb.StatusUninstalled).Check(rls) - }) -} - -// ListDeployed returns all releases with Status == DEPLOYED. An error is returned -// if the storage backend fails to retrieve the releases. -func (s *Storage) ListDeployed() ([]*rspb.Release, error) { - s.Log("listing all deployed releases in storage") - return s.Driver.List(func(rls *rspb.Release) bool { - return relutil.StatusFilter(rspb.StatusDeployed).Check(rls) - }) -} - -// Deployed returns the last deployed release with the provided release name, or -// returns ErrReleaseNotFound if not found. -func (s *Storage) Deployed(name string) (*rspb.Release, error) { - ls, err := s.DeployedAll(name) - if err != nil { - return nil, err - } - - if len(ls) == 0 { - return nil, driver.NewErrNoDeployedReleases(name) - } - - // If executed concurrently, Helm's database gets corrupted - // and multiple releases are DEPLOYED. Take the latest. - relutil.Reverse(ls, relutil.SortByRevision) - - return ls[0], nil -} - -// DeployedAll returns all deployed releases with the provided name, or -// returns ErrReleaseNotFound if not found. -func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { - s.Log("getting deployed releases from %q history", name) - - ls, err := s.Driver.Query(map[string]string{ - "name": name, - "owner": "helm", - "status": "deployed", - }) - if err == nil { - return ls, nil - } - if strings.Contains(err.Error(), "not found") { - return nil, driver.NewErrNoDeployedReleases(name) - } - return nil, err -} - -// History returns the revision history for the release with the provided name, or -// returns ErrReleaseNotFound if no such release name exists. -func (s *Storage) History(name string) ([]*rspb.Release, error) { - s.Log("getting release history for %q", name) - - return s.Driver.Query(map[string]string{"name": name, "owner": "helm"}) -} - -// removeLeastRecent removes items from history until the length number of releases -// does not exceed max. -// -// We allow max to be set explicitly so that calling functions can "make space" -// for the new records they are going to write. -func (s *Storage) removeLeastRecent(name string, max int) error { - if max < 0 { - return nil - } - h, err := s.History(name) - if err != nil { - return err - } - if len(h) <= max { - return nil - } - - // We want oldest to newest - relutil.SortByRevision(h) - - lastDeployed, err := s.Deployed(name) - if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { - return err - } - - var toDelete []*rspb.Release - for _, rel := range h { - // once we have enough releases to delete to reach the max, stop - if len(h)-len(toDelete) == max { - break - } - if lastDeployed != nil { - if rel.Version != lastDeployed.Version { - toDelete = append(toDelete, rel) - } - } else { - toDelete = append(toDelete, rel) - } - } - - // Delete as many as possible. In the case of API throughput limitations, - // multiple invocations of this function will eventually delete them all. - errs := []error{} - for _, rel := range toDelete { - err = s.deleteReleaseVersion(name, rel.Version) - if err != nil { - errs = append(errs, err) - } - } - - s.Log("Pruned %d record(s) from %s with %d error(s)", len(toDelete), name, len(errs)) - switch c := len(errs); c { - case 0: - return nil - case 1: - return errs[0] - default: - return errors.Errorf("encountered %d deletion errors. First is: %s", c, errs[0]) - } -} - -func (s *Storage) deleteReleaseVersion(name string, version int) error { - key := makeKey(name, version) - _, err := s.Delete(name, version) - if err != nil { - s.Log("error pruning %s from release history: %s", key, err) - return err - } - return nil -} - -// Last fetches the last revision of the named release. -func (s *Storage) Last(name string) (*rspb.Release, error) { - s.Log("getting last revision of %q", name) - h, err := s.History(name) - if err != nil { - return nil, err - } - if len(h) == 0 { - return nil, errors.Errorf("no revision for release %q", name) - } - - relutil.Reverse(h, relutil.SortByRevision) - return h[0], nil -} - -// makeKey concatenates the Kubernetes storage object type, a release name and version -// into a string with format:```..v```. -// The storage type is prepended to keep name uniqueness between different -// release storage types. An example of clash when not using the type: -// https://github.com/helm/helm/issues/6435. -// This key is used to uniquely identify storage objects. -func makeKey(rlsname string, version int) string { - return fmt.Sprintf("%s.%s.v%d", HelmStorageType, rlsname, version) -} - -// Init initializes a new storage backend with the driver d. -// If d is nil, the default in-memory driver is used. -func Init(d driver.Driver) *Storage { - // default driver is in memory - if d == nil { - d = driver.NewMemory() - } - return &Storage{ - Driver: d, - Log: func(_ string, _ ...interface{}) {}, - } -} diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go deleted file mode 100644 index 058b077e8..000000000 --- a/pkg/storage/storage_test.go +++ /dev/null @@ -1,560 +0,0 @@ -/* -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 storage // import "helm.sh/helm/v3/pkg/storage" - -import ( - "fmt" - "reflect" - "testing" - - "github.com/pkg/errors" - - rspb "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" -) - -func TestStorageCreate(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // create fake release - rls := ReleaseTestData{ - Name: "angry-beaver", - Version: 1, - }.ToRelease() - - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - - // fetch the release - res, err := storage.Get(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "QueryRelease") - - // verify the fetched and created release are the same - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } -} - -func TestStorageUpdate(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // create fake release - rls := ReleaseTestData{ - Name: "angry-beaver", - Version: 1, - Status: rspb.StatusDeployed, - }.ToRelease() - - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - - // modify the release - rls.Info.Status = rspb.StatusUninstalled - assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease") - - // retrieve the updated release - res, err := storage.Get(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "QueryRelease") - - // verify updated and fetched releases are the same. - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } -} - -func TestStorageDelete(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // create fake release - rls := ReleaseTestData{ - Name: "angry-beaver", - Version: 1, - }.ToRelease() - rls2 := ReleaseTestData{ - Name: "angry-beaver", - Version: 2, - }.ToRelease() - - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - assertErrNil(t.Fatal, storage.Create(rls2), "StoreRelease") - - // delete the release - res, err := storage.Delete(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "DeleteRelease") - - // verify updated and fetched releases are the same. - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } - - hist, err := storage.History(rls.Name) - if err != nil { - t.Errorf("unexpected error: %s", err) - } - - // We have now deleted one of the two records. - if len(hist) != 1 { - t.Errorf("expected 1 record for deleted release version, got %d", len(hist)) - } - - if hist[0].Version != 2 { - t.Errorf("Expected version to be 2, got %d", hist[0].Version) - } -} - -func TestStorageList(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: "happy-catdog", Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: "livid-human", Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: "relaxed-cat", Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: "hungry-hippo", Status: rspb.StatusDeployed}.ToRelease() - rls4 := ReleaseTestData{Name: "angry-beaver", Status: rspb.StatusDeployed}.ToRelease() - rls5 := ReleaseTestData{Name: "opulent-frog", Status: rspb.StatusUninstalled}.ToRelease() - rls6 := ReleaseTestData{Name: "happy-liger", Status: rspb.StatusUninstalled}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'rls0'") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'rls1'") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'rls2'") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'rls3'") - assertErrNil(t.Fatal, storage.Create(rls4), "Storing release 'rls4'") - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'rls5'") - assertErrNil(t.Fatal, storage.Create(rls6), "Storing release 'rls6'") - } - - var listTests = []struct { - Description string - NumExpected int - ListFunc func() ([]*rspb.Release, error) - }{ - {"ListDeployed", 2, storage.ListDeployed}, - {"ListReleases", 7, storage.ListReleases}, - {"ListUninstalled", 2, storage.ListUninstalled}, - } - - setup() - - for _, tt := range listTests { - list, err := tt.ListFunc() - assertErrNil(t.Fatal, err, tt.Description) - // verify the count of releases returned - if len(list) != tt.NumExpected { - t.Errorf("ListReleases(%s): expected %d, actual %d", - tt.Description, - tt.NumExpected, - len(list)) - } - } -} - -func TestStorageDeployed(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - const vers = 4 - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - rls, err := storage.Last(name) - if err != nil { - t.Fatalf("Failed to query for deployed release: %s\n", err) - } - - switch { - case rls == nil: - t.Fatalf("Release is nil") - case rls.Name != name: - t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name) - case rls.Version != vers: - t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version) - case rls.Info.Status != rspb.StatusDeployed: - t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.String()) - } -} - -func TestStorageDeployedWithCorruption(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - const vers = int(4) - - // setup storage with test releases - setup := func() { - // release records (notice odd order and corruption) - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - rls, err := storage.Deployed(name) - if err != nil { - t.Fatalf("Failed to query for deployed release: %s\n", err) - } - - switch { - case rls == nil: - t.Fatalf("Release is nil") - case rls.Name != name: - t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name) - case rls.Version != vers: - t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version) - case rls.Info.Status != rspb.StatusDeployed: - t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.String()) - } -} - -func TestStorageHistory(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - h, err := storage.History(name) - if err != nil { - t.Fatalf("Failed to query for release history (%q): %s\n", name, err) - } - if len(h) != 4 { - t.Fatalf("Release history (%q) is empty\n", name) - } -} - -var errMaxHistoryMockDriverSomethingHappened = errors.New("something happened") - -type MaxHistoryMockDriver struct { - Driver driver.Driver -} - -func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver { - return &MaxHistoryMockDriver{Driver: d} -} -func (d *MaxHistoryMockDriver) Create(key string, rls *rspb.Release) error { - return d.Driver.Create(key, rls) -} -func (d *MaxHistoryMockDriver) Update(key string, rls *rspb.Release) error { - return d.Driver.Update(key, rls) -} -func (d *MaxHistoryMockDriver) Delete(key string) (*rspb.Release, error) { - return nil, errMaxHistoryMockDriverSomethingHappened -} -func (d *MaxHistoryMockDriver) Get(key string) (*rspb.Release, error) { - return d.Driver.Get(key) -} -func (d *MaxHistoryMockDriver) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - return d.Driver.List(filter) -} -func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]*rspb.Release, error) { - return d.Driver.Query(labels) -} -func (d *MaxHistoryMockDriver) Name() string { - return d.Driver.Name() -} - -func TestMaxHistoryErrorHandling(t *testing.T) { - //func TestStorageRemoveLeastRecentWithError(t *testing.T) { - storage := Init(NewMaxHistoryMockDriver(driver.NewMemory())) - storage.Log = t.Logf - - storage.MaxHistory = 1 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls1 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Driver.Create(makeKey(rls1.Name, rls1.Version), rls1), "Storing release 'angry-bird' (v1)") - } - setup() - - rls2 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - wantErr := errMaxHistoryMockDriverSomethingHappened - gotErr := storage.Create(rls2) - if !errors.Is(gotErr, wantErr) { - t.Fatalf("Storing release 'angry-bird' (v2) should return the error %#v, but returned %#v", wantErr, gotErr) - } -} - -func TestStorageRemoveLeastRecent(t *testing.T) { - storage := Init(driver.NewMemory()) - storage.Log = t.Logf - - // Make sure that specifying this at the outset doesn't cause any bugs. - storage.MaxHistory = 10 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - setup() - - // Because we have not set a limit, we expect 4. - expect := 4 - if hist, err := storage.History(name); err != nil { - t.Fatal(err) - } else if len(hist) != expect { - t.Fatalf("expected %d items in history, got %d", expect, len(hist)) - } - - storage.MaxHistory = 3 - rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusDeployed}.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") - - // On inserting the 5th record, we expect two records to be pruned from history. - hist, err := storage.History(name) - if err != nil { - t.Fatal(err) - } else if len(hist) != storage.MaxHistory { - for _, item := range hist { - t.Logf("%s %v", item.Name, item.Version) - } - t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist)) - } - - // We expect the existing records to be 3, 4, and 5. - for i, item := range hist { - v := item.Version - if expect := i + 3; v != expect { - t.Errorf("Expected release %d, got %d", expect, v) - } - } -} - -func TestStorageDoNotDeleteDeployed(t *testing.T) { - storage := Init(driver.NewMemory()) - storage.Log = t.Logf - storage.MaxHistory = 3 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - setup() - - rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") - - // On inserting the 5th record, we expect a total of 3 releases, but we expect version 2 - // (the only deployed release), to still exist - hist, err := storage.History(name) - if err != nil { - t.Fatal(err) - } else if len(hist) != storage.MaxHistory { - for _, item := range hist { - t.Logf("%s %v", item.Name, item.Version) - } - t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist)) - } - - expectedVersions := map[int]bool{ - 2: true, - 4: true, - 5: true, - } - - for _, item := range hist { - if !expectedVersions[item.Version] { - t.Errorf("Release version %d, found when not expected", item.Version) - } - } -} - -func TestStorageLast(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - - // Set up storage with test releases. - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - h, err := storage.Last(name) - if err != nil { - t.Fatalf("Failed to query for release history (%q): %s\n", name, err) - } - - if h.Version != 4 { - t.Errorf("Expected revision 4, got %d", h.Version) - } -} - -// TestUpgradeInitiallyFailedRelease tests a case when there are no deployed release yet, but history limit has been -// reached: the has-no-deployed-releases error should not occur in such case. -func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) { - storage := Init(driver.NewMemory()) - storage.MaxHistory = 4 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusFailed}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusFailed}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - - hist, err := storage.History(name) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - wantHistoryLen := 4 - if len(hist) != wantHistoryLen { - t.Fatalf("expected history of release %q to contain %d releases, got %d", name, wantHistoryLen, len(hist)) - } - } - - setup() - - rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease() - err := storage.Create(rls5) - if err != nil { - t.Fatalf("Failed to create a new release version: %s", err) - } - - hist, err := storage.History(name) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - for i, rel := range hist { - wantVersion := i + 2 - if rel.Version != wantVersion { - t.Fatalf("Expected history release %d version to equal %d, got %d", i+1, wantVersion, rel.Version) - } - - wantStatus := rspb.StatusFailed - if rel.Info.Status != wantStatus { - t.Fatalf("Expected history release %d status to equal %q, got %q", i+1, wantStatus, rel.Info.Status) - } - } -} - -type ReleaseTestData struct { - Name string - Version int - Manifest string - Namespace string - Status rspb.Status -} - -func (test ReleaseTestData) ToRelease() *rspb.Release { - return &rspb.Release{ - Name: test.Name, - Version: test.Version, - Manifest: test.Manifest, - Namespace: test.Namespace, - Info: &rspb.Info{Status: test.Status}, - } -} - -func assertErrNil(eh func(args ...interface{}), err error, message string) { - if err != nil { - eh(fmt.Sprintf("%s: %q", message, err)) - } -} diff --git a/pkg/strvals/doc.go b/pkg/strvals/doc.go deleted file mode 100644 index f17290587..000000000 --- a/pkg/strvals/doc.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -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 strvals provides tools for working with strval lines. - -Helm supports a compressed format for YAML settings which we call strvals. -The format is roughly like this: - - name=value,topname.subname=value - -The above is equivalent to the YAML document - - name: value - topname: - subname: value - -This package provides a parser and utilities for converting the strvals format -to other formats. -*/ -package strvals diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go deleted file mode 100644 index 26bc0fcf2..000000000 --- a/pkg/strvals/parser.go +++ /dev/null @@ -1,549 +0,0 @@ -/* -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 strvals - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "strconv" - "strings" - "unicode" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" -) - -// ErrNotList indicates that a non-list was treated as a list. -var ErrNotList = errors.New("not a list") - -// MaxIndex is the maximum index that will be allowed by setIndex. -// The default value 65536 = 1024 * 64 -var MaxIndex = 65536 - -// ToYAML takes a string of arguments and converts to a YAML document. -func ToYAML(s string) (string, error) { - m, err := Parse(s) - if err != nil { - return "", err - } - d, err := yaml.Marshal(m) - return strings.TrimSuffix(string(d), "\n"), err -} - -// Parse parses a set line. -// -// A set line is of the form name1=value1,name2=value2 -func Parse(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} - scanner := bytes.NewBufferString(s) - t := newParser(scanner, vals, false) - err := t.parse() - return vals, err -} - -// ParseString parses a set line and forces a string value. -// -// A set line is of the form name1=value1,name2=value2 -func ParseString(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} - scanner := bytes.NewBufferString(s) - t := newParser(scanner, vals, true) - err := t.parse() - return vals, err -} - -// ParseInto parses a strvals line and merges the result into dest. -// -// If the strval string has a key that exists in dest, it overwrites the -// dest version. -func ParseInto(s string, dest map[string]interface{}) error { - scanner := bytes.NewBufferString(s) - t := newParser(scanner, dest, false) - return t.parse() -} - -// ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value. -// -// A set line is of the form name1=path1,name2=path2 -// -// When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as -// name1=val1,name2=val2 -func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) { - vals := map[string]interface{}{} - scanner := bytes.NewBufferString(s) - t := newFileParser(scanner, vals, reader) - err := t.parse() - return vals, err -} - -// ParseIntoString parses a strvals line and merges the result into dest. -// -// This method always returns a string as the value. -func ParseIntoString(s string, dest map[string]interface{}) error { - scanner := bytes.NewBufferString(s) - t := newParser(scanner, dest, true) - return t.parse() -} - -// ParseJSON parses a string with format key1=val1, key2=val2, ... -// where values are json strings (null, or scalars, or arrays, or objects). -// An empty val is treated as null. -// -// If a key exists in dest, the new value overwrites the dest version. -// -func ParseJSON(s string, dest map[string]interface{}) error { - scanner := bytes.NewBufferString(s) - t := newJSONParser(scanner, dest) - return t.parse() -} - -// ParseIntoFile parses a filevals line and merges the result into dest. -// -// This method always returns a string as the value. -func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error { - scanner := bytes.NewBufferString(s) - t := newFileParser(scanner, dest, reader) - return t.parse() -} - -// RunesValueReader is a function that takes the given value (a slice of runes) -// and returns the parsed value -type RunesValueReader func([]rune) (interface{}, error) - -// parser is a simple parser that takes a strvals line and parses it into a -// map representation. -// -// where sc is the source of the original data being parsed -// where data is the final parsed data from the parses with correct types -type parser struct { - sc *bytes.Buffer - data map[string]interface{} - reader RunesValueReader - isjsonval bool -} - -func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { - stringConverter := func(rs []rune) (interface{}, error) { - return typedVal(rs, stringBool), nil - } - return &parser{sc: sc, data: data, reader: stringConverter} -} - -func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser { - return &parser{sc: sc, data: data, reader: nil, isjsonval: true} -} - -func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { - return &parser{sc: sc, data: data, reader: reader} -} - -func (t *parser) parse() error { - for { - err := t.key(t.data) - if err == nil { - continue - } - if err == io.EOF { - return nil - } - return err - } -} - -func runeSet(r []rune) map[rune]bool { - s := make(map[rune]bool, len(r)) - for _, rr := range r { - s[rr] = true - } - return s -} - -func (t *parser) key(data map[string]interface{}) (reterr error) { - defer func() { - if r := recover(); r != nil { - reterr = fmt.Errorf("unable to parse key: %s", r) - } - }() - stop := runeSet([]rune{'=', '[', ',', '.'}) - for { - switch k, last, err := runesUntil(t.sc, stop); { - case err != nil: - if len(k) == 0 { - return err - } - return errors.Errorf("key %q has no value", string(k)) - //set(data, string(k), "") - //return err - case last == '[': - // We are in a list index context, so we need to set an index. - i, err := t.keyIndex() - if err != nil { - return errors.Wrap(err, "error parsing index") - } - kk := string(k) - // Find or create target list - list := []interface{}{} - if _, ok := data[kk]; ok { - list = data[kk].([]interface{}) - } - - // Now we need to get the value after the ]. - list, err = t.listItem(list, i) - set(data, kk, list) - return err - case last == '=': - if t.isjsonval { - empval, err := t.emptyVal() - if err != nil { - return err - } - if empval { - set(data, string(k), nil) - return nil - } - // parse jsonvals by using Go’s JSON standard library - // Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,... - // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, - // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we - // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} - dec := json.NewDecoder(strings.NewReader(t.sc.String())) - if err = dec.Decode(&jsonval); err != nil { - return err - } - set(data, string(k), jsonval) - if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil { - return err - } - // skip possible blanks and comma - _, err = t.emptyVal() - return err - } - //End of key. Consume =, Get value. - // FIXME: Get value list first - vl, e := t.valList() - switch e { - case nil: - set(data, string(k), vl) - return nil - case io.EOF: - set(data, string(k), "") - return e - case ErrNotList: - rs, e := t.val() - if e != nil && e != io.EOF { - return e - } - v, e := t.reader(rs) - set(data, string(k), v) - return e - default: - return e - } - case last == ',': - // No value given. Set the value to empty string. Return error. - set(data, string(k), "") - return errors.Errorf("key %q has no value (cannot end with ,)", string(k)) - case last == '.': - // First, create or find the target map. - inner := map[string]interface{}{} - if _, ok := data[string(k)]; ok { - inner = data[string(k)].(map[string]interface{}) - } - - // Recurse - e := t.key(inner) - if len(inner) == 0 { - return errors.Errorf("key map %q has no value", string(k)) - } - set(data, string(k), inner) - return e - } - } -} - -func set(data map[string]interface{}, key string, val interface{}) { - // If key is empty, don't set it. - if len(key) == 0 { - return - } - data[key] = val -} - -func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) { - // There are possible index values that are out of range on a target system - // causing a panic. This will catch the panic and return an error instead. - // The value of the index that causes a panic varies from system to system. - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("error processing index %d: %s", index, r) - } - }() - - if index < 0 { - return list, fmt.Errorf("negative %d index not allowed", index) - } - if index > MaxIndex { - return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex) - } - if len(list) <= index { - newlist := make([]interface{}, index+1) - copy(newlist, list) - list = newlist - } - list[index] = val - return list, nil -} - -func (t *parser) keyIndex() (int, error) { - // First, get the key. - stop := runeSet([]rune{']'}) - v, _, err := runesUntil(t.sc, stop) - if err != nil { - return 0, err - } - // v should be the index - return strconv.Atoi(string(v)) - -} -func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { - if i < 0 { - return list, fmt.Errorf("negative %d index not allowed", i) - } - stop := runeSet([]rune{'[', '.', '='}) - switch k, last, err := runesUntil(t.sc, stop); { - case len(k) > 0: - return list, errors.Errorf("unexpected data at end of array index: %q", k) - case err != nil: - return list, err - case last == '=': - if t.isjsonval { - empval, err := t.emptyVal() - if err != nil { - return list, err - } - if empval { - return setIndex(list, i, nil) - } - // parse jsonvals by using Go’s JSON standard library - // Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,... - // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, - // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we - // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} - dec := json.NewDecoder(strings.NewReader(t.sc.String())) - if err = dec.Decode(&jsonval); err != nil { - return list, err - } - if list, err = setIndex(list, i, jsonval); err != nil { - return list, err - } - if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil { - return list, err - } - // skip possible blanks and comma - _, err = t.emptyVal() - return list, err - } - vl, e := t.valList() - switch e { - case nil: - return setIndex(list, i, vl) - case io.EOF: - return setIndex(list, i, "") - case ErrNotList: - rs, e := t.val() - if e != nil && e != io.EOF { - return list, e - } - v, e := t.reader(rs) - if e != nil { - return list, e - } - return setIndex(list, i, v) - default: - return list, e - } - case last == '[': - // now we have a nested list. Read the index and handle. - nextI, err := t.keyIndex() - if err != nil { - return list, errors.Wrap(err, "error parsing index") - } - var crtList []interface{} - if len(list) > i { - // If nested list already exists, take the value of list to next cycle. - existed := list[i] - if existed != nil { - crtList = list[i].([]interface{}) - } - } - // Now we need to get the value after the ]. - list2, err := t.listItem(crtList, nextI) - if err != nil { - return list, err - } - return setIndex(list, i, list2) - case last == '.': - // We have a nested object. Send to t.key - inner := map[string]interface{}{} - if len(list) > i { - var ok bool - inner, ok = list[i].(map[string]interface{}) - if !ok { - // We have indices out of order. Initialize empty value. - list[i] = map[string]interface{}{} - inner = list[i].(map[string]interface{}) - } - } - - // Recurse - e := t.key(inner) - if e != nil { - return list, e - } - return setIndex(list, i, inner) - default: - return nil, errors.Errorf("parse error: unexpected token %v", last) - } -} - -// check for an empty value -// read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val) -// comma and spaces are consumed, while any other char is not cosumed -func (t *parser) emptyVal() (bool, error) { - for { - r, _, e := t.sc.ReadRune() - if e == io.EOF { - return true, nil - } - if e != nil { - return false, e - } - if r == ',' { - return true, nil - } - if !unicode.IsSpace(r) { - t.sc.UnreadRune() - return false, nil - } - } -} - -func (t *parser) val() ([]rune, error) { - stop := runeSet([]rune{','}) - v, _, err := runesUntil(t.sc, stop) - return v, err -} - -func (t *parser) valList() ([]interface{}, error) { - r, _, e := t.sc.ReadRune() - if e != nil { - return []interface{}{}, e - } - - if r != '{' { - t.sc.UnreadRune() - return []interface{}{}, ErrNotList - } - - list := []interface{}{} - stop := runeSet([]rune{',', '}'}) - for { - switch rs, last, err := runesUntil(t.sc, stop); { - case err != nil: - if err == io.EOF { - err = errors.New("list must terminate with '}'") - } - return list, err - case last == '}': - // If this is followed by ',', consume it. - if r, _, e := t.sc.ReadRune(); e == nil && r != ',' { - t.sc.UnreadRune() - } - v, e := t.reader(rs) - list = append(list, v) - return list, e - case last == ',': - v, e := t.reader(rs) - if e != nil { - return list, e - } - list = append(list, v) - } - } -} - -func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) { - v := []rune{} - for { - switch r, _, e := in.ReadRune(); { - case e != nil: - return v, r, e - case inMap(r, stop): - return v, r, nil - case r == '\\': - next, _, e := in.ReadRune() - if e != nil { - return v, next, e - } - v = append(v, next) - default: - v = append(v, r) - } - } -} - -func inMap(k rune, m map[rune]bool) bool { - _, ok := m[k] - return ok -} - -func typedVal(v []rune, st bool) interface{} { - val := string(v) - - if st { - return val - } - - if strings.EqualFold(val, "true") { - return true - } - - if strings.EqualFold(val, "false") { - return false - } - - if strings.EqualFold(val, "null") { - return nil - } - - if strings.EqualFold(val, "0") { - return int64(0) - } - - // If this value does not start with zero, try parsing it to an int - if len(val) != 0 && val[0] != '0' { - if iv, err := strconv.ParseInt(val, 10, 64); err == nil { - return iv - } - } - - return val -} diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go deleted file mode 100644 index f7eba7830..000000000 --- a/pkg/strvals/parser_test.go +++ /dev/null @@ -1,756 +0,0 @@ -/* -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 strvals - -import ( - "testing" - - "sigs.k8s.io/yaml" -) - -func TestSetIndex(t *testing.T) { - tests := []struct { - name string - initial []interface{} - expect []interface{} - add int - val int - err bool - }{ - { - name: "short", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 1, 2}, - add: 2, - val: 2, - err: false, - }, - { - name: "equal", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 2}, - add: 1, - val: 2, - err: false, - }, - { - name: "long", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 4, 4, 5}, - add: 3, - val: 4, - err: false, - }, - { - name: "negative", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, - add: -1, - val: 4, - err: true, - }, - { - name: "large", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, - add: MaxIndex + 1, - val: 4, - err: true, - }, - } - - for _, tt := range tests { - got, err := setIndex(tt.initial, tt.add, tt.val) - - if err != nil && tt.err == false { - t.Fatalf("%s: Expected no error but error returned", tt.name) - } else if err == nil && tt.err == true { - t.Fatalf("%s: Expected error but no error returned", tt.name) - } - - if len(got) != len(tt.expect) { - t.Fatalf("%s: Expected length %d, got %d", tt.name, len(tt.expect), len(got)) - } - - if !tt.err { - if gg := got[tt.add].(int); gg != tt.val { - t.Errorf("%s, Expected value %d, got %d", tt.name, tt.val, gg) - } - } - - for k, v := range got { - if v != tt.expect[k] { - t.Errorf("%s, Expected value %d, got %d", tt.name, tt.expect[k], v) - } - } - } -} - -func TestParseSet(t *testing.T) { - testsString := []struct { - str string - expect map[string]interface{} - err bool - }{ - { - str: "long_int_string=1234567890", - expect: map[string]interface{}{"long_int_string": "1234567890"}, - err: false, - }, - { - str: "boolean=true", - expect: map[string]interface{}{"boolean": "true"}, - err: false, - }, - { - str: "is_null=null", - expect: map[string]interface{}{"is_null": "null"}, - err: false, - }, - { - str: "zero=0", - expect: map[string]interface{}{"zero": "0"}, - err: false, - }, - } - tests := []struct { - str string - expect map[string]interface{} - err bool - }{ - { - "name1=null,f=false,t=true", - map[string]interface{}{"name1": nil, "f": false, "t": true}, - false, - }, - { - "name1=value1", - map[string]interface{}{"name1": "value1"}, - false, - }, - { - "name1=value1,name2=value2", - map[string]interface{}{"name1": "value1", "name2": "value2"}, - false, - }, - { - "name1=value1,name2=value2,", - map[string]interface{}{"name1": "value1", "name2": "value2"}, - false, - }, - { - str: "name1=value1,,,,name2=value2,", - err: true, - }, - { - str: "name1=,name2=value2", - expect: map[string]interface{}{"name1": "", "name2": "value2"}, - }, - { - str: "leading_zeros=00009", - expect: map[string]interface{}{"leading_zeros": "00009"}, - }, - { - str: "zero_int=0", - expect: map[string]interface{}{"zero_int": 0}, - }, - { - str: "long_int=1234567890", - expect: map[string]interface{}{"long_int": 1234567890}, - }, - { - str: "boolean=true", - expect: map[string]interface{}{"boolean": true}, - }, - { - str: "is_null=null", - expect: map[string]interface{}{"is_null": nil}, - err: false, - }, - { - str: "name1,name2=", - err: true, - }, - { - str: "name1,name2=value2", - err: true, - }, - { - str: "name1,name2=value2\\", - err: true, - }, - { - str: "name1,name2", - err: true, - }, - { - "name1=one\\,two,name2=three\\,four", - map[string]interface{}{"name1": "one,two", "name2": "three,four"}, - false, - }, - { - "name1=one\\=two,name2=three\\=four", - map[string]interface{}{"name1": "one=two", "name2": "three=four"}, - false, - }, - { - "name1=one two three,name2=three two one", - map[string]interface{}{"name1": "one two three", "name2": "three two one"}, - false, - }, - { - "outer.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}}, - false, - }, - { - "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, - false, - }, - { - "outer.inner1=value,outer.inner2=value2", - map[string]interface{}{"outer": map[string]interface{}{"inner1": "value", "inner2": "value2"}}, - false, - }, - { - "outer.inner1=value,outer.middle.inner=value", - map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "value", - "middle": map[string]interface{}{ - "inner": "value", - }, - }, - }, - false, - }, - { - str: "name1.name2", - err: true, - }, - { - str: "name1.name2,name1.name3", - err: true, - }, - { - str: "name1.name2=", - expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}}, - }, - { - str: "name1.=name2", - err: true, - }, - { - str: "name1.,name2", - err: true, - }, - { - "name1={value1,value2}", - map[string]interface{}{"name1": []string{"value1", "value2"}}, - false, - }, - { - "name1={value1,value2},name2={value1,value2}", - map[string]interface{}{ - "name1": []string{"value1", "value2"}, - "name2": []string{"value1", "value2"}, - }, - false, - }, - { - "name1={1021,902}", - map[string]interface{}{"name1": []int{1021, 902}}, - false, - }, - { - "name1.name2={value1,value2}", - map[string]interface{}{"name1": map[string]interface{}{"name2": []string{"value1", "value2"}}}, - false, - }, - { - str: "name1={1021,902", - err: true, - }, - // List support - { - str: "list[0]=foo", - expect: map[string]interface{}{"list": []string{"foo"}}, - }, - { - str: "list[0].foo=bar", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar"}, - }, - }, - }, - { - str: "list[0].foo=bar,list[0].hello=world", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar", "hello": "world"}, - }, - }, - }, - { - str: "list[0].foo=bar,list[-30].hello=world", - err: true, - }, - { - str: "list[0]=foo,list[1]=bar", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, - }, - { - str: "list[0]=foo,list[1]=bar,", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, - }, - { - str: "list[0]=foo,list[3]=bar", - expect: map[string]interface{}{"list": []interface{}{"foo", nil, nil, "bar"}}, - }, - { - str: "list[0]=foo,list[-20]=bar", - err: true, - }, - { - str: "illegal[0]name.foo=bar", - err: true, - }, - { - str: "noval[0]", - expect: map[string]interface{}{"noval": []interface{}{}}, - }, - { - str: "noval[0]=", - expect: map[string]interface{}{"noval": []interface{}{""}}, - }, - { - str: "nested[0][0]=1", - expect: map[string]interface{}{"nested": []interface{}{[]interface{}{1}}}, - }, - { - str: "nested[1][1]=1", - expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}}, - }, - { - str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, - }, - }, - }, - { - str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, - }, - }, - }, - { - str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, - }, - }, - }, - { - str: "]={}].", - err: true, - }, - } - - for _, tt := range tests { - got, err := Parse(tt.str) - if err != nil { - if tt.err { - continue - } - t.Fatalf("%s: %s", tt.str, err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) - } - } - for _, tt := range testsString { - got, err := ParseString(tt.str) - if err != nil { - if tt.err { - continue - } - t.Fatalf("%s: %s", tt.str, err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) - } - } -} - -func TestParseInto(t *testing.T) { - tests := []struct { - input string - input2 string - got map[string]interface{} - expect map[string]interface{} - err bool - }{ - { - input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "value1", - "inner2": "value2", - "inner3": "value3", - "inner4": 4, - }}, - err: false, - }, - { - input: "listOuter[0][0].type=listValue", - input2: "listOuter[0][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{{map[string]string{ - "type": "listValue", - "status": "alive", - }}}, - }, - err: false, - }, - { - input: "listOuter[0][0].type=listValue", - input2: "listOuter[1][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{ - { - map[string]string{"type": "listValue"}, - }, - { - map[string]string{"status": "alive"}, - }, - }, - }, - err: false, - }, - { - input: "listOuter[0][1][0].type=listValue", - input2: "listOuter[0][0][1].status=alive", - got: map[string]interface{}{ - "listOuter": []interface{}{ - []interface{}{ - []interface{}{ - map[string]string{"exited": "old"}, - }, - }, - }, - }, - expect: map[string]interface{}{ - "listOuter": [][][]interface{}{ - { - { - map[string]string{"exited": "old"}, - map[string]string{"status": "alive"}, - }, - { - map[string]string{"type": "listValue"}, - }, - }, - }, - }, - err: false, - }, - } - for _, tt := range tests { - if err := ParseInto(tt.input, tt.got); err != nil { - t.Fatal(err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input) - } - - if tt.input2 != "" { - if err := ParseInto(tt.input2, tt.got); err != nil { - t.Fatal(err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input2) - } - } - - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(tt.got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) - } - } -} - -func TestParseIntoString(t *testing.T) { - got := map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", - }, - } - input := "outer.inner1=1,outer.inner3=3" - expect := map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "1", - "inner2": "value2", - "inner3": "3", - }, - } - - if err := ParseIntoString(input, got); err != nil { - t.Fatal(err) - } - - y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) - } -} - -func TestParseJSON(t *testing.T) { - tests := []struct { - input string - got map[string]interface{} - expect map[string]interface{} - err bool - }{ - { // set json scalars values, and replace one existing key - input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "1", - "inner2": "value2", - "inner3": 3, - "inner4": true, - "inner5": "true", - }, - }, - err: false, - }, - { // set json objects and arrays, and replace one existing key - input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ - "x": "overwrite", - }, - "inner2": "value2", - "inner3": []interface{}{ - "overwrite", - }, - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}}, - "inner2": "value2", - "inner3": []interface{}{"new value 1", "new value 2"}, - "inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}}, - "inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}}, - }, - }, - err: false, - }, - { // null assigment, and no value assigned (equivalent to null) - input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ - "x": "overwrite", - }, - "inner2": "value2", - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": nil, - "inner2": "value2", - "inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}}, - }, - }, - err: false, - }, - { // syntax error - input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5={\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]", - got: nil, - expect: nil, - err: true, - }, - } - for _, tt := range tests { - if err := ParseJSON(tt.input, tt.got); err != nil { - if tt.err { - continue - } - t.Fatalf("%s: %s", tt.input, err) - } - if tt.err { - t.Fatalf("%s: Expected error. Got nil", tt.input) - } - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatalf("Error serializing expected value: %s", err) - } - y2, err := yaml.Marshal(tt.got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) - } - } -} - -func TestParseFile(t *testing.T) { - input := "name1=path1" - expect := map[string]interface{}{ - "name1": "value1", - } - rs2v := func(rs []rune) (interface{}, error) { - v := string(rs) - if v != "path1" { - t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) - return "", nil - } - return "value1", nil - } - - got, err := ParseFile(input, rs2v) - if err != nil { - t.Fatal(err) - } - - y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) - } -} - -func TestParseIntoFile(t *testing.T) { - got := map[string]interface{}{} - input := "name1=path1" - expect := map[string]interface{}{ - "name1": "value1", - } - rs2v := func(rs []rune) (interface{}, error) { - v := string(rs) - if v != "path1" { - t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) - return "", nil - } - return "value1", nil - } - - if err := ParseIntoFile(input, got, rs2v); err != nil { - t.Fatal(err) - } - - y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) - } -} - -func TestToYAML(t *testing.T) { - // The TestParse does the hard part. We just verify that YAML formatting is - // happening. - o, err := ToYAML("name=value") - if err != nil { - t.Fatal(err) - } - expect := "name: value" - if o != expect { - t.Errorf("Expected %q, got %q", expect, o) - } -} diff --git a/pkg/time/time.go b/pkg/time/time.go deleted file mode 100644 index 44f3fedfb..000000000 --- a/pkg/time/time.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -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 time contains a wrapper for time.Time in the standard library and -// associated methods. This package mainly exists to workaround an issue in Go -// where the serializer doesn't omit an empty value for time: -// https://github.com/golang/go/issues/11939. As such, this can be removed if a -// proposal is ever accepted for Go -package time - -import ( - "bytes" - "time" -) - -// emptyString contains an empty JSON string value to be used as output -var emptyString = `""` - -// Time is a convenience wrapper around stdlib time, but with different -// marshalling and unmarshaling for zero values -type Time struct { - time.Time -} - -// Now returns the current time. It is a convenience wrapper around time.Now() -func Now() Time { - return Time{time.Now()} -} - -func (t Time) MarshalJSON() ([]byte, error) { - if t.Time.IsZero() { - return []byte(emptyString), nil - } - - return t.Time.MarshalJSON() -} - -func (t *Time) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - return nil - } - // If it is empty, we don't have to set anything since time.Time is not a - // pointer and will be set to the zero value - if bytes.Equal([]byte(emptyString), b) { - return nil - } - - return t.Time.UnmarshalJSON(b) -} - -func Parse(layout, value string) (Time, error) { - t, err := time.Parse(layout, value) - return Time{Time: t}, err -} -func ParseInLocation(layout, value string, loc *time.Location) (Time, error) { - t, err := time.ParseInLocation(layout, value, loc) - return Time{Time: t}, err -} - -func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { - return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)} -} - -func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} } - -func (t Time) Add(d time.Duration) Time { return Time{Time: t.Time.Add(d)} } -func (t Time) AddDate(years int, months int, days int) Time { - return Time{Time: t.Time.AddDate(years, months, days)} -} -func (t Time) After(u Time) bool { return t.Time.After(u.Time) } -func (t Time) Before(u Time) bool { return t.Time.Before(u.Time) } -func (t Time) Equal(u Time) bool { return t.Time.Equal(u.Time) } -func (t Time) In(loc *time.Location) Time { return Time{Time: t.Time.In(loc)} } -func (t Time) Local() Time { return Time{Time: t.Time.Local()} } -func (t Time) Round(d time.Duration) Time { return Time{Time: t.Time.Round(d)} } -func (t Time) Sub(u Time) time.Duration { return t.Time.Sub(u.Time) } -func (t Time) Truncate(d time.Duration) Time { return Time{Time: t.Time.Truncate(d)} } -func (t Time) UTC() Time { return Time{Time: t.Time.UTC()} } diff --git a/pkg/time/time_test.go b/pkg/time/time_test.go deleted file mode 100644 index 20f0f8e29..000000000 --- a/pkg/time/time_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -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 time - -import ( - "encoding/json" - "testing" - "time" -) - -var ( - testingTime, _ = Parse(time.RFC3339, "1977-09-02T22:04:05Z") - testingTimeString = `"1977-09-02T22:04:05Z"` -) - -func TestNonZeroValueMarshal(t *testing.T) { - res, err := json.Marshal(testingTime) - if err != nil { - t.Fatal(err) - } - if testingTimeString != string(res) { - t.Errorf("expected a marshaled value of %s, got %s", testingTimeString, res) - } -} - -func TestZeroValueMarshal(t *testing.T) { - res, err := json.Marshal(Time{}) - if err != nil { - t.Fatal(err) - } - if string(res) != emptyString { - t.Errorf("expected zero value to marshal to empty string, got %s", res) - } -} - -func TestNonZeroValueUnmarshal(t *testing.T) { - var myTime Time - err := json.Unmarshal([]byte(testingTimeString), &myTime) - if err != nil { - t.Fatal(err) - } - if !myTime.Equal(testingTime) { - t.Errorf("expected time to be equal to %v, got %v", testingTime, myTime) - } -} - -func TestEmptyStringUnmarshal(t *testing.T) { - var myTime Time - err := json.Unmarshal([]byte(emptyString), &myTime) - if err != nil { - t.Fatal(err) - } - if !myTime.IsZero() { - t.Errorf("expected time to be equal to zero value, got %v", myTime) - } -} - -func TestZeroValueUnmarshal(t *testing.T) { - // This test ensures that we can unmarshal any time value that was output - // with the current go default value of "0001-01-01T00:00:00Z" - var myTime Time - err := json.Unmarshal([]byte(`"0001-01-01T00:00:00Z"`), &myTime) - if err != nil { - t.Fatal(err) - } - if !myTime.IsZero() { - t.Errorf("expected time to be equal to zero value, got %v", myTime) - } -} diff --git a/pkg/uploader/chart_uploader.go b/pkg/uploader/chart_uploader.go deleted file mode 100644 index d7e940406..000000000 --- a/pkg/uploader/chart_uploader.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -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 uploader - -import ( - "fmt" - "io" - "net/url" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/pusher" - "helm.sh/helm/v3/pkg/registry" -) - -// ChartUploader handles uploading a chart. -type ChartUploader struct { - // Out is the location to write warning and info messages. - Out io.Writer - // Pusher collection for the operation - Pushers pusher.Providers - // Options provide parameters to be passed along to the Pusher being initialized. - Options []pusher.Option - // RegistryClient is a client for interacting with registries. - RegistryClient *registry.Client -} - -// UploadTo uploads a chart. Depending on the settings, it may also upload a provenance file. -func (c *ChartUploader) UploadTo(ref, remote string) error { - u, err := url.Parse(remote) - if err != nil { - return errors.Errorf("invalid chart URL format: %s", remote) - } - - if u.Scheme == "" { - return fmt.Errorf("scheme prefix missing from remote (e.g. \"%s://\")", registry.OCIScheme) - } - - p, err := c.Pushers.ByScheme(u.Scheme) - if err != nil { - return err - } - - return p.Push(ref, u.String(), c.Options...) -} diff --git a/pkg/uploader/doc.go b/pkg/uploader/doc.go deleted file mode 100644 index 45eacbbf5..000000000 --- a/pkg/uploader/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 uploader provides a library for uploading charts. - -This package contains tools for uploading charts to registries. -*/ -package uploader