420
vendor/helm.sh/helm/v3/pkg/action/action.go
vendored
Normal file
420
vendor/helm.sh/helm/v3/pkg/action/action.go
vendored
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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/internal/experimental/registry"
|
||||
"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/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 (c *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 := c.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 && c.RESTClientGetter != nil {
|
||||
rest, err := c.RESTClientGetter.ToRESTConfig()
|
||||
if err != nil {
|
||||
return hs, b, "", err
|
||||
}
|
||||
files, err2 = engine.RenderWithClient(ch, values, rest)
|
||||
} 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 (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
|
||||
if c.Capabilities != nil {
|
||||
return c.Capabilities, nil
|
||||
}
|
||||
dc, err := c.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) {
|
||||
c.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
|
||||
c.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
|
||||
}
|
||||
}
|
||||
|
||||
c.Capabilities = &chartutil.Capabilities{
|
||||
APIVersions: apiVersions,
|
||||
KubeVersion: chartutil.KubeVersion{
|
||||
Version: kubeVersion.GitVersion,
|
||||
Major: kubeVersion.Major,
|
||||
Minor: kubeVersion.Minor,
|
||||
},
|
||||
}
|
||||
return c.Capabilities, nil
|
||||
}
|
||||
|
||||
// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration
|
||||
func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
|
||||
conf, err := c.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 (c *Configuration) Now() time.Time {
|
||||
return Timestamper()
|
||||
}
|
||||
|
||||
func (c *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 c.Releases.Last(name)
|
||||
}
|
||||
|
||||
return c.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 (c *Configuration) recordRelease(r *release.Release) {
|
||||
if err := c.Releases.Update(r); err != nil {
|
||||
c.Log("warning: Failed to update release %s: %s", r.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the action configuration
|
||||
func (c *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 c.Releases != nil {
|
||||
if mem, ok := c.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)
|
||||
}
|
||||
|
||||
c.RESTClientGetter = getter
|
||||
c.KubeClient = kc
|
||||
c.Releases = store
|
||||
c.Log = log
|
||||
|
||||
return nil
|
||||
}
|
||||
63
vendor/helm.sh/helm/v3/pkg/action/chart_export.go
vendored
Normal file
63
vendor/helm.sh/helm/v3/pkg/action/chart_export.go
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
// ChartExport performs a chart export operation.
|
||||
type ChartExport struct {
|
||||
cfg *Configuration
|
||||
|
||||
Destination string
|
||||
}
|
||||
|
||||
// NewChartExport creates a new ChartExport object with the given configuration.
|
||||
func NewChartExport(cfg *Configuration) *ChartExport {
|
||||
return &ChartExport{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart export operation
|
||||
func (a *ChartExport) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := a.cfg.RegistryClient.LoadChart(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the chart to local destination directory
|
||||
err = chartutil.SaveDir(ch, a.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := filepath.Join(a.Destination, ch.Metadata.Name)
|
||||
fmt.Fprintf(out, "Exported chart to %s/\n", d)
|
||||
return nil
|
||||
}
|
||||
38
vendor/helm.sh/helm/v3/pkg/action/chart_list.go
vendored
Normal file
38
vendor/helm.sh/helm/v3/pkg/action/chart_list.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
)
|
||||
|
||||
// ChartList performs a chart list operation.
|
||||
type ChartList struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartList creates a new ChartList object with the given configuration.
|
||||
func NewChartList(cfg *Configuration) *ChartList {
|
||||
return &ChartList{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart list operation
|
||||
func (a *ChartList) Run(out io.Writer) error {
|
||||
return a.cfg.RegistryClient.PrintChartTable()
|
||||
}
|
||||
44
vendor/helm.sh/helm/v3/pkg/action/chart_pull.go
vendored
Normal file
44
vendor/helm.sh/helm/v3/pkg/action/chart_pull.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartPull performs a chart pull operation.
|
||||
type ChartPull struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartPull creates a new ChartPull object with the given configuration.
|
||||
func NewChartPull(cfg *Configuration) *ChartPull {
|
||||
return &ChartPull{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart pull operation
|
||||
func (a *ChartPull) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.cfg.RegistryClient.PullChartToCache(r)
|
||||
}
|
||||
44
vendor/helm.sh/helm/v3/pkg/action/chart_push.go
vendored
Normal file
44
vendor/helm.sh/helm/v3/pkg/action/chart_push.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartPush performs a chart push operation.
|
||||
type ChartPush struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartPush creates a new ChartPush object with the given configuration.
|
||||
func NewChartPush(cfg *Configuration) *ChartPush {
|
||||
return &ChartPush{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart push operation
|
||||
func (a *ChartPush) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.cfg.RegistryClient.PushChart(r)
|
||||
}
|
||||
44
vendor/helm.sh/helm/v3/pkg/action/chart_remove.go
vendored
Normal file
44
vendor/helm.sh/helm/v3/pkg/action/chart_remove.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartRemove performs a chart remove operation.
|
||||
type ChartRemove struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartRemove creates a new ChartRemove object with the given configuration.
|
||||
func NewChartRemove(cfg *Configuration) *ChartRemove {
|
||||
return &ChartRemove{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart remove operation
|
||||
func (a *ChartRemove) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.cfg.RegistryClient.RemoveChart(r)
|
||||
}
|
||||
51
vendor/helm.sh/helm/v3/pkg/action/chart_save.go
vendored
Normal file
51
vendor/helm.sh/helm/v3/pkg/action/chart_save.go
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// ChartSave performs a chart save operation.
|
||||
type ChartSave struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartSave creates a new ChartSave object with the given configuration.
|
||||
func NewChartSave(cfg *Configuration) *ChartSave {
|
||||
return &ChartSave{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart save operation
|
||||
func (a *ChartSave) Run(out io.Writer, ch *chart.Chart, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no tag is present, use the chart version
|
||||
if r.Tag == "" {
|
||||
r.Tag = ch.Metadata.Version
|
||||
}
|
||||
|
||||
return a.cfg.RegistryClient.SaveChart(ch, r)
|
||||
}
|
||||
227
vendor/helm.sh/helm/v3/pkg/action/dependency.go
vendored
Normal file
227
vendor/helm.sh/helm/v3/pkg/action/dependency.go
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
|
||||
// NewDependency creates a new Dependency object with the given configuration.
|
||||
func NewDependency() *Dependency {
|
||||
return &Dependency{}
|
||||
}
|
||||
|
||||
// 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 = 80
|
||||
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 Charts.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
vendor/helm.sh/helm/v3/pkg/action/doc.go
vendored
Normal file
22
vendor/helm.sh/helm/v3/pkg/action/doc.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
47
vendor/helm.sh/helm/v3/pkg/action/get.go
vendored
Normal file
47
vendor/helm.sh/helm/v3/pkg/action/get.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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)
|
||||
}
|
||||
60
vendor/helm.sh/helm/v3/pkg/action/get_values.go
vendored
Normal file
60
vendor/helm.sh/helm/v3/pkg/action/get_values.go
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
58
vendor/helm.sh/helm/v3/pkg/action/history.go
vendored
Normal file
58
vendor/helm.sh/helm/v3/pkg/action/history.go
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
151
vendor/helm.sh/helm/v3/pkg/action/hooks.go
vendored
Normal file
151
vendor/helm.sh/helm/v3/pkg/action/hooks.go
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
720
vendor/helm.sh/helm/v3/pkg/action/install.go
vendored
Normal file
720
vendor/helm.sh/helm/v3/pkg/action/install.go
vendored
Normal file
@@ -0,0 +1,720 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"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/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"
|
||||
)
|
||||
|
||||
// releaseNameMaxLen is the maximum length of a release name.
|
||||
//
|
||||
// As of Kubernetes 1.4, the max limit on a name is 63 chars. We reserve 10 for
|
||||
// charts to add data. Effectively, that gives us 53 chars.
|
||||
// See https://github.com/helm/helm/issues/1528
|
||||
const releaseNameMaxLen = 53
|
||||
|
||||
// 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/<ReleaseName>
|
||||
UseReleaseName bool
|
||||
PostRenderer postrender.PostRenderer
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewInstall creates a new Install object with the given configuration.
|
||||
func NewInstall(cfg *Configuration) *Install {
|
||||
return &Install{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
if err := chartutil.ProcessDependencies(chrt, vals); 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
|
||||
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
|
||||
}
|
||||
|
||||
// pre-install hooks
|
||||
if !i.DisableHooks {
|
||||
if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil {
|
||||
return i.failRelease(rel, fmt.Errorf("failed pre-install: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return i.failRelease(rel, err)
|
||||
}
|
||||
} else if len(resources) > 0 {
|
||||
if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil {
|
||||
return i.failRelease(rel, err)
|
||||
}
|
||||
}
|
||||
|
||||
if i.Wait {
|
||||
if i.WaitForJobs {
|
||||
if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil {
|
||||
return i.failRelease(rel, err)
|
||||
}
|
||||
} else {
|
||||
if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil {
|
||||
return i.failRelease(rel, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !i.DisableHooks {
|
||||
if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil {
|
||||
return i.failRelease(rel, fmt.Errorf("failed post-install: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
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 start == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
if len(start) > releaseNameMaxLen {
|
||||
return errors.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen)
|
||||
}
|
||||
|
||||
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 <data> to <output-dir>/<name>. <append> 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) {
|
||||
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,
|
||||
}
|
||||
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 {
|
||||
lname, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return filename, err
|
||||
}
|
||||
return lname, nil
|
||||
} else if settings.Debug {
|
||||
return filename, err
|
||||
}
|
||||
|
||||
atVersion := ""
|
||||
if version != "" {
|
||||
atVersion = fmt.Sprintf(" at version %q", version)
|
||||
}
|
||||
return filename, errors.Errorf("failed to download %q%s (hint: running `helm repo update` may help)", name, atVersion)
|
||||
}
|
||||
197
vendor/helm.sh/helm/v3/pkg/action/lazyclient.go
vendored
Normal file
197
vendor/helm.sh/helm/v3/pkg/action/lazyclient.go
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
118
vendor/helm.sh/helm/v3/pkg/action/lint.go
vendored
Normal file
118
vendor/helm.sh/helm/v3/pkg/action/lint.go
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 := ioutil.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
|
||||
}
|
||||
323
vendor/helm.sh/helm/v3/pkg/action/list.go
vendored
Normal file
323
vendor/helm.sh/helm/v3/pkg/action/list.go
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
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
|
||||
}
|
||||
182
vendor/helm.sh/helm/v3/pkg/action/package.go
vendored
Normal file
182
vendor/helm.sh/helm/v3/pkg/action/package.go
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
169
vendor/helm.sh/helm/v3/pkg/action/pull.go
vendored
Normal file
169
vendor/helm.sh/helm/v3/pkg/action/pull.go
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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/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),
|
||||
},
|
||||
RepositoryConfig: p.Settings.RepositoryConfig,
|
||||
RepositoryCache: p.Settings.RepositoryCache,
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chartRef, "oci://") {
|
||||
if p.Version == "" {
|
||||
return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
|
||||
}
|
||||
|
||||
c.Options = append(c.Options,
|
||||
getter.WithRegistryClient(p.cfg.RegistryClient),
|
||||
getter.WithTagName(p.Version))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
38
vendor/helm.sh/helm/v3/pkg/action/registry_login.go
vendored
Normal file
38
vendor/helm.sh/helm/v3/pkg/action/registry_login.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
)
|
||||
|
||||
// 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, username, password, insecure)
|
||||
}
|
||||
38
vendor/helm.sh/helm/v3/pkg/action/registry_logout.go
vendored
Normal file
38
vendor/helm.sh/helm/v3/pkg/action/registry_logout.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
138
vendor/helm.sh/helm/v3/pkg/action/release_testing.go
vendored
Normal file
138
vendor/helm.sh/helm/v3/pkg/action/release_testing.go
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
46
vendor/helm.sh/helm/v3/pkg/action/resource_policy.go
vendored
Normal file
46
vendor/helm.sh/helm/v3/pkg/action/resource_policy.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
241
vendor/helm.sh/helm/v3/pkg/action/rollback.go
vendored
Normal file
241
vendor/helm.sh/helm/v3/pkg/action/rollback.go
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
130
vendor/helm.sh/helm/v3/pkg/action/show.go
vendored
Normal file
130
vendor/helm.sh/helm/v3/pkg/action/show.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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.
|
||||
func NewShow(output ShowOutputFormat) *Show {
|
||||
return &Show{
|
||||
OutputFormat: output,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if s.OutputFormat == ShowAll {
|
||||
fmt.Fprintln(&out, "---")
|
||||
}
|
||||
readme := findReadme(s.chart.Files)
|
||||
if readme == nil {
|
||||
return out.String(), nil
|
||||
}
|
||||
fmt.Fprintf(&out, "%s\n", readme.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
|
||||
}
|
||||
51
vendor/helm.sh/helm/v3/pkg/action/status.go
vendored
Normal file
51
vendor/helm.sh/helm/v3/pkg/action/status.go
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
212
vendor/helm.sh/helm/v3/pkg/action/uninstall.go
vendored
Normal file
212
vendor/helm.sh/helm/v3/pkg/action/uninstall.go
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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/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
|
||||
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)
|
||||
}
|
||||
|
||||
kept, errs := u.deleteRelease(rel)
|
||||
|
||||
if kept != "" {
|
||||
kept = "These resources were kept due to the resource policy:\n" + kept
|
||||
}
|
||||
res.Info = kept
|
||||
|
||||
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 manifests that were kept in the deletion process
|
||||
func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) {
|
||||
var errs []error
|
||||
caps, err := u.cfg.getCapabilities()
|
||||
if err != nil {
|
||||
return 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 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 "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
|
||||
}
|
||||
if len(resources) > 0 {
|
||||
_, errs = u.cfg.KubeClient.Delete(resources)
|
||||
}
|
||||
return kept, errs
|
||||
}
|
||||
509
vendor/helm.sh/helm/v3/pkg/action/upgrade.go
vendored
Normal file
509
vendor/helm.sh/helm/v3/pkg/action/upgrade.go
vendored
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
"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
|
||||
}
|
||||
|
||||
// NewUpgrade creates a new Upgrade object with the given configuration.
|
||||
func NewUpgrade(cfg *Configuration) *Upgrade {
|
||||
return &Upgrade{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the upgrade on the given release.
|
||||
func (u *Upgrade) Run(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(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(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
|
||||
}
|
||||
|
||||
// pre-upgrade hooks
|
||||
if !u.DisableHooks {
|
||||
if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil {
|
||||
return u.failRelease(upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err))
|
||||
}
|
||||
} 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)
|
||||
return u.failRelease(upgradedRelease, results.Created, err)
|
||||
}
|
||||
|
||||
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 {
|
||||
if u.WaitForJobs {
|
||||
if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil {
|
||||
u.cfg.recordRelease(originalRelease)
|
||||
return u.failRelease(upgradedRelease, results.Created, err)
|
||||
}
|
||||
} else {
|
||||
if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
|
||||
u.cfg.recordRelease(originalRelease)
|
||||
return u.failRelease(upgradedRelease, results.Created, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// post-upgrade hooks
|
||||
if !u.DisableHooks {
|
||||
if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil {
|
||||
return u.failRelease(upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
return upgradedRelease, 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)
|
||||
}
|
||||
184
vendor/helm.sh/helm/v3/pkg/action/validate.go
vendored
Normal file
184
vendor/helm.sh/helm/v3/pkg/action/validate.go
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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.Wrap(err, "could not get information about the resource")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
59
vendor/helm.sh/helm/v3/pkg/action/verify.go
vendored
Normal file
59
vendor/helm.sh/helm/v3/pkg/action/verify.go
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
17
vendor/helm.sh/helm/v3/pkg/chart/dependency.go
vendored
17
vendor/helm.sh/helm/v3/pkg/chart/dependency.go
vendored
@@ -49,6 +49,23 @@ type Dependency struct {
|
||||
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 {
|
||||
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.
|
||||
|
||||
77
vendor/helm.sh/helm/v3/pkg/chart/metadata.go
vendored
77
vendor/helm.sh/helm/v3/pkg/chart/metadata.go
vendored
@@ -15,6 +15,13 @@ 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
|
||||
@@ -25,15 +32,23 @@ type Maintainer struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks valid data and sanitizes string characters.
|
||||
func (m *Maintainer) Validate() error {
|
||||
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
|
||||
// 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
|
||||
// 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"`
|
||||
@@ -43,7 +58,7 @@ type Metadata struct {
|
||||
Maintainers []*Maintainer `json:"maintainers,omitempty"`
|
||||
// The URL to an icon file.
|
||||
Icon string `json:"icon,omitempty"`
|
||||
// The API Version of this chart.
|
||||
// The API Version of this chart. Required.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// The condition to check to enable chart
|
||||
Condition string `json:"condition,omitempty"`
|
||||
@@ -64,11 +79,28 @@ type Metadata struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks the metadata for known issues, returning an error if metadata is not correct
|
||||
// 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")
|
||||
}
|
||||
@@ -78,19 +110,26 @@ func (md *Metadata) Validate() error {
|
||||
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 := validateDependency(dependency); err != nil {
|
||||
if err := dependency.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO validate valid semver here?
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -102,12 +141,20 @@ func isValidChartType(in string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// validateDependency 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 validateDependency(dep *Dependency) error {
|
||||
if len(dep.Alias) > 0 && !aliasNameFormat.MatchString(dep.Alias) {
|
||||
return ValidationErrorf("dependency %q has disallowed characters in the alias", dep.Name)
|
||||
}
|
||||
return nil
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
@@ -58,6 +59,14 @@ type Capabilities struct {
|
||||
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
|
||||
@@ -73,6 +82,19 @@ func (kv *KubeVersion) String() string { return kv.Version }
|
||||
// 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
|
||||
|
||||
|
||||
29
vendor/helm.sh/helm/v3/pkg/chartutil/create.go
vendored
29
vendor/helm.sh/helm/v3/pkg/chartutil/create.go
vendored
@@ -147,12 +147,15 @@ service:
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
@@ -212,7 +215,14 @@ const defaultIgnore = `# Patterns to ignore when building packages.
|
||||
const defaultIngress = `{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "<CHARTNAME>.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
{{- 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
|
||||
@@ -227,6 +237,9 @@ metadata:
|
||||
{{- 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 }}
|
||||
@@ -244,12 +257,22 @@ spec:
|
||||
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 }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultDeployment = `apiVersion: apps/v1
|
||||
|
||||
@@ -61,7 +61,7 @@ const (
|
||||
// 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 reguar expression.
|
||||
// a regular expression.
|
||||
//
|
||||
// According to the Kubernetes help text, the regular expression it uses is:
|
||||
//
|
||||
@@ -96,6 +96,9 @@ func ValidateReleaseName(name string) error {
|
||||
//
|
||||
// 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
|
||||
|
||||
384
vendor/helm.sh/helm/v3/pkg/downloader/chart_downloader.go
vendored
Normal file
384
vendor/helm.sh/helm/v3/pkg/downloader/chart_downloader.go
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"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/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 == "oci" {
|
||||
name = fmt.Sprintf("%s-%s.tgz", name, version)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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, 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)
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
23
vendor/helm.sh/helm/v3/pkg/downloader/doc.go
vendored
Normal file
23
vendor/helm.sh/helm/v3/pkg/downloader/doc.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*Package 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
|
||||
898
vendor/helm.sh/helm/v3/pkg/downloader/manager.go
vendored
Normal file
898
vendor/helm.sh/helm/v3/pkg/downloader/manager.go
vendored
Normal file
@@ -0,0 +1,898 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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/experimental/registry"
|
||||
"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/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)
|
||||
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")
|
||||
|
||||
// Create 'charts' directory if it doesn't already exist.
|
||||
if fi, err := os.Stat(destPath); err != nil {
|
||||
if err := os.MkdirAll(destPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !fi.IsDir() {
|
||||
return errors.Errorf("%q is not a directory", destPath)
|
||||
}
|
||||
|
||||
if err := fs.RenameWithFallback(destPath, tmpPath); err != nil {
|
||||
return errors.Wrap(err, "unable to move current charts to tmp dir")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
chartPath := filepath.Join(tmpPath, dep.Name)
|
||||
ch, err := loader.LoadDir(chartPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load chart: %v", 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)
|
||||
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,
|
||||
Getters: m.Getters,
|
||||
Options: []getter.Option{
|
||||
getter.WithBasicAuth(username, password),
|
||||
getter.WithPassCredentialsAll(passcredentialsall),
|
||||
getter.WithInsecureSkipVerifyTLS(insecureskiptlsverify),
|
||||
getter.WithTLSClientConfig(certFile, keyFile, caFile),
|
||||
},
|
||||
}
|
||||
|
||||
version := ""
|
||||
if strings.HasPrefix(churl, "oci://") {
|
||||
if !resolver.FeatureGateOCI.IsEnabled() {
|
||||
return errors.Wrapf(resolver.FeatureGateOCI.Error(),
|
||||
"the repository %s is an OCI registry", 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))
|
||||
}
|
||||
|
||||
_, _, err = dl.DownloadTo(churl, version, destPath)
|
||||
if err != nil {
|
||||
saveError = errors.Wrapf(err, "could not download %s", churl)
|
||||
break
|
||||
}
|
||||
|
||||
churls[churl] = struct{}{}
|
||||
}
|
||||
|
||||
if saveError == nil {
|
||||
fmt.Fprintln(m.Out, "Deleting outdated charts")
|
||||
for _, dep := range deps {
|
||||
// Chart from local charts directory stays in place
|
||||
if dep.Repository != "" {
|
||||
if err := m.safeDeleteDep(dep.Name, tmpPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := move(tmpPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(tmpPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove %v", tmpPath)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(m.Out, "Save error occurred: ", saveError)
|
||||
fmt.Fprintln(m.Out, "Deleting newly downloaded charts, restoring pre-update state")
|
||||
for _, dep := range deps {
|
||||
if err := m.safeDeleteDep(dep.Name, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := os.RemoveAll(destPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove %v", destPath)
|
||||
}
|
||||
if err := fs.RenameWithFallback(tmpPath, destPath); err != nil {
|
||||
return errors.Wrap(err, "unable to move current charts to tmp dir")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// safeDeleteDep deletes any versions of the given dependency in the given directory.
|
||||
//
|
||||
// It does this by first matching the file name to an expected pattern, then loading
|
||||
// the file to verify that it is a chart with the same name as the given name.
|
||||
//
|
||||
// Because it requires tar file introspection, it is more intensive than a basic delete.
|
||||
//
|
||||
// This will only return errors that should stop processing entirely. Other errors
|
||||
// will emit log messages or be ignored.
|
||||
func (m *Manager) safeDeleteDep(name, dir string) error {
|
||||
files, err := filepath.Glob(filepath.Join(dir, name+"-*.tgz"))
|
||||
if err != nil {
|
||||
// Only for ErrBadPattern
|
||||
return err
|
||||
}
|
||||
for _, fname := range files {
|
||||
ch, err := loader.LoadFile(fname)
|
||||
if err != nil {
|
||||
fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err)
|
||||
continue
|
||||
}
|
||||
if ch.Name() != name {
|
||||
// This is not the file you are looking for.
|
||||
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://") || strings.HasPrefix(dd.Repository, "oci://") {
|
||||
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
|
||||
// When OCI is used there is no Helm repository
|
||||
if dd.Repository == "" || strings.HasPrefix(dd.Repository, "oci://") {
|
||||
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 strings.HasPrefix(dd.Repository, "oci://") {
|
||||
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 strings.HasPrefix(repoURL, "oci://") {
|
||||
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.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 charts/
|
||||
func tarFromLocalDir(chartpath, name, repo, version string) (string, error) {
|
||||
destPath := filepath.Join(chartpath, "charts")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// move files from tmppath to destpath
|
||||
func move(tmpPath, destPath string) error {
|
||||
files, _ := ioutil.ReadDir(tmpPath)
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
tmpfile := filepath.Join(tmpPath, filename)
|
||||
destfile := filepath.Join(destPath, filename)
|
||||
if err := fs.RenameWithFallback(tmpfile, destfile); err != nil {
|
||||
return errors.Wrap(err, "unable to move local charts to charts dir")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
23
vendor/helm.sh/helm/v3/pkg/engine/doc.go
vendored
Normal file
23
vendor/helm.sh/helm/v3/pkg/engine/doc.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*Package 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"
|
||||
392
vendor/helm.sh/helm/v3/pkg/engine/engine.go
vendored
Normal file
392
vendor/helm.sh/helm/v3/pkg/engine/engine.go
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 + `(.*)` + 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 <no value> 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 "<no value>" 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(), "<no value>", "")
|
||||
}
|
||||
|
||||
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) {
|
||||
next := map[string]interface{}{
|
||||
"Chart": c.Metadata,
|
||||
"Files": newFiles(c.Files),
|
||||
"Release": vals["Release"],
|
||||
"Capabilities": vals["Capabilities"],
|
||||
"Values": make(chartutil.Values),
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
160
vendor/helm.sh/helm/v3/pkg/engine/files.go
vendored
Normal file
160
vendor/helm.sh/helm/v3/pkg/engine/files.go
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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")
|
||||
}
|
||||
177
vendor/helm.sh/helm/v3/pkg/engine/funcs.go
vendored
Normal file
177
vendor/helm.sh/helm/v3/pkg/engine/funcs.go
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
124
vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go
vendored
Normal file
124
vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
20
vendor/helm.sh/helm/v3/pkg/gates/doc.go
vendored
Normal file
20
vendor/helm.sh/helm/v3/pkg/gates/doc.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
38
vendor/helm.sh/helm/v3/pkg/gates/gates.go
vendored
Normal file
38
vendor/helm.sh/helm/v3/pkg/gates/gates.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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())
|
||||
}
|
||||
7
vendor/helm.sh/helm/v3/pkg/getter/getter.go
vendored
7
vendor/helm.sh/helm/v3/pkg/getter/getter.go
vendored
@@ -38,6 +38,7 @@ type options struct {
|
||||
insecureSkipVerifyTLS bool
|
||||
username string
|
||||
password string
|
||||
passCredentialsAll bool
|
||||
userAgent string
|
||||
version string
|
||||
registryClient *registry.Client
|
||||
@@ -64,6 +65,12 @@ func WithBasicAuth(username, password string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
21
vendor/helm.sh/helm/v3/pkg/getter/httpgetter.go
vendored
21
vendor/helm.sh/helm/v3/pkg/getter/httpgetter.go
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -56,8 +57,24 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
|
||||
req.Header.Set("User-Agent", g.opts.userAgent)
|
||||
}
|
||||
|
||||
if g.opts.username != "" && g.opts.password != "" {
|
||||
req.SetBasicAuth(g.opts.username, g.opts.password)
|
||||
// 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 buf, errors.Wrap(err, "Unable to parse getter URL")
|
||||
}
|
||||
u2, err := url.Parse(href)
|
||||
if err != nil {
|
||||
return buf, 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()
|
||||
|
||||
15
vendor/helm.sh/helm/v3/pkg/getter/ocigetter.go
vendored
15
vendor/helm.sh/helm/v3/pkg/getter/ocigetter.go
vendored
@@ -58,10 +58,19 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
|
||||
}
|
||||
|
||||
// NewOCIGetter constructs a valid http/https client as a Getter
|
||||
func NewOCIGetter(options ...Option) (Getter, error) {
|
||||
var client OCIGetter
|
||||
func NewOCIGetter(ops ...Option) (Getter, error) {
|
||||
registryClient, err := registry.NewClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
client := OCIGetter{
|
||||
opts: options{
|
||||
registryClient: registryClient,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range ops {
|
||||
opt(&client.opts)
|
||||
}
|
||||
|
||||
|
||||
52
vendor/helm.sh/helm/v3/pkg/kube/client.go
vendored
52
vendor/helm.sh/helm/v3/pkg/kube/client.go
vendored
@@ -21,6 +21,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -55,6 +57,10 @@ 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
|
||||
@@ -100,7 +106,7 @@ func (c *Client) getKubeClient() (*kubernetes.Clientset, error) {
|
||||
return c.kubeClient, err
|
||||
}
|
||||
|
||||
// IsReachable tests connectivity to the cluster
|
||||
// IsReachable tests connectivity to the cluster.
|
||||
func (c *Client) IsReachable() error {
|
||||
client, err := c.getKubeClient()
|
||||
if err == genericclioptions.ErrEmptyConfig {
|
||||
@@ -126,18 +132,19 @@ func (c *Client) Create(resources ResourceList) (*Result, error) {
|
||||
return &Result{Created: resources}, nil
|
||||
}
|
||||
|
||||
// Wait up to the given timeout for the specified resources to be ready
|
||||
// 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: cs,
|
||||
c: checker,
|
||||
log: c.Log,
|
||||
timeout: timeout,
|
||||
}
|
||||
return w.waitForResources(resources, false)
|
||||
return w.waitForResources(resources)
|
||||
}
|
||||
|
||||
// WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs.
|
||||
@@ -146,12 +153,13 @@ func (c *Client) WaitWithJobs(resources ResourceList, timeout time.Duration) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checker := NewReadyChecker(cs, c.Log, PausedAsReady(true), CheckJobs(true))
|
||||
w := waiter{
|
||||
c: cs,
|
||||
c: checker,
|
||||
log: c.Log,
|
||||
timeout: timeout,
|
||||
}
|
||||
return w.waitForResources(resources, true)
|
||||
return w.waitForResources(resources)
|
||||
}
|
||||
|
||||
func (c *Client) namespace() string {
|
||||
@@ -204,7 +212,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
|
||||
return err
|
||||
}
|
||||
|
||||
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||
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")
|
||||
@@ -322,7 +330,7 @@ func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error {
|
||||
|
||||
// WatchUntilReady watches the resources given and waits until it is ready.
|
||||
//
|
||||
// This function is mainly for hook implementations. It watches for a resource to
|
||||
// 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
|
||||
@@ -357,6 +365,26 @@ func perform(infos ResourceList, fn func(*resource.Info) error) error {
|
||||
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
|
||||
@@ -375,7 +403,7 @@ func batchPerform(infos ResourceList, fn func(*resource.Info) error, errs chan<-
|
||||
}
|
||||
|
||||
func createResource(info *resource.Info) error {
|
||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||
obj, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).Create(info.Namespace, true, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -385,7 +413,7 @@ func createResource(info *resource.Info) error {
|
||||
func deleteResource(info *resource.Info) error {
|
||||
policy := metav1.DeletePropagationBackground
|
||||
opts := &metav1.DeleteOptions{PropagationPolicy: &policy}
|
||||
_, err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, opts)
|
||||
_, err := resource.NewHelper(info.Client, info.Mapping).WithFieldManager(getManagedFieldsManager()).DeleteWithOptions(info.Namespace, info.Name, opts)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -400,7 +428,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
|
||||
}
|
||||
|
||||
// Fetch the current object for the three way merge
|
||||
helper := resource.NewHelper(target.Client, target.Mapping)
|
||||
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)
|
||||
@@ -442,7 +470,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
|
||||
func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error {
|
||||
var (
|
||||
obj runtime.Object
|
||||
helper = resource.NewHelper(target.Client, target.Mapping)
|
||||
helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager())
|
||||
kind = target.Mapping.GroupVersionKind.Kind
|
||||
)
|
||||
|
||||
|
||||
107
vendor/helm.sh/helm/v3/pkg/kube/fake/fake.go
vendored
Normal file
107
vendor/helm.sh/helm/v3/pkg/kube/fake/fake.go
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Wait returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error {
|
||||
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.Wait(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)
|
||||
}
|
||||
105
vendor/helm.sh/helm/v3/pkg/kube/fake/printer.go
vendored
Normal file
105
vendor/helm.sh/helm/v3/pkg/kube/fake/printer.go
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
13
vendor/helm.sh/helm/v3/pkg/kube/interface.go
vendored
13
vendor/helm.sh/helm/v3/pkg/kube/interface.go
vendored
@@ -30,14 +30,19 @@ 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)
|
||||
|
||||
// Watch the resource in reader until it is "ready". This method
|
||||
// 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".
|
||||
@@ -49,9 +54,9 @@ type Interface interface {
|
||||
// if it doesn't exist.
|
||||
Update(original, target ResourceList, force bool) (*Result, error)
|
||||
|
||||
// Build creates a resource list from a Reader
|
||||
// Build creates a resource list from a Reader.
|
||||
//
|
||||
// reader must contain a YAML stream (one or more YAML documents separated
|
||||
// Reader must contain a YAML stream (one or more YAML documents separated
|
||||
// by "\n---\n")
|
||||
//
|
||||
// Validates against OpenAPI schema if validate is true.
|
||||
@@ -61,7 +66,7 @@ type Interface interface {
|
||||
// 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 checks whether the client is able to connect to the cluster.
|
||||
IsReachable() error
|
||||
}
|
||||
|
||||
|
||||
397
vendor/helm.sh/helm/v3/pkg/kube/ready.go
vendored
Normal file
397
vendor/helm.sh/helm/v3/pkg/kube/ready.go
vendored
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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.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 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
323
vendor/helm.sh/helm/v3/pkg/kube/wait.go
vendored
323
vendor/helm.sh/helm/v3/pkg/kube/wait.go
vendored
@@ -28,339 +28,36 @@ import (
|
||||
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/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
deploymentutil "helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
type waiter struct {
|
||||
c kubernetes.Interface
|
||||
c ReadyChecker
|
||||
timeout time.Duration
|
||||
log func(string, ...interface{})
|
||||
}
|
||||
|
||||
// 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, waitForJobsEnabled bool) error {
|
||||
func (w *waiter) waitForResources(created ResourceList) error {
|
||||
w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout)
|
||||
|
||||
return wait.Poll(2*time.Second, w.timeout, func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), w.timeout)
|
||||
defer cancel()
|
||||
|
||||
return wait.PollImmediateUntil(2*time.Second, func() (bool, error) {
|
||||
for _, v := range created {
|
||||
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 := w.c.CoreV1().Pods(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil || !w.isPodReady(pod) {
|
||||
return false, err
|
||||
}
|
||||
case *batchv1.Job:
|
||||
if waitForJobsEnabled {
|
||||
job, err := w.c.BatchV1().Jobs(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil || !w.jobReady(job) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
case *appsv1.Deployment, *appsv1beta1.Deployment, *appsv1beta2.Deployment, *extensionsv1beta1.Deployment:
|
||||
currentDeployment, err := w.c.AppsV1().Deployments(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// If paused deployment will never be ready
|
||||
if currentDeployment.Spec.Paused {
|
||||
continue
|
||||
}
|
||||
// Find RS associated with deployment
|
||||
newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
|
||||
if err != nil || newReplicaSet == nil {
|
||||
return false, err
|
||||
}
|
||||
if !w.deploymentReady(newReplicaSet, currentDeployment) {
|
||||
return false, nil
|
||||
}
|
||||
case *corev1.PersistentVolumeClaim:
|
||||
claim, err := w.c.CoreV1().PersistentVolumeClaims(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !w.volumeReady(claim) {
|
||||
return false, nil
|
||||
}
|
||||
case *corev1.Service:
|
||||
svc, err := w.c.CoreV1().Services(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !w.serviceReady(svc) {
|
||||
return false, nil
|
||||
}
|
||||
case *extensionsv1beta1.DaemonSet, *appsv1.DaemonSet, *appsv1beta2.DaemonSet:
|
||||
ds, err := w.c.AppsV1().DaemonSets(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !w.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 !w.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 !w.crdReady(*crd) {
|
||||
return false, nil
|
||||
}
|
||||
case *appsv1.StatefulSet, *appsv1beta1.StatefulSet, *appsv1beta2.StatefulSet:
|
||||
sts, err := w.c.AppsV1().StatefulSets(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !w.statefulSetReady(sts) {
|
||||
return false, nil
|
||||
}
|
||||
case *corev1.ReplicationController, *extensionsv1beta1.ReplicaSet, *appsv1beta2.ReplicaSet, *appsv1.ReplicaSet:
|
||||
ok, err = w.podsReadyForObject(v.Namespace, value)
|
||||
}
|
||||
if !ok || err != nil {
|
||||
ready, err := w.c.IsReady(ctx, v)
|
||||
if !ready || err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (w *waiter) podsReadyForObject(namespace string, obj runtime.Object) (bool, error) {
|
||||
pods, err := w.podsforObject(namespace, obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, pod := range pods {
|
||||
if !w.isPodReady(&pod) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (w *waiter) podsforObject(namespace string, obj runtime.Object) ([]corev1.Pod, error) {
|
||||
selector, err := SelectorsForObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := getPods(w.c, namespace, selector.String())
|
||||
return list, err
|
||||
}
|
||||
|
||||
// isPodReady returns true if a pod is ready; false otherwise.
|
||||
func (w *waiter) isPodReady(pod *corev1.Pod) bool {
|
||||
for _, c := range pod.Status.Conditions {
|
||||
if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
w.log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName())
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *waiter) jobReady(job *batchv1.Job) bool {
|
||||
if job.Status.Failed >= *job.Spec.BackoffLimit {
|
||||
w.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName())
|
||||
return false
|
||||
}
|
||||
if job.Status.Succeeded < *job.Spec.Completions {
|
||||
w.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *waiter) 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 == "" {
|
||||
w.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 {
|
||||
w.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 {
|
||||
w.log("Service does not have load balancer ingress IP address: %s/%s", s.GetNamespace(), s.GetName())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *waiter) volumeReady(v *corev1.PersistentVolumeClaim) bool {
|
||||
if v.Status.Phase != corev1.ClaimBound {
|
||||
w.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *waiter) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool {
|
||||
expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep)
|
||||
if !(rs.Status.ReadyReplicas >= expectedReady) {
|
||||
w.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 (w *waiter) 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 {
|
||||
w.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) {
|
||||
w.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 (w *waiter) 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 (w *waiter) 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 (w *waiter) 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 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 {
|
||||
w.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 {
|
||||
w.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
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getPods(client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) {
|
||||
list, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: selector,
|
||||
})
|
||||
return list.Items, err
|
||||
}, ctx.Done())
|
||||
}
|
||||
|
||||
// SelectorsForObject returns the pod label selector for a given object
|
||||
|
||||
37
vendor/helm.sh/helm/v3/pkg/lint/lint.go
vendored
Normal file
37
vendor/helm.sh/helm/v3/pkg/lint/lint.go
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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
|
||||
}
|
||||
210
vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go
vendored
Normal file
210
vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
82
vendor/helm.sh/helm/v3/pkg/lint/rules/dependencies.go
vendored
Normal file
82
vendor/helm.sh/helm/v3/pkg/lint/rules/dependencies.go
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
84
vendor/helm.sh/helm/v3/pkg/lint/rules/deprecations.go
vendored
Normal file
84
vendor/helm.sh/helm/v3/pkg/lint/rules/deprecations.go
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/deprecation"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
const (
|
||||
// This should be set in the Makefile based on the version of client-go being imported.
|
||||
// These constants will be overwritten with LDFLAGS
|
||||
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
|
||||
}
|
||||
if !deprecation.IsDeprecated(runtimeObject, k8sVersionMajor, k8sVersionMinor) {
|
||||
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
|
||||
}
|
||||
310
vendor/helm.sh/helm/v3/pkg/lint/rules/template.go
vendored
Normal file
310
vendor/helm.sh/helm/v3/pkg/lint/rules/template.go
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
87
vendor/helm.sh/helm/v3/pkg/lint/rules/values.go
vendored
Normal file
87
vendor/helm.sh/helm/v3/pkg/lint/rules/values.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
22
vendor/helm.sh/helm/v3/pkg/lint/support/doc.go
vendored
Normal file
22
vendor/helm.sh/helm/v3/pkg/lint/support/doc.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
76
vendor/helm.sh/helm/v3/pkg/lint/support/message.go
vendored
Normal file
76
vendor/helm.sh/helm/v3/pkg/lint/support/message.go
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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
|
||||
}
|
||||
16
vendor/helm.sh/helm/v3/pkg/plugin/plugin.go
vendored
16
vendor/helm.sh/helm/v3/pkg/plugin/plugin.go
vendored
@@ -23,6 +23,7 @@ import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
@@ -175,10 +176,25 @@ 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{}
|
||||
|
||||
|
||||
108
vendor/helm.sh/helm/v3/pkg/postrender/exec.go
vendored
Normal file
108
vendor/helm.sh/helm/v3/pkg/postrender/exec.go
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
|
||||
// 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) (PostRenderer, error) {
|
||||
fullPath, err := getFullPath(binaryPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &execRender{fullPath}, nil
|
||||
}
|
||||
|
||||
// Run the configured binary for the post render
|
||||
func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) {
|
||||
cmd := exec.Command(p.binaryPath)
|
||||
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)
|
||||
}
|
||||
29
vendor/helm.sh/helm/v3/pkg/postrender/postrender.go
vendored
Normal file
29
vendor/helm.sh/helm/v3/pkg/postrender/postrender.go
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package 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)
|
||||
}
|
||||
2
vendor/helm.sh/helm/v3/pkg/release/hook.go
vendored
2
vendor/helm.sh/helm/v3/pkg/release/hook.go
vendored
@@ -102,5 +102,5 @@ const (
|
||||
HookPhaseFailed HookPhase = "Failed"
|
||||
)
|
||||
|
||||
// Strng converts a hook phase to a printable string
|
||||
// String converts a hook phase to a printable string
|
||||
func (x HookPhase) String() string { return string(x) }
|
||||
|
||||
78
vendor/helm.sh/helm/v3/pkg/releaseutil/filter.go
vendored
Normal file
78
vendor/helm.sh/helm/v3/pkg/releaseutil/filter.go
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
})
|
||||
}
|
||||
156
vendor/helm.sh/helm/v3/pkg/releaseutil/kind_sorter.go
vendored
Normal file
156
vendor/helm.sh/helm/v3/pkg/releaseutil/kind_sorter.go
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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",
|
||||
"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",
|
||||
"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
|
||||
}
|
||||
72
vendor/helm.sh/helm/v3/pkg/releaseutil/manifest.go
vendored
Normal file
72
vendor/helm.sh/helm/v3/pkg/releaseutil/manifest.go
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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] }
|
||||
233
vendor/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go
vendored
Normal file
233
vendor/helm.sh/helm/v3/pkg/releaseutil/manifest_sorter.go
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
78
vendor/helm.sh/helm/v3/pkg/releaseutil/sorter.go
vendored
Normal file
78
vendor/helm.sh/helm/v3/pkg/releaseutil/sorter.go
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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})
|
||||
}
|
||||
27
vendor/helm.sh/helm/v3/pkg/repo/chartrepo.go
vendored
27
vendor/helm.sh/helm/v3/pkg/repo/chartrepo.go
vendored
@@ -48,6 +48,7 @@ type Entry struct {
|
||||
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
|
||||
@@ -82,6 +83,8 @@ func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository,
|
||||
// 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 {
|
||||
@@ -99,7 +102,7 @@ func (r *ChartRepository) Load() error {
|
||||
if strings.Contains(f.Name(), "-index.yaml") {
|
||||
i, err := LoadIndexFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
r.IndexFile = i
|
||||
} else if strings.HasSuffix(f.Name(), ".tgz") {
|
||||
@@ -127,6 +130,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
|
||||
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
|
||||
@@ -137,7 +141,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
indexFile, err := loadIndex(index)
|
||||
indexFile, err := loadIndex(index, r.Config.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -187,7 +191,9 @@ func (r *ChartRepository) generateIndex() error {
|
||||
}
|
||||
|
||||
if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) {
|
||||
r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
|
||||
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?
|
||||
}
|
||||
@@ -213,6 +219,15 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
|
||||
// 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)
|
||||
@@ -223,6 +238,7 @@ func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartV
|
||||
URL: repoURL,
|
||||
Username: username,
|
||||
Password: password,
|
||||
PassCredentialsAll: passCredentialsAll,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
@@ -270,7 +286,8 @@ func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartV
|
||||
// ResolveReferenceURL resolves refURL relative to baseURL.
|
||||
// If refURL is absolute, it simply returns refURL.
|
||||
func ResolveReferenceURL(baseURL, refURL string) (string, error) {
|
||||
parsedBaseURL, err := url.Parse(baseURL)
|
||||
// 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)
|
||||
}
|
||||
@@ -280,8 +297,6 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) {
|
||||
return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
|
||||
}
|
||||
|
||||
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
|
||||
parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
|
||||
return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
|
||||
}
|
||||
|
||||
|
||||
56
vendor/helm.sh/helm/v3/pkg/repo/index.go
vendored
56
vendor/helm.sh/helm/v3/pkg/repo/index.go
vendored
@@ -19,6 +19,7 @@ package repo
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -105,16 +106,27 @@ func LoadIndexFile(path string) (*IndexFile, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loadIndex(b)
|
||||
i, err := loadIndex(b, path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error loading %s", path)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Add adds a file to the index
|
||||
// MustAdd adds a file to the index
|
||||
// This can leave the index in an unsorted state
|
||||
func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) {
|
||||
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 != "" {
|
||||
var err error
|
||||
_, file := filepath.Split(filename)
|
||||
var err error
|
||||
u, err = urlutil.URLJoin(baseURL, file)
|
||||
if err != nil {
|
||||
u = path.Join(baseURL, file)
|
||||
@@ -126,10 +138,17 @@ func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) {
|
||||
Digest: digest,
|
||||
Created: time.Now(),
|
||||
}
|
||||
if ee, ok := i.Entries[md.Name]; !ok {
|
||||
i.Entries[md.Name] = ChartVersions{cr}
|
||||
} else {
|
||||
i.Entries[md.Name] = append(ee, cr)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +267,7 @@ type ChartVersion struct {
|
||||
// YAML parser enabled, this field must be present.
|
||||
TillerVersionDeprecated string `json:"tillerVersion,omitempty"`
|
||||
|
||||
// URLDeprecated is deprectaed in Helm 3, superseded by URLs. It is ignored. However,
|
||||
// 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"`
|
||||
}
|
||||
@@ -294,19 +313,34 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
|
||||
if err != nil {
|
||||
return index, err
|
||||
}
|
||||
index.Add(c.Metadata, fname, parentURL, hash)
|
||||
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) (*IndexFile, error) {
|
||||
func loadIndex(data []byte, source string) (*IndexFile, error) {
|
||||
i := &IndexFile{}
|
||||
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
|
||||
|
||||
257
vendor/helm.sh/helm/v3/pkg/storage/driver/cfgmaps.go
vendored
Normal file
257
vendor/helm.sh/helm/v3/pkg/storage/driver/cfgmaps.go
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
105
vendor/helm.sh/helm/v3/pkg/storage/driver/driver.go
vendored
Normal file
105
vendor/helm.sh/helm/v3/pkg/storage/driver/driver.go
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
48
vendor/helm.sh/helm/v3/pkg/storage/driver/labels.go
vendored
Normal file
48
vendor/helm.sh/helm/v3/pkg/storage/driver/labels.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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)
|
||||
}
|
||||
}
|
||||
240
vendor/helm.sh/helm/v3/pkg/storage/driver/memory.go
vendored
Normal file
240
vendor/helm.sh/helm/v3/pkg/storage/driver/memory.go
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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() }
|
||||
124
vendor/helm.sh/helm/v3/pkg/storage/driver/records.go
vendored
Normal file
124
vendor/helm.sh/helm/v3/pkg/storage/driver/records.go
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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}
|
||||
}
|
||||
250
vendor/helm.sh/helm/v3/pkg/storage/driver/secrets.go
vendored
Normal file
250
vendor/helm.sh/helm/v3/pkg/storage/driver/secrets.go
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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:
|
||||
// <helm_domain>/<helm_object>.v<helm_object_version>
|
||||
// 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
|
||||
}
|
||||
496
vendor/helm.sh/helm/v3/pkg/storage/driver/sql.go
vendored
Normal file
496
vendor/helm.sh/helm/v3/pkg/storage/driver/sql.go
vendored
Normal file
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
}
|
||||
85
vendor/helm.sh/helm/v3/pkg/storage/driver/util.go
vendored
Normal file
85
vendor/helm.sh/helm/v3/pkg/storage/driver/util.go
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 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
|
||||
}
|
||||
266
vendor/helm.sh/helm/v3/pkg/storage/storage.go
vendored
Normal file
266
vendor/helm.sh/helm/v3/pkg/storage/storage.go
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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 failed to store the
|
||||
// release, or a release with identical an 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 {
|
||||
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:```<helm_storage_type>.<release_name>.v<release_version>```.
|
||||
// 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{}) {},
|
||||
}
|
||||
}
|
||||
32
vendor/helm.sh/helm/v3/pkg/strvals/doc.go
vendored
Normal file
32
vendor/helm.sh/helm/v3/pkg/strvals/doc.go
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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
|
||||
446
vendor/helm.sh/helm/v3/pkg/strvals/parser.go
vendored
Normal file
446
vendor/helm.sh/helm/v3/pkg/strvals/parser.go
vendored
Normal file
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT 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"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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")
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 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 == '=':
|
||||
//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 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 == '=':
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user