mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
11 KiB
321 lines
11 KiB
/*
|
|
Copyright The Helm Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"io"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"helm.sh/helm/cmd/helm/require"
|
|
"helm.sh/helm/pkg/action"
|
|
"helm.sh/helm/pkg/chart"
|
|
"helm.sh/helm/pkg/chart/loader"
|
|
"helm.sh/helm/pkg/cli"
|
|
"helm.sh/helm/pkg/downloader"
|
|
"helm.sh/helm/pkg/getter"
|
|
"helm.sh/helm/pkg/release"
|
|
"helm.sh/helm/pkg/strvals"
|
|
)
|
|
|
|
const installDesc = `
|
|
This command installs a chart archive.
|
|
|
|
The install argument must be a chart reference, a path to a packaged chart,
|
|
a path to an unpacked chart directory or a URL.
|
|
|
|
To override values in a chart, use either the '--values' flag and pass in a file
|
|
or use the '--set' flag and pass configuration from the command line, to force
|
|
a string value use '--set-string'.
|
|
|
|
$ helm install -f myvalues.yaml myredis ./redis
|
|
|
|
or
|
|
|
|
$ helm install --set name=prod myredis ./redis
|
|
|
|
or
|
|
|
|
$ helm install --set-string long_int=1234567890 myredis ./redis
|
|
|
|
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
|
|
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
|
|
contained a key called 'Test', the value set in override.yaml would take precedence:
|
|
|
|
$ helm install -f myvalues.yaml -f override.yaml myredis ./redis
|
|
|
|
You can specify the '--set' flag multiple times. The priority will be given to the
|
|
last (right-most) set specified. For example, if both 'bar' and 'newbar' values are
|
|
set for a key called 'foo', the 'newbar' value would take precedence:
|
|
|
|
$ helm install --set foo=bar --set foo=newbar myredis ./redis
|
|
|
|
|
|
To check the generated manifests of a release without installing the chart,
|
|
the '--debug' and '--dry-run' flags can be combined. This will still require a
|
|
round-trip to the Tiller server.
|
|
|
|
If --verify is set, the chart MUST have a provenance file, and the provenance
|
|
file MUST pass all verification steps.
|
|
|
|
There are five different ways you can express the chart you want to install:
|
|
|
|
1. By chart reference: helm install example/mariadb
|
|
2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz
|
|
3. By path to an unpacked chart directory: helm install ./nginx
|
|
4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz
|
|
5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx
|
|
|
|
CHART REFERENCES
|
|
|
|
A chart reference is a convenient way of reference a chart in a chart repository.
|
|
|
|
When you use a chart reference with a repo prefix ('example/mariadb'), Helm will look in the local
|
|
configuration for a chart repository named 'example', and will then look for a
|
|
chart in that repository whose name is 'mariadb'. It will install the latest
|
|
version of that chart unless you also supply a version number with the
|
|
'--version' flag.
|
|
|
|
To see the list of chart repositories, use 'helm repo list'. To search for
|
|
charts in a repository, use 'helm search'.
|
|
`
|
|
|
|
func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|
client := action.NewInstall(cfg)
|
|
valueOpts := &ValueOptions{}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "install [NAME] [CHART]",
|
|
Short: "install a chart",
|
|
Long: installDesc,
|
|
Args: require.MinimumNArgs(1),
|
|
RunE: func(_ *cobra.Command, args []string) error {
|
|
rel, err := runInstall(args, client, valueOpts, out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
action.PrintRelease(out, rel)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
addInstallFlags(cmd.Flags(), client, valueOpts)
|
|
|
|
return cmd
|
|
}
|
|
|
|
func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *ValueOptions) {
|
|
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
|
|
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
|
|
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
|
|
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
|
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment 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.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")
|
|
addValueOptionsFlags(f, valueOpts)
|
|
addChartPathOptionsFlags(f, &client.ChartPathOptions)
|
|
}
|
|
|
|
func addValueOptionsFlags(f *pflag.FlagSet, v *ValueOptions) {
|
|
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
|
|
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
|
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
|
}
|
|
|
|
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
|
|
f.StringVar(&c.Version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
|
|
f.BoolVar(&c.Verify, "verify", false, "verify the package before installing it")
|
|
f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
|
|
f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart")
|
|
f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart")
|
|
f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart")
|
|
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
|
|
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
|
|
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
|
}
|
|
|
|
func runInstall(args []string, client *action.Install, valueOpts *ValueOptions, out io.Writer) (*release.Release, error) {
|
|
debug("Original chart version: %q", client.Version)
|
|
if client.Version == "" && client.Devel {
|
|
debug("setting version to >0.0.0-0")
|
|
client.Version = ">0.0.0-0"
|
|
}
|
|
|
|
name, chart, err := client.NameAndChart(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
client.ReleaseName = name
|
|
|
|
cp, err := client.ChartPathOptions.LocateChart(chart, settings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
debug("CHART PATH: %s\n", cp)
|
|
|
|
vals, err := valueOpts.MergeValues(settings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check chart dependencies to make sure all are present in /charts
|
|
chartRequested, err := loader.Load(cp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
validInstallableChart, err := isChartInstallable(chartRequested)
|
|
if !validInstallableChart {
|
|
return nil, err
|
|
}
|
|
|
|
if req := chartRequested.Metadata.Dependencies; req != nil {
|
|
// If CheckDependencies returns an error, we have unfulfilled dependencies.
|
|
// As of Helm 2.4.0, this is treated as a stopping condition:
|
|
// https://github.com/helm/helm/issues/2209
|
|
if err := action.CheckDependencies(chartRequested, req); err != nil {
|
|
if client.DependencyUpdate {
|
|
man := &downloader.Manager{
|
|
Out: out,
|
|
ChartPath: cp,
|
|
HelmHome: settings.Home,
|
|
Keyring: client.ChartPathOptions.Keyring,
|
|
SkipUpdate: false,
|
|
Getters: getter.All(settings),
|
|
}
|
|
if err := man.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
client.Namespace = getNamespace()
|
|
return client.Run(chartRequested, vals)
|
|
}
|
|
|
|
// isChartInstallable validates if a chart can be installed
|
|
//
|
|
// Application chart type is only installable
|
|
func isChartInstallable(ch *chart.Chart) (bool, error) {
|
|
switch ch.Metadata.Type {
|
|
case "", "application":
|
|
return true, nil
|
|
}
|
|
return false, errors.Errorf("%s charts are not installable", ch.Metadata.Type)
|
|
}
|
|
|
|
type ValueOptions struct {
|
|
ValueFiles []string
|
|
StringValues []string
|
|
Values []string
|
|
}
|
|
|
|
// MergeValues merges values from files specified via -f/--values and
|
|
// directly via --set or --set-string, marshaling them to YAML
|
|
func (v *ValueOptions) MergeValues(settings cli.EnvSettings) (map[string]interface{}, error) {
|
|
base := map[string]interface{}{}
|
|
|
|
// User specified a values files via -f/--values
|
|
for _, filePath := range v.ValueFiles {
|
|
currentMap := map[string]interface{}{}
|
|
|
|
bytes, err := readFile(filePath, settings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := yaml.Unmarshal(bytes, ¤tMap); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse %s", filePath)
|
|
}
|
|
// Merge with the previous map
|
|
base = mergeMaps(base, currentMap)
|
|
}
|
|
|
|
// User specified a value via --set
|
|
for _, value := range v.Values {
|
|
if err := strvals.ParseInto(value, base); err != nil {
|
|
return nil, errors.Wrap(err, "failed parsing --set data")
|
|
}
|
|
}
|
|
|
|
// User specified a value via --set-string
|
|
for _, value := range v.StringValues {
|
|
if err := strvals.ParseIntoString(value, base); err != nil {
|
|
return nil, errors.Wrap(err, "failed parsing --set-string data")
|
|
}
|
|
}
|
|
|
|
return base, nil
|
|
}
|
|
|
|
func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
|
|
out := make(map[string]interface{}, len(a))
|
|
for k, v := range a {
|
|
out[k] = v
|
|
}
|
|
for k, v := range b {
|
|
if v, ok := v.(map[string]interface{}); ok {
|
|
if bv, ok := out[k]; ok {
|
|
if bv, ok := bv.(map[string]interface{}); ok {
|
|
out[k] = mergeMaps(bv, v)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// readFile load a file from stdin, the local directory, or a remote file with a url.
|
|
func readFile(filePath string, settings cli.EnvSettings) ([]byte, error) {
|
|
if strings.TrimSpace(filePath) == "-" {
|
|
return ioutil.ReadAll(os.Stdin)
|
|
}
|
|
u, _ := url.Parse(filePath)
|
|
p := getter.All(settings)
|
|
|
|
// FIXME: maybe someone handle other protocols like ftp.
|
|
getterConstructor, err := p.ByScheme(u.Scheme)
|
|
|
|
if err != nil {
|
|
return ioutil.ReadFile(filePath)
|
|
}
|
|
|
|
getter, err := getterConstructor(getter.WithURL(filePath))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
data, err := getter.Get(filePath)
|
|
return data.Bytes(), err
|
|
}
|