From 98426d6ad3253804a36e0068758075a7b7ef104f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 16 Aug 2019 16:26:09 -0600 Subject: [PATCH] feat: Add support for a crds/ directory Closes #5871 Signed-off-by: Matt Butcher --- cmd/helm/install.go | 1 + pkg/action/install.go | 34 +++++++++++++++++++++++++++ pkg/chart/chart.go | 19 +++++++++++++++ pkg/chart/chart_test.go | 51 +++++++++++++++++++++++++++++++++++++++++ pkg/chart/metadata.go | 1 + 5 files changed, 106 insertions(+) create mode 100644 pkg/chart/chart_test.go diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 509463ab1..ec5d76188 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -131,6 +131,7 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart") f.BoolVar(&client.Atomic, "atomic", false, "if set, installation process purges chart on fail. The --wait flag will be set automatically if --atomic is used") + f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present.") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) } diff --git a/pkg/action/install.go b/pkg/action/install.go index a03fe2462..e1ced1411 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -29,6 +29,7 @@ import ( "github.com/Masterminds/sprig" "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chartutil" @@ -80,8 +81,10 @@ type Install struct { NameTemplate string OutputDir string Atomic bool + SkipCRDs bool } +// ChartPathOptions captures common options used for controlling chart paths type ChartPathOptions struct { CaFile string // --ca-file CertFile string // --cert-file @@ -141,6 +144,35 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. } rel := i.createRelease(chrt, vals) + + // Pre-install anything in the crd/ directory + if crds := chrt.CRDs(); !i.SkipCRDs && len(crds) > 0 { + // We do these one at a time in the order they were read. + for _, obj := range crds { + // Read in the resources + res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.Data)) + if err != nil { + // We bail out immediately + return nil, errors.Wrapf(err, "failed to install CRD %s", obj.Name) + } + // 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.") + continue + } + // 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 i.failRelease(rel, err) + } + } + } + var manifestDoc *bytes.Buffer rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir) // Even for errors, attach this if available @@ -484,6 +516,7 @@ func (i *Install) NameAndChart(args []string) (string, string, error) { 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 @@ -501,6 +534,7 @@ func TemplateName(nameTemplate string) (string, error) { return b.String(), nil } +// CheckDependencies checks the dependencies for a chart. func CheckDependencies(ch *chart.Chart, reqs []*chart.Dependency) error { var missing []string diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 1a54c169e..3189071b4 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -15,6 +15,8 @@ limitations under the License. package chart +import "strings" + // APIVersionV1 is the API version number for version 1. const APIVersionV1 = "v1" @@ -100,6 +102,7 @@ func (ch *Chart) ChartFullPath() string { return ch.Name() } +// Validate validates the metadata. func (ch *Chart) Validate() error { return ch.Metadata.Validate() } @@ -111,3 +114,19 @@ func (ch *Chart) AppVersion() string { } return ch.Metadata.AppVersion } + +// CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. +func (ch *Chart) CRDs() []*File { + files := []*File{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") { + files = append(files, f) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + files = append(files, dep.CRDs()...) + } + return files +} diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go new file mode 100644 index 000000000..8b656d393 --- /dev/null +++ b/pkg/chart/chart_test.go @@ -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 chart + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCRDs(t *testing.T) { + chrt := Chart{ + Files: []*File{ + { + Name: "crds/foo.yaml", + Data: []byte("hello"), + }, + { + Name: "bar.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/foo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crdsfoo/bar/baz.yaml", + Data: []byte("hello"), + }, + }, + } + + is := assert.New(t) + crds := chrt.CRDs() + is.Equal(2, len(crds)) + is.Equal("crds/foo.yaml", crds[0].Name) + is.Equal("crds/foo/bar/baz.yaml", crds[1].Name) +} diff --git a/pkg/chart/metadata.go b/pkg/chart/metadata.go index 627ee19f3..96a3965b9 100644 --- a/pkg/chart/metadata.go +++ b/pkg/chart/metadata.go @@ -64,6 +64,7 @@ type Metadata struct { Type string `json:"type,omitempty"` } +// Validate checks the metadata for known issues, returning an error if metadata is not correct func (md *Metadata) Validate() error { if md == nil { return ValidationError("chart.metadata is required")