diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 944d89e04..bc70d46c0 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -92,25 +92,13 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } fileWritten := make(map[string]bool) - // deal hooks - if !client.DisableHooks { - for _, m := range rel.Hooks { - if matchFilePatterns(m.Path, showFiles) || (skipTests && isTestHook(m)) { - continue - } - err = writeManifest(outputDir, m.Path, m.Manifest, fileWritten, out) - if err != nil { - return err - } - } - } // deal crds if includeCrds && !client.SkipCRDs && rel.Chart != nil { for _, crd := range rel.Chart.CRDObjects() { - if !matchFilePatterns(crd.Name, showFiles) { + if len(showFiles) > 0 && !matchFilePatterns(crd.Name, showFiles) { continue } - err = writeManifest(outputDir, crd.Name, string(crd.File.Data), fileWritten, out) + err := writeManifest(outputDir, filepath.ToSlash(crd.Filename), string(crd.File.Data), fileWritten, true, out) if err != nil { return err } @@ -124,7 +112,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { // with globs or directory names. splitManifests := releaseutil.SplitManifests(manifests.String()) manifestsKeys := make([]string, 0, len(splitManifests)) - manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)") + manifestNameRegex := regexp.MustCompile("# Source: ([^/]+/)(.+)") for k := range splitManifests { manifestsKeys = append(manifestsKeys, k) } @@ -132,14 +120,27 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { for _, manifestKey := range manifestsKeys { manifest := splitManifests[manifestKey] submatch := manifestNameRegex.FindStringSubmatch(manifest) - var manifestName string - if len(submatch) > 1 { - manifestName = submatch[1] + var manifestName, manifestPath string + if len(submatch) > 2 { + manifestName = submatch[2] + manifestPath = submatch[1] + submatch[2] + } + if len(showFiles) > 0 && !matchFilePatterns(manifestName, showFiles) { + continue + } + err := writeManifest(outputDir, manifestPath, manifest, fileWritten, false, out) + if err != nil { + return err } - if matchFilePatterns(manifestName, showFiles) { - // remove text like # Source: XXX/XXX.yaml - manifest = manifestNameRegex.ReplaceAllString(manifest, "") - //err = writeManifest(outputDir, , manifest, fileWritten, out) todo + } + + // deal hooks + if !client.DisableHooks { + for _, m := range rel.Hooks { + if (skipTests && isTestHook(m)) || (len(showFiles) > 0 && !matchFilePatterns(m.Name, showFiles)) { + continue + } + err := writeManifest(outputDir, m.Path, m.Manifest, fileWritten, true, out) if err != nil { return err } @@ -175,8 +176,8 @@ func isTestHook(h *release.Hook) bool { } // writeToFile write manifests into output dir. -func writeToFile(outputDir string, name string, data string, append bool) error { - outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) +func writeToFile(outputDir string, name string, data string, append, withHeader bool) error { + outfileName := filepath.Join(outputDir, name) err := ensureDirectoryForFile(outfileName) if err != nil { @@ -190,7 +191,7 @@ func writeToFile(outputDir string, name string, data string, append bool) error defer f.Close() - _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) + err = writeStream(name, data, withHeader, f) if err != nil { return err @@ -218,9 +219,6 @@ func ensureDirectoryForFile(file string) error { } func matchFilePatterns(target string, sf []string) bool { - if len(sf) == 0 { - return true - } for _, pattern := range sf { pattern = filepath.ToSlash(pattern) matched, _ := filepath.Match(pattern, target) @@ -231,11 +229,12 @@ func matchFilePatterns(target string, sf []string) bool { return false } -func writeManifest(outputDir, path, manifest string, fileWritten map[string]bool, outStream io.Writer) error { +// writeManifest write manifest to stdout or file stream. use withHeader to control if write file header `# Source: XXXXX.yaml` or not. +func writeManifest(outputDir, path, manifest string, fileWritten map[string]bool, withHeader bool, outStream io.Writer) error { if outputDir == "" { - fmt.Fprintf(outStream, "---\n# Source: %s\n%s\n", path, manifest) + return writeStream(path, manifest, withHeader, outStream) } else { - err := writeToFile(outputDir, path, manifest, fileWritten[path]) + err := writeToFile(outputDir, path, manifest, fileWritten[path], withHeader) if err != nil { return err } @@ -243,3 +242,22 @@ func writeManifest(outputDir, path, manifest string, fileWritten map[string]bool } return nil } + +func writeStream(path, manifest string, withHeader bool, outStream io.Writer) error { + //write yaml delimiter + _, err := fmt.Fprintf(outStream, "---\n") + if err != nil { + return err + } + //write file header + if withHeader { + _, err = fmt.Fprintf(outStream, "# Source: %s\n", path) + if err != nil { + return err + } + } + + //write manifest content + _, err = fmt.Fprintf(outStream, "%s\n", manifest) + return err +} diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index d1f17fe98..87c5a6b9a 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -18,6 +18,8 @@ package main import ( "fmt" + "github.com/stretchr/testify/assert" + "os" "path/filepath" "testing" ) @@ -165,6 +167,138 @@ func TestTemplateVersionCompletion(t *testing.T) { runTestCmd(t, tests) } +func TestTemplateOutputDir(t *testing.T) { + is := assert.New(t) + dir := t.TempDir() + releaseName := "madra" + _, out, err := executeActionCommand(fmt.Sprintf("template %s '%s' --output-dir=%s", releaseName, chartPath, dir)) + if err != nil { + t.Logf("Output: %s", out) + t.Fatal(err) + } + var exitFileList = [][]string{ + {dir, "subchart", "templates", "service.yaml"}, + {dir, "subchart", "templates", "tests", "test-config.yaml"}, + {dir, "subchart", "templates", "tests", "test-nothing.yaml"}, + {dir, "subchart", "templates", "subdir", "role.yaml"}, + {dir, "subchart", "templates", "subdir", "rolebinding.yaml"}, + {dir, "subchart", "templates", "subdir", "serviceaccount.yaml"}, + {dir, "subchart", "charts", "subcharta", "templates", "service.yaml"}, + {dir, "subchart", "charts", "subchartb", "templates", "service.yaml"}, + } + for _, s := range exitFileList { + _, err = os.Stat(filepath.Join(s...)) + is.NoError(err) + } + notExistFileList := [][]string{ + {dir, "hello", "templates", "empty"}, + {dir, releaseName, "subchart", "templates", "service.yaml"}, + {dir, releaseName, "subchart", "templates", "tests", "test-config.yaml"}, + {dir, releaseName, "subchart", "templates", "tests", "test-nothing.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "role.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "rolebinding.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "serviceaccount.yaml"}, + {dir, releaseName, "subchart", "charts", "subcharta", "templates", "service.yaml"}, + {dir, releaseName, "subchart", "charts", "subchartb", "templates", "service.yaml"}, + } + for _, f := range notExistFileList { + _, err = os.Stat(filepath.Join(f...)) + is.True(os.IsNotExist(err)) + } +} + +func TestTemplateWithCRDsOutputDir(t *testing.T) { + is := assert.New(t) + dir := t.TempDir() + releaseName := "madra" + _, out, err := executeActionCommand(fmt.Sprintf("template %s '%s' --output-dir=%s --include-crds", releaseName, chartPath, dir)) + if err != nil { + t.Logf("Output: %s", out) + t.Fatal(err) + } + var exitFileList = [][]string{ + {dir, "subchart", "crds", "crdA.yaml"}, + } + for _, s := range exitFileList { + _, err = os.Stat(filepath.Join(s...)) + is.NoError(err) + } + notExistFileList := [][]string{ + {dir, "hello", "templates", "empty"}, + {dir, releaseName, "subchart", "templates", "service.yaml"}, + {dir, releaseName, "subchart", "templates", "tests", "test-config.yaml"}, + {dir, releaseName, "subchart", "templates", "tests", "test-nothing.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "role.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "rolebinding.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "serviceaccount.yaml"}, + {dir, releaseName, "subchart", "charts", "subcharta", "templates", "service.yaml"}, + {dir, releaseName, "subchart", "charts", "subchartb", "templates", "service.yaml"}, + } + for _, f := range notExistFileList { + _, err = os.Stat(filepath.Join(f...)) + is.True(os.IsNotExist(err)) + } +} + +func TestTemplateOutputDirWithReleaseName(t *testing.T) { + is := assert.New(t) + dir := t.TempDir() + releaseName := "madra" + _, out, err := executeActionCommand(fmt.Sprintf("template %s '%s' --output-dir=%s --release-name", releaseName, chartPath, dir)) + if err != nil { + t.Logf("Output: %s", out) + t.Fatal(err) + } + var exitFileList = [][]string{ + {dir, releaseName, "subchart", "templates", "service.yaml"}, + {dir, releaseName, "subchart", "templates", "tests", "test-config.yaml"}, + {dir, releaseName, "subchart", "templates", "tests", "test-nothing.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "role.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "rolebinding.yaml"}, + {dir, releaseName, "subchart", "templates", "subdir", "serviceaccount.yaml"}, + {dir, releaseName, "subchart", "charts", "subcharta", "templates", "service.yaml"}, + {dir, releaseName, "subchart", "charts", "subchartb", "templates", "service.yaml"}, + } + for _, s := range exitFileList { + _, err = os.Stat(filepath.Join(s...)) + is.NoError(err) + } + notExistFileList := [][]string{ + {dir, releaseName, "hello", "templates", "empty"}, + {dir, "subchart", "templates", "service.yaml"}, + {dir, "subchart", "templates", "tests", "test-config.yaml"}, + {dir, "subchart", "templates", "tests", "test-nothing.yaml"}, + {dir, "subchart", "templates", "subdir", "role.yaml"}, + {dir, "subchart", "templates", "subdir", "rolebinding.yaml"}, + {dir, "subchart", "templates", "subdir", "serviceaccount.yaml"}, + {dir, "subchart", "charts", "subcharta", "templates", "service.yaml"}, + {dir, "subchart", "charts", "subchartb", "templates", "service.yaml"}, + } + for _, f := range notExistFileList { + _, err = os.Stat(filepath.Join(f...)) + is.True(os.IsNotExist(err)) + } +} + +func TestTemplateOutputDirSkiptest(t *testing.T) { + is := assert.New(t) + dir := t.TempDir() + releaseName := "madra" + _, out, err := executeActionCommand(fmt.Sprintf("template %s '%s' --output-dir=%s --skip-tests", releaseName, chartPath, dir)) + if err != nil { + t.Logf("Output: %s", out) + t.Fatal(err) + } + notExistFileList := [][]string{ + {dir, "subchart", "templates", "tests", "test-config.yaml"}, + {dir, "subchart", "templates", "tests", "test-nothing.yaml"}, + } + for _, f := range notExistFileList { + _, err = os.Stat(filepath.Join(f...)) + is.True(os.IsNotExist(err)) + } +} + func TestTemplateFileCompletion(t *testing.T) { checkFileCompletion(t, "template", false) checkFileCompletion(t, "template --generate-name", true) diff --git a/cmd/helm/testdata/output/template-show-only-multiple.txt b/cmd/helm/testdata/output/template-show-only-multiple.txt index 1aac3081a..eb0727bae 100644 --- a/cmd/helm/testdata/output/template-show-only-multiple.txt +++ b/cmd/helm/testdata/output/template-show-only-multiple.txt @@ -1,38 +1,38 @@ --- -# Source: subchart/templates/service.yaml +# Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 kind: Service metadata: - name: subchart + name: subcharta labels: - helm.sh/chart: "subchart-0.1.0" - app.kubernetes.io/instance: "release-name" - kube-version/major: "1" - kube-version/minor: "20" - kube-version/version: "v1.20.0" + helm.sh/chart: "subcharta-0.1.0" spec: type: ClusterIP ports: - port: 80 targetPort: 80 protocol: TCP - name: nginx + name: apache selector: - app.kubernetes.io/name: subchart + app.kubernetes.io/name: subcharta --- -# Source: subchart/charts/subcharta/templates/service.yaml +# Source: subchart/templates/service.yaml apiVersion: v1 kind: Service metadata: - name: subcharta + name: subchart labels: - helm.sh/chart: "subcharta-0.1.0" + helm.sh/chart: "subchart-0.1.0" + app.kubernetes.io/instance: "release-name" + kube-version/major: "1" + kube-version/minor: "20" + kube-version/version: "v1.20.0" spec: type: ClusterIP ports: - port: 80 targetPort: 80 protocol: TCP - name: apache + name: nginx selector: - app.kubernetes.io/name: subcharta + app.kubernetes.io/name: subchart diff --git a/cmd/helm/testdata/output/template-with-crds.txt b/cmd/helm/testdata/output/template-with-crds.txt index dd58480c9..256fc7c3b 100644 --- a/cmd/helm/testdata/output/template-with-crds.txt +++ b/cmd/helm/testdata/output/template-with-crds.txt @@ -1,5 +1,5 @@ --- -# Source: crds/crdA.yaml +# Source: subchart/crds/crdA.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: diff --git a/pkg/action/install.go b/pkg/action/install.go index 230218685..9ceaf579c 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -59,8 +59,6 @@ import ( // 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 @@ -83,12 +81,14 @@ type Install struct { GenerateName bool NameTemplate string Description string + // OutputDir is deprecated because of template-related code is removed from install action. // Deprecated OutputDir string Atomic bool SkipCRDs bool SubNotes bool DisableOpenAPIValidation bool + // IncludeCRDs is deprecated because of template-related code is removed from install action. // Deprecated IncludeCRDs bool // KubeVersion allows specifying a custom kubernetes version to use and @@ -98,9 +98,10 @@ type Install struct { 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 + // Used by helm template to add the release as part of OutputDir path, UseReleaseName is deprecated because of template-related code is removed from install action. // OutputDir/ - UseReleaseName bool // Deprecated + //Deprecated + UseReleaseName bool PostRenderer postrender.PostRenderer // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex @@ -216,7 +217,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma } if i.ClientOnly { - // Add mock objects in here so it doesn't use Kube API server + // 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 { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 45e5a2670..8c0c73a9a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -20,8 +20,6 @@ import ( "context" "fmt" "io/ioutil" - "os" - "path/filepath" "regexp" "strings" "testing" @@ -30,7 +28,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "helm.sh/helm/v3/internal/test" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" kubefake "helm.sh/helm/v3/pkg/kube/fake" @@ -546,74 +543,6 @@ func TestNameTemplate(t *testing.T) { } } -func TestInstallReleaseOutputDir(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - - dir := t.TempDir() - - instAction.OutputDir = dir - - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) - is.NoError(err) - - test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") - - _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) - is.True(os.IsNotExist(err)) -} - -func TestInstallOutputDirWithReleaseName(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - - dir := t.TempDir() - - instAction.OutputDir = dir - instAction.UseReleaseName = true - instAction.ReleaseName = "madra" - - newDir := filepath.Join(dir, instAction.ReleaseName) - - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/hello")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac")) - is.NoError(err) - - test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) - is.True(os.IsNotExist(err)) -} - func TestNameAndChart(t *testing.T) { is := assert.New(t) instAction := installAction(t)