github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/cmd/helm/install.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  	"context"
    21  	"fmt"
    22  	"io"
    23  	"log"
    24  	"os"
    25  	"os/signal"
    26  	"syscall"
    27  	"time"
    28  
    29  	"github.com/pkg/errors"
    30  	"github.com/spf13/cobra"
    31  	"github.com/spf13/pflag"
    32  
    33  	"github.com/stefanmcshane/helm/cmd/helm/require"
    34  	"github.com/stefanmcshane/helm/pkg/action"
    35  	"github.com/stefanmcshane/helm/pkg/chart"
    36  	"github.com/stefanmcshane/helm/pkg/chart/loader"
    37  	"github.com/stefanmcshane/helm/pkg/cli/output"
    38  	"github.com/stefanmcshane/helm/pkg/cli/values"
    39  	"github.com/stefanmcshane/helm/pkg/downloader"
    40  	"github.com/stefanmcshane/helm/pkg/getter"
    41  	"github.com/stefanmcshane/helm/pkg/release"
    42  )
    43  
    44  const installDesc = `
    45  This command installs a chart archive.
    46  
    47  The install argument must be a chart reference, a path to a packaged chart,
    48  a path to an unpacked chart directory or a URL.
    49  
    50  To override values in a chart, use either the '--values' flag and pass in a file
    51  or use the '--set' flag and pass configuration from the command line, to force
    52  a string value use '--set-string'. You can use '--set-file' to set individual
    53  values from a file when the value itself is too long for the command line
    54  or is dynamically generated. You can also use '--set-json' to set json values
    55  (scalars/objects/arrays) from the command line.
    56  
    57      $ helm install -f myvalues.yaml myredis ./redis
    58  
    59  or
    60  
    61      $ helm install --set name=prod myredis ./redis
    62  
    63  or
    64  
    65      $ helm install --set-string long_int=1234567890 myredis ./redis
    66  
    67  or
    68  
    69      $ helm install --set-file my_script=dothings.sh myredis ./redis
    70  
    71  or
    72  
    73      $ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis
    74  
    75  
    76  You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
    77  last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
    78  contained a key called 'Test', the value set in override.yaml would take precedence:
    79  
    80      $ helm install -f myvalues.yaml -f override.yaml  myredis ./redis
    81  
    82  You can specify the '--set' flag multiple times. The priority will be given to the
    83  last (right-most) set specified. For example, if both 'bar' and 'newbar' values are
    84  set for a key called 'foo', the 'newbar' value would take precedence:
    85  
    86      $ helm install --set foo=bar --set foo=newbar  myredis ./redis
    87  
    88  Similarly, in the following example 'foo' is set to '["four"]': 
    89  
    90      $ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis
    91  
    92  And in the following example, 'foo' is set to '{"key1":"value1","key2":"bar"}':
    93  
    94      $ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis
    95  
    96  To check the generated manifests of a release without installing the chart,
    97  the '--debug' and '--dry-run' flags can be combined.
    98  
    99  If --verify is set, the chart MUST have a provenance file, and the provenance
   100  file MUST pass all verification steps.
   101  
   102  There are six different ways you can express the chart you want to install:
   103  
   104  1. By chart reference: helm install mymaria example/mariadb
   105  2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz
   106  3. By path to an unpacked chart directory: helm install mynginx ./nginx
   107  4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz
   108  5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx
   109  6. By OCI registries: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx
   110  
   111  CHART REFERENCES
   112  
   113  A chart reference is a convenient way of referencing a chart in a chart repository.
   114  
   115  When you use a chart reference with a repo prefix ('example/mariadb'), Helm will look in the local
   116  configuration for a chart repository named 'example', and will then look for a
   117  chart in that repository whose name is 'mariadb'. It will install the latest stable version of that chart
   118  until you specify '--devel' flag to also include development version (alpha, beta, and release candidate releases), or
   119  supply a version number with the '--version' flag.
   120  
   121  To see the list of chart repositories, use 'helm repo list'. To search for
   122  charts in a repository, use 'helm search'.
   123  `
   124  
   125  func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
   126  	client := action.NewInstall(cfg)
   127  	valueOpts := &values.Options{}
   128  	var outfmt output.Format
   129  
   130  	cmd := &cobra.Command{
   131  		Use:   "install [NAME] [CHART]",
   132  		Short: "install a chart",
   133  		Long:  installDesc,
   134  		Args:  require.MinimumNArgs(1),
   135  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   136  			return compInstall(args, toComplete, client)
   137  		},
   138  		RunE: func(_ *cobra.Command, args []string) error {
   139  			rel, err := runInstall(args, client, valueOpts, out)
   140  			if err != nil {
   141  				return errors.Wrap(err, "INSTALLATION FAILED")
   142  			}
   143  
   144  			return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false})
   145  		},
   146  	}
   147  
   148  	addInstallFlags(cmd, cmd.Flags(), client, valueOpts)
   149  	bindOutputFlag(cmd, &outfmt)
   150  	bindPostRenderFlag(cmd, &client.PostRenderer)
   151  
   152  	return cmd
   153  }
   154  
   155  func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
   156  	f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
   157  	f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
   158  	f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
   159  	f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
   160  	f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
   161  	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")
   162  	f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
   163  	f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)")
   164  	f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
   165  	f.StringVar(&client.Description, "description", "", "add a custom description")
   166  	f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
   167  	f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
   168  	f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema")
   169  	f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
   170  	f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
   171  	f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
   172  	addValueOptionsFlags(f, valueOpts)
   173  	addChartPathOptionsFlags(f, &client.ChartPathOptions)
   174  
   175  	err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   176  		requiredArgs := 2
   177  		if client.GenerateName {
   178  			requiredArgs = 1
   179  		}
   180  		if len(args) != requiredArgs {
   181  			return nil, cobra.ShellCompDirectiveNoFileComp
   182  		}
   183  		return compVersionFlag(args[requiredArgs-1], toComplete)
   184  	})
   185  
   186  	if err != nil {
   187  		log.Fatal(err)
   188  	}
   189  }
   190  
   191  func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {
   192  	debug("Original chart version: %q", client.Version)
   193  	if client.Version == "" && client.Devel {
   194  		debug("setting version to >0.0.0-0")
   195  		client.Version = ">0.0.0-0"
   196  	}
   197  
   198  	name, chart, err := client.NameAndChart(args)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	client.ReleaseName = name
   203  
   204  	cp, err := client.ChartPathOptions.LocateChart(chart, settings)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	debug("CHART PATH: %s\n", cp)
   210  
   211  	p := getter.All(settings)
   212  	vals, err := valueOpts.MergeValues(p)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	// Check chart dependencies to make sure all are present in /charts
   218  	chartRequested, err := loader.Load(cp)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	if err := checkIfInstallable(chartRequested); err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	if chartRequested.Metadata.Deprecated {
   228  		warning("This chart is deprecated")
   229  	}
   230  
   231  	if req := chartRequested.Metadata.Dependencies; req != nil {
   232  		// If CheckDependencies returns an error, we have unfulfilled dependencies.
   233  		// As of Helm 2.4.0, this is treated as a stopping condition:
   234  		// https://github.com/helm/helm/issues/2209
   235  		if err := action.CheckDependencies(chartRequested, req); err != nil {
   236  			err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
   237  			if client.DependencyUpdate {
   238  				man := &downloader.Manager{
   239  					Out:              out,
   240  					ChartPath:        cp,
   241  					Keyring:          client.ChartPathOptions.Keyring,
   242  					SkipUpdate:       false,
   243  					Getters:          p,
   244  					RepositoryConfig: settings.RepositoryConfig,
   245  					RepositoryCache:  settings.RepositoryCache,
   246  					Debug:            settings.Debug,
   247  				}
   248  				if err := man.Update(); err != nil {
   249  					return nil, err
   250  				}
   251  				// Reload the chart with the updated Chart.lock file.
   252  				if chartRequested, err = loader.Load(cp); err != nil {
   253  					return nil, errors.Wrap(err, "failed reloading chart after repo update")
   254  				}
   255  			} else {
   256  				return nil, err
   257  			}
   258  		}
   259  	}
   260  
   261  	client.Namespace = settings.Namespace()
   262  
   263  	// Create context and prepare the handle of SIGTERM
   264  	ctx := context.Background()
   265  	ctx, cancel := context.WithCancel(ctx)
   266  
   267  	// Set up channel on which to send signal notifications.
   268  	// We must use a buffered channel or risk missing the signal
   269  	// if we're not ready to receive when the signal is sent.
   270  	cSignal := make(chan os.Signal, 2)
   271  	signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
   272  	go func() {
   273  		<-cSignal
   274  		fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0])
   275  		cancel()
   276  	}()
   277  
   278  	return client.RunWithContext(ctx, chartRequested, vals)
   279  }
   280  
   281  // checkIfInstallable validates if a chart can be installed
   282  //
   283  // Application chart type is only installable
   284  func checkIfInstallable(ch *chart.Chart) error {
   285  	switch ch.Metadata.Type {
   286  	case "", "application":
   287  		return nil
   288  	}
   289  	return errors.Errorf("%s charts are not installable", ch.Metadata.Type)
   290  }
   291  
   292  // Provide dynamic auto-completion for the install and template commands
   293  func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) {
   294  	requiredArgs := 1
   295  	if client.GenerateName {
   296  		requiredArgs = 0
   297  	}
   298  	if len(args) == requiredArgs {
   299  		return compListCharts(toComplete, true)
   300  	}
   301  	return nil, cobra.ShellCompDirectiveNoFileComp
   302  }