github.com/codefresh-io/kcfi@v0.0.0-20230301195427-c1578715cc46/cmd/kcfi/template.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"path/filepath"
    24  	"regexp"
    25  	"sort"
    26  	"strings"
    27  
    28  	"github.com/spf13/cobra"
    29  
    30  	"github.com/codefresh-io/kcfi/pkg/helm-internal/completion"
    31  	"helm.sh/helm/v3/cmd/helm/require"
    32  	"helm.sh/helm/v3/pkg/action"
    33  	"helm.sh/helm/v3/pkg/chartutil"
    34  	"helm.sh/helm/v3/pkg/cli/values"
    35  	"helm.sh/helm/v3/pkg/releaseutil"
    36  )
    37  
    38  const templateDesc = `
    39  Render chart templates locally and display the output.
    40  
    41  Any values that would normally be looked up or retrieved in-cluster will be
    42  faked locally. Additionally, none of the server-side testing of chart validity
    43  (e.g. whether an API is supported) is done.
    44  `
    45  
    46  func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
    47  	var validate bool
    48  	var includeCrds bool
    49  	client := action.NewInstall(cfg)
    50  	valueOpts := &values.Options{}
    51  	var extraAPIs []string
    52  	var showFiles []string
    53  
    54  	cmd := &cobra.Command{
    55  		Use:   "template [NAME] [CHART]",
    56  		Short: "locally render templates",
    57  		Long:  templateDesc,
    58  		Args:  require.MinimumNArgs(1),
    59  		RunE: func(_ *cobra.Command, args []string) error {
    60  			client.DryRun = true
    61  			client.ReleaseName = "RELEASE-NAME"
    62  			client.Replace = true // Skip the name check
    63  			client.ClientOnly = !validate
    64  			client.APIVersions = chartutil.VersionSet(extraAPIs)
    65  			client.IncludeCRDs = includeCrds
    66  			rel, err := runInstall(args, client, valueOpts, out)
    67  
    68  			if err != nil && !settings.Debug {
    69  				if rel != nil {
    70  					return fmt.Errorf("%w\n\nUse --debug flag to render out invalid YAML", err)
    71  				}
    72  				return err
    73  			}
    74  
    75  			// We ignore a potential error here because, when the --debug flag was specified,
    76  			// we always want to print the YAML, even if it is not valid. The error is still returned afterwards.
    77  			if rel != nil {
    78  				var manifests bytes.Buffer
    79  				fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
    80  
    81  				if !client.DisableHooks {
    82  					for _, m := range rel.Hooks {
    83  						fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
    84  					}
    85  				}
    86  
    87  				// if we have a list of files to render, then check that each of the
    88  				// provided files exists in the chart.
    89  				if len(showFiles) > 0 {
    90  					// This is necessary to ensure consistent manifest ordering when using --show-only
    91  					// with globs or directory names.
    92  					splitManifests := releaseutil.SplitManifests(manifests.String())
    93  					manifestsKeys := make([]string, 0, len(splitManifests))
    94  					for k := range splitManifests {
    95  						manifestsKeys = append(manifestsKeys, k)
    96  					}
    97  					sort.Sort(releaseutil.BySplitManifestsOrder(manifestsKeys))
    98  
    99  					manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)")
   100  					var manifestsToRender []string
   101  					for _, f := range showFiles {
   102  						missing := true
   103  						for _, manifestKey := range manifestsKeys {
   104  							manifest := splitManifests[manifestKey]
   105  							submatch := manifestNameRegex.FindStringSubmatch(manifest)
   106  							if len(submatch) == 0 {
   107  								continue
   108  							}
   109  							manifestName := submatch[1]
   110  							// manifest.Name is rendered using linux-style filepath separators on Windows as
   111  							// well as macOS/linux.
   112  							manifestPathSplit := strings.Split(manifestName, "/")
   113  							manifestPath := filepath.Join(manifestPathSplit...)
   114  
   115  							// if the filepath provided matches a manifest path in the
   116  							// chart, render that manifest
   117  							if matched, _ := filepath.Match(f, manifestPath); !matched {
   118  								continue
   119  							}
   120  							manifestsToRender = append(manifestsToRender, manifest)
   121  							missing = false
   122  						}
   123  						if missing {
   124  							return fmt.Errorf("could not find template %s in chart", f)
   125  						}
   126  					}
   127  					for _, m := range manifestsToRender {
   128  						fmt.Fprintf(out, "---\n%s\n", m)
   129  					}
   130  				} else {
   131  					fmt.Fprintf(out, "%s", manifests.String())
   132  				}
   133  			}
   134  
   135  			return err
   136  		},
   137  	}
   138  
   139  	// Function providing dynamic auto-completion
   140  	completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) {
   141  		return compInstall(args, toComplete, client)
   142  	})
   143  
   144  	f := cmd.Flags()
   145  	addInstallFlags(f, client, valueOpts)
   146  	f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates")
   147  	f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
   148  	f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install")
   149  	f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output")
   150  	f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
   151  	f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
   152  	f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
   153  	bindPostRenderFlag(cmd, &client.PostRenderer)
   154  
   155  	return cmd
   156  }