diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 4c36766ea..0962f36c6 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -141,10 +141,11 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)") f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") - 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.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.") + f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") + f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) } diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index d9cd997de..29b463c5b 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -155,7 +155,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") - 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.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") @@ -163,11 +163,12 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") - f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.") + f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") - f.IntVar(&client.MaxHistory, "history-max", 10, "limit the maximum number of revisions saved per release. Use 0 for no limit.") + f.IntVar(&client.MaxHistory, "history-max", 10, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") + f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") addChartPathOptionsFlags(f, &client.ChartPathOptions) addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &client.OutputFormat) diff --git a/pkg/action/install.go b/pkg/action/install.go index 8d2ea939d..c2dce1817 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -83,6 +83,7 @@ type Install struct { Atomic bool SkipCRDs bool OutputFormat string + SubNotes bool } // ChartPathOptions captures common options used for controlling chart paths @@ -204,7 +205,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.OutputDir, i.SubNotes) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() @@ -390,7 +391,7 @@ func (i *Install) replaceRelease(rel *release.Release) error { } // renderResources renders the templates in a chart -func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, outputDir string) ([]*release.Hook, *bytes.Buffer, string, error) { +func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, outputDir string, subNotes bool) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) @@ -415,17 +416,20 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values // 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. - notes := "" + var notesBuffer bytes.Buffer for k, v := range files { if strings.HasSuffix(k, notesFileSuffix) { - // Only apply the notes if it belongs to the parent chart - // Note: Do not use filePath.Join since it creates a path with \ which is not expected - if k == path.Join(ch.Name(), "templates", notesFileSuffix) { - notes = v + 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 diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 6ae891b42..48551d962 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "regexp" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -167,7 +168,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { is.Equal(rel.Info.Description, "Install complete") } -func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { +func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { // Regression: Make sure that the child's notes don't override the parent's is := assert.New(t) instAction := installAction(t) @@ -185,6 +186,28 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { is.Equal(rel.Info.Description, "Install complete") } +func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { + // Regression: Make sure that the child's notes don't override the parent's + is := assert.New(t) + instAction := installAction(t) + instAction.ReleaseName = "with-notes" + instAction.SubNotes = true + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) + is.Equal("with-notes", rel.Name) + is.NoError(err) + // test run can return as either 'parent\nchild' or 'child\nparent' + if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") { + t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes) + } + is.Equal(rel.Info.Description, "Install complete") +} + func TestInstallRelease_DryRun(t *testing.T) { is := assert.New(t) instAction := installAction(t) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 265e8df86..6046026dd 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -57,6 +57,7 @@ type Upgrade struct { Atomic bool CleanupOnFail bool OutputFormat string + SubNotes bool } // NewUpgrade creates a new Upgrade object with the given configuration. @@ -165,7 +166,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "") + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", u.SubNotes) if err != nil { return nil, nil, err }