diff --git a/api/internal/builtins/HelmChartInflationGenerator.go b/api/internal/builtins/HelmChartInflationGenerator.go index aed01081a2..4c898c23f2 100644 --- a/api/internal/builtins/HelmChartInflationGenerator.go +++ b/api/internal/builtins/HelmChartInflationGenerator.go @@ -270,14 +270,27 @@ func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err er if err = p.checkHelmVersion(); err != nil { return nil, err } - if path, exists := p.chartExistsLocally(); !exists { + chartPath, exists := p.chartExistsLocally() + if !exists { if p.Repo == "" { return nil, fmt.Errorf( - "no repo specified for pull, no chart found at '%s'", path) + "no repo specified for pull, no chart found at '%s'", + filepath.Join(p.absChartHome(), p.Name)) } if _, err := p.runHelmCommand(p.pullCommand()); err != nil { return nil, err } + chartPath, exists = p.chartExistsLocally() + if !exists { + return nil, fmt.Errorf( + "chart not found at %q after helm pull", + filepath.Join(p.absChartHome(), p.Name)) + } + } + if p.h.GeneralConfig().HelmConfig.DependencyUpdate { + if _, err := p.runHelmCommand([]string{"dependency", "update", chartPath}); err != nil { + return nil, err + } } if len(p.ValuesInline) > 0 { p.ValuesFile, err = p.createNewMergedValuesFile() diff --git a/api/krusty/helmchartinflationgenerator_test.go b/api/krusty/helmchartinflationgenerator_test.go index 2cc4b4acc0..28fcdfc595 100644 --- a/api/krusty/helmchartinflationgenerator_test.go +++ b/api/krusty/helmchartinflationgenerator_test.go @@ -5,6 +5,7 @@ package krusty_test import ( "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" @@ -1307,3 +1308,70 @@ func copyValuesFilesTestChartsIntoHarness(t *testing.T, th *kusttest_test.Harnes require.NoError(t, fs.MkdirAll(filepath.Join(thDir, "templates"))) require.NoError(t, copyutil.CopyDir(th.GetFSys(), chartDir, thDir)) } + +// TestHelmDependencyUpdate verifies HelmConfig.DependencyUpdate runs +// `helm dependency update` so local charts with dependencies can render +// (https://github.com/kubernetes-sigs/kustomize/issues/6119). +func TestHelmDependencyUpdate(t *testing.T) { + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t) + defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } + + chartBase := filepath.Join(th.GetRoot(), "charts") + wrapperDir := filepath.Join(chartBase, "wrapper") + subchartDir := filepath.Join(chartBase, "subchart") + require.NoError(t, th.GetFSys().MkdirAll(filepath.Join(wrapperDir, "templates"))) + require.NoError(t, th.GetFSys().MkdirAll(filepath.Join(subchartDir, "templates"))) + + th.WriteF(filepath.Join(wrapperDir, "Chart.yaml"), ` +apiVersion: v2 +name: wrapper +type: application +version: 0.1.0 +dependencies: + - name: subchart + version: 0.1.0 + repository: "file://../subchart" +`) + th.WriteF(filepath.Join(wrapperDir, "values.yaml"), ``) + + th.WriteF(filepath.Join(subchartDir, "Chart.yaml"), ` +apiVersion: v2 +name: subchart +type: application +version: 0.1.0 +`) + th.WriteF(filepath.Join(subchartDir, "values.yaml"), ``) + th.WriteF(filepath.Join(subchartDir, "templates", "cm.yaml"), ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-from-sub +data: + k: v +`) + + th.WriteK(th.GetRoot(), ` +helmGlobals: + chartHome: ./charts +helmCharts: + - name: wrapper + releaseName: wr +`) + + o := th.MakeOptionsPluginsEnabled() + err := th.RunWithErr(th.GetRoot(), o) + require.Error(t, err) + require.True(t, + strings.Contains(err.Error(), "dependency") || strings.Contains(err.Error(), "charts/"), + "expected dependency error, got: %v", err) + + o.PluginConfig.HelmConfig.DependencyUpdate = true + m := th.Run(th.GetRoot(), o) + yml, err := m.AsYaml() + require.NoError(t, err) + require.Contains(t, string(yml), "kind: ConfigMap") + require.Contains(t, string(yml), "from-sub") +} diff --git a/api/types/pluginconfig.go b/api/types/pluginconfig.go index 9721eb724a..4befc749da 100644 --- a/api/types/pluginconfig.go +++ b/api/types/pluginconfig.go @@ -9,6 +9,10 @@ type HelmConfig struct { ApiVersions []string KubeVersion string Debug bool + // DependencyUpdate, when true, runs "helm dependency update" on the chart + // directory before "helm template". Opt-in because it can be slow and uses + // the network for remote chart dependencies. + DependencyUpdate bool } // PluginConfig holds plugin configuration. diff --git a/kustomize/commands/build/build.go b/kustomize/commands/build/build.go index 46e812edb6..b2252c1a87 100644 --- a/kustomize/commands/build/build.go +++ b/kustomize/commands/build/build.go @@ -27,13 +27,14 @@ var theFlags struct { managedByLabel bool helm bool } - helmCommand string - helmApiVersions []string - helmKubeVersion string - helmDebug bool - loadRestrictor string - reorderOutput string - fnOptions types.FnPluginLoadingOptions + helmCommand string + helmApiVersions []string + helmKubeVersion string + helmDebug bool + helmDependencyUpdate bool + loadRestrictor string + reorderOutput string + fnOptions types.FnPluginLoadingOptions } type Help struct { @@ -146,6 +147,9 @@ func Validate(args []string) error { if err := validateFlagLoadRestrictor(); err != nil { return err } + if err := validateFlagHelmDependencyUpdate(); err != nil { + return err + } return validateFlagReorderOutput() } @@ -165,6 +169,7 @@ func HonorKustomizeFlags(kOpts *krusty.Options, flags *flag.FlagSet) *krusty.Opt kOpts.PluginConfig.HelmConfig.ApiVersions = theFlags.helmApiVersions kOpts.PluginConfig.HelmConfig.KubeVersion = theFlags.helmKubeVersion kOpts.PluginConfig.HelmConfig.Debug = theFlags.helmDebug + kOpts.PluginConfig.HelmConfig.DependencyUpdate = theFlags.helmDependencyUpdate kOpts.AddManagedbyLabel = isManagedByLabelEnabled() return kOpts } diff --git a/kustomize/commands/build/flagenablehelm.go b/kustomize/commands/build/flagenablehelm.go index d7d02d6029..6524d44f54 100644 --- a/kustomize/commands/build/flagenablehelm.go +++ b/kustomize/commands/build/flagenablehelm.go @@ -4,6 +4,8 @@ package build import ( + "fmt" + "github.com/spf13/pflag" ) @@ -36,4 +38,17 @@ func AddFlagEnableHelm(set *pflag.FlagSet) { "helm-debug", false, "Enable debug output from the Helm chart inflator generator.") + set.BoolVar( + &theFlags.helmDependencyUpdate, + "helm-dependency-update", + false, + "Run 'helm dependency update' on each chart before rendering. Requires --enable-helm or --enable-alpha-plugins.") +} + +func validateFlagHelmDependencyUpdate() error { + if theFlags.helmDependencyUpdate && !theFlags.enable.helm && !theFlags.enable.plugins { + return fmt.Errorf( + "--helm-dependency-update requires --enable-helm or --enable-alpha-plugins") + } + return nil } diff --git a/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go b/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go index 105bf9b94e..b500774cf7 100644 --- a/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go +++ b/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go @@ -278,14 +278,27 @@ func (p *plugin) Generate() (rm resmap.ResMap, err error) { if err = p.checkHelmVersion(); err != nil { return nil, err } - if path, exists := p.chartExistsLocally(); !exists { + chartPath, exists := p.chartExistsLocally() + if !exists { if p.Repo == "" { return nil, fmt.Errorf( - "no repo specified for pull, no chart found at '%s'", path) + "no repo specified for pull, no chart found at '%s'", + filepath.Join(p.absChartHome(), p.Name)) } if _, err := p.runHelmCommand(p.pullCommand()); err != nil { return nil, err } + chartPath, exists = p.chartExistsLocally() + if !exists { + return nil, fmt.Errorf( + "chart not found at %q after helm pull", + filepath.Join(p.absChartHome(), p.Name)) + } + } + if p.h.GeneralConfig().HelmConfig.DependencyUpdate { + if _, err := p.runHelmCommand([]string{"dependency", "update", chartPath}); err != nil { + return nil, err + } } if len(p.ValuesInline) > 0 { p.ValuesFile, err = p.createNewMergedValuesFile()