github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+incompatible/cmd/helm/install.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 29 "text/template" 30 31 "github.com/Masterminds/sprig" 32 "github.com/ghodss/yaml" 33 "github.com/spf13/cobra" 34 35 "k8s.io/helm/pkg/helm" 36 "k8s.io/helm/pkg/proto/hapi/release" 37 "k8s.io/helm/pkg/timeconv" 38 ) 39 40 const installDesc = ` 41 This command installs a chart archive. 42 43 The install argument must be either a relative path to a chart directory or the 44 name of a chart in the current working directory. 45 46 To override values in a chart, use either the '--values' flag and pass in a file 47 or use the '--set' flag and pass configuration from the command line. 48 49 $ helm install -f myvalues.yaml redis 50 51 or 52 53 $ helm install --set name=prod redis 54 55 To check the generated manifests of a release without installing the chart, 56 the '--debug' and '--dry-run' flags can be combined. This will still require a 57 round-trip to the Tiller server. 58 59 If --verify is set, the chart MUST have a provenance file, and the provenenace 60 fall MUST pass all verification steps. 61 ` 62 63 type installCmd struct { 64 name string 65 namespace string 66 valuesFile string 67 chartPath string 68 dryRun bool 69 disableHooks bool 70 replace bool 71 verify bool 72 keyring string 73 out io.Writer 74 client helm.Interface 75 values *values 76 nameTemplate string 77 } 78 79 func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { 80 inst := &installCmd{ 81 out: out, 82 client: c, 83 values: new(values), 84 } 85 86 cmd := &cobra.Command{ 87 Use: "install [CHART]", 88 Short: "install a chart archive", 89 Long: installDesc, 90 PersistentPreRunE: setupConnection, 91 RunE: func(cmd *cobra.Command, args []string) error { 92 if err := checkArgsLength(1, len(args), "chart name"); err != nil { 93 return err 94 } 95 cp, err := locateChartPath(args[0], inst.verify, inst.keyring) 96 if err != nil { 97 return err 98 } 99 inst.chartPath = cp 100 inst.client = ensureHelmClient(inst.client) 101 return inst.run() 102 }, 103 } 104 105 f := cmd.Flags() 106 f.StringVarP(&inst.valuesFile, "values", "f", "", "specify values in a YAML file") 107 f.StringVarP(&inst.name, "name", "n", "", "the release name. If unspecified, it will autogenerate one for you") 108 // TODO use kubeconfig default 109 f.StringVar(&inst.namespace, "namespace", "default", "the namespace to install the release into") 110 f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") 111 f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") 112 f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") 113 f.Var(inst.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2") 114 f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") 115 f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") 116 f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") 117 return cmd 118 } 119 120 func (i *installCmd) run() error { 121 if flagDebug { 122 fmt.Fprintf(i.out, "Chart path: %s\n", i.chartPath) 123 } 124 125 rawVals, err := i.vals() 126 if err != nil { 127 return err 128 } 129 130 // If template is specified, try to run the template. 131 if i.nameTemplate != "" { 132 i.name, err = generateName(i.nameTemplate) 133 if err != nil { 134 return err 135 } 136 // Print the final name so the user knows what the final name of the release is. 137 fmt.Printf("Final name: %s\n", i.name) 138 } 139 140 res, err := i.client.InstallRelease( 141 i.chartPath, 142 i.namespace, 143 helm.ValueOverrides(rawVals), 144 helm.ReleaseName(i.name), 145 helm.InstallDryRun(i.dryRun), 146 helm.InstallReuseName(i.replace), 147 helm.InstallDisableHooks(i.disableHooks)) 148 if err != nil { 149 return prettyError(err) 150 } 151 152 rel := res.GetRelease() 153 if rel == nil { 154 return nil 155 } 156 i.printRelease(rel) 157 158 // If this is a dry run, we can't display status. 159 if i.dryRun { 160 return nil 161 } 162 163 // Print the status like status command does 164 status, err := i.client.ReleaseStatus(rel.Name) 165 if err != nil { 166 return prettyError(err) 167 } 168 PrintStatus(i.out, status) 169 return nil 170 } 171 172 func (i *installCmd) vals() ([]byte, error) { 173 var buffer bytes.Buffer 174 175 // User specified a values file via -f/--values 176 if i.valuesFile != "" { 177 bytes, err := ioutil.ReadFile(i.valuesFile) 178 if err != nil { 179 return []byte{}, err 180 } 181 buffer.Write(bytes) 182 } 183 184 // User specified value pairs via --set 185 // These override any values in the specified file 186 if len(i.values.pairs) > 0 { 187 bytes, err := i.values.yaml() 188 if err != nil { 189 return []byte{}, err 190 } 191 buffer.Write(bytes) 192 } 193 194 return buffer.Bytes(), nil 195 } 196 197 // printRelease prints info about a release if the flagDebug is true. 198 func (i *installCmd) printRelease(rel *release.Release) { 199 if rel == nil { 200 return 201 } 202 // TODO: Switch to text/template like everything else. 203 if flagDebug { 204 fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) 205 fmt.Fprintf(i.out, "NAMESPACE: %s\n", rel.Namespace) 206 fmt.Fprintf(i.out, "INFO: %s %s\n", timeconv.String(rel.Info.LastDeployed), rel.Info.Status) 207 fmt.Fprintf(i.out, "CHART: %s %s\n", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version) 208 fmt.Fprintf(i.out, "MANIFEST: %s\n", rel.Manifest) 209 } else { 210 fmt.Fprintln(i.out, rel.Name) 211 } 212 } 213 214 // values represents the command-line value pairs 215 type values struct { 216 pairs map[string]interface{} 217 } 218 219 func (v *values) yaml() ([]byte, error) { 220 return yaml.Marshal(v.pairs) 221 } 222 223 func (v *values) String() string { 224 out, _ := v.yaml() 225 return string(out) 226 } 227 228 func (v *values) Type() string { 229 // Added to pflags.Value interface, but not documented there. 230 return "struct" 231 } 232 233 func (v *values) Set(data string) error { 234 v.pairs = map[string]interface{}{} 235 236 items := strings.Split(data, ",") 237 for _, item := range items { 238 n, val := splitPair(item) 239 names := strings.Split(n, ".") 240 ln := len(names) 241 current := &v.pairs 242 for i := 0; i < ln; i++ { 243 if i+1 == ln { 244 // We're at the last element. Set it. 245 (*current)[names[i]] = val 246 } else { 247 // 248 if e, ok := (*current)[names[i]]; !ok { 249 m := map[string]interface{}{} 250 (*current)[names[i]] = m 251 current = &m 252 } else if m, ok := e.(map[string]interface{}); ok { 253 current = &m 254 } 255 } 256 } 257 } 258 return nil 259 } 260 261 func splitPair(item string) (name string, value interface{}) { 262 pair := strings.SplitN(item, "=", 2) 263 if len(pair) == 1 { 264 return pair[0], true 265 } 266 return pair[0], pair[1] 267 } 268 269 // locateChartPath looks for a chart directory in known places, and returns either the full path or an error. 270 // 271 // This does not ensure that the chart is well-formed; only that the requested filename exists. 272 // 273 // Order of resolution: 274 // - current working directory 275 // - if path is absolute or begins with '.', error out here 276 // - chart repos in $HELM_HOME 277 // 278 // If 'verify' is true, this will attempt to also verify the chart. 279 func locateChartPath(name string, verify bool, keyring string) (string, error) { 280 if fi, err := os.Stat(name); err == nil { 281 abs, err := filepath.Abs(name) 282 if err != nil { 283 return abs, err 284 } 285 if verify { 286 if fi.IsDir() { 287 return "", errors.New("cannot verify a directory") 288 } 289 if err := verifyChart(abs, keyring); err != nil { 290 return "", err 291 } 292 } 293 return abs, nil 294 } 295 if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { 296 return name, fmt.Errorf("path %q not found", name) 297 } 298 299 crepo := filepath.Join(repositoryDirectory(), name) 300 if _, err := os.Stat(crepo); err == nil { 301 return filepath.Abs(crepo) 302 } 303 304 // Try fetching the chart from a remote repo into a tmpdir 305 origname := name 306 if filepath.Ext(name) != ".tgz" { 307 name += ".tgz" 308 } 309 if err := downloadChart(name, false, ".", verify, keyring); err == nil { 310 lname, err := filepath.Abs(filepath.Base(name)) 311 if err != nil { 312 return lname, err 313 } 314 fmt.Printf("Fetched %s to %s\n", origname, lname) 315 return lname, nil 316 } 317 318 return name, fmt.Errorf("file %q not found", origname) 319 } 320 321 func generateName(nameTemplate string) (string, error) { 322 t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) 323 if err != nil { 324 return "", err 325 } 326 var b bytes.Buffer 327 err = t.Execute(&b, nil) 328 if err != nil { 329 return "", err 330 } 331 return b.String(), nil 332 }