github.com/brockwood/helm@v2.8.0-rc.1.0.20180112204834-077be881c4cc+incompatible/cmd/helm/init.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 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 27 "github.com/spf13/cobra" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 "k8s.io/client-go/kubernetes" 30 31 "k8s.io/apimachinery/pkg/util/yaml" 32 "k8s.io/helm/cmd/helm/installer" 33 "k8s.io/helm/pkg/getter" 34 "k8s.io/helm/pkg/helm/helmpath" 35 "k8s.io/helm/pkg/repo" 36 ) 37 38 const initDesc = ` 39 This command installs Tiller (the Helm server-side component) onto your 40 Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/). 41 42 As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters 43 by reading $KUBECONFIG (default '~/.kube/config') and using the default context. 44 45 To set up just a local environment, use '--client-only'. That will configure 46 $HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller 47 deployment. 48 49 When installing Tiller, 'helm init' will attempt to install the latest released 50 version. You can specify an alternative image with '--tiller-image'. For those 51 frequently working on the latest code, the flag '--canary-image' will install 52 the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub 53 repository on the master branch). 54 55 To dump a manifest containing the Tiller deployment YAML, combine the 56 '--dry-run' and '--debug' flags. 57 ` 58 59 const ( 60 stableRepository = "stable" 61 localRepository = "local" 62 localRepositoryIndexFile = "index.yaml" 63 ) 64 65 var ( 66 stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com" 67 // This is the IPv4 loopback, not localhost, because we have to force IPv4 68 // for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410 69 localRepositoryURL = "http://127.0.0.1:8879/charts" 70 ) 71 72 type initCmd struct { 73 image string 74 clientOnly bool 75 canary bool 76 upgrade bool 77 namespace string 78 dryRun bool 79 forceUpgrade bool 80 skipRefresh bool 81 out io.Writer 82 home helmpath.Home 83 opts installer.Options 84 kubeClient kubernetes.Interface 85 serviceAccount string 86 maxHistory int 87 } 88 89 func newInitCmd(out io.Writer) *cobra.Command { 90 i := &initCmd{out: out} 91 92 cmd := &cobra.Command{ 93 Use: "init", 94 Short: "initialize Helm on both client and server", 95 Long: initDesc, 96 RunE: func(cmd *cobra.Command, args []string) error { 97 if len(args) != 0 { 98 return errors.New("This command does not accept arguments") 99 } 100 i.namespace = settings.TillerNamespace 101 i.home = settings.Home 102 return i.run() 103 }, 104 } 105 106 f := cmd.Flags() 107 f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image") 108 f.BoolVar(&i.canary, "canary-image", false, "use the canary Tiller image") 109 f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed") 110 f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version") 111 f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Tiller") 112 f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") 113 f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") 114 115 f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") 116 f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates") 117 f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller") 118 f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller") 119 f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") 120 121 f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") 122 f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") 123 124 f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host") 125 f.StringVar(&i.serviceAccount, "service-account", "", "name of service account") 126 f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") 127 128 f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") 129 f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)") 130 f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)") 131 132 return cmd 133 } 134 135 // tlsOptions sanitizes the tls flags as well as checks for the existence of required 136 // tls files indicated by those flags, if any. 137 func (i *initCmd) tlsOptions() error { 138 i.opts.EnableTLS = tlsEnable || tlsVerify 139 i.opts.VerifyTLS = tlsVerify 140 141 if i.opts.EnableTLS { 142 missing := func(file string) bool { 143 _, err := os.Stat(file) 144 return os.IsNotExist(err) 145 } 146 if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { 147 return errors.New("missing required TLS key file") 148 } 149 if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { 150 return errors.New("missing required TLS certificate file") 151 } 152 if i.opts.VerifyTLS { 153 if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { 154 return errors.New("missing required TLS CA file") 155 } 156 } 157 } 158 return nil 159 } 160 161 // run initializes local config and installs Tiller to Kubernetes cluster. 162 func (i *initCmd) run() error { 163 if err := i.tlsOptions(); err != nil { 164 return err 165 } 166 i.opts.Namespace = i.namespace 167 i.opts.UseCanary = i.canary 168 i.opts.ImageSpec = i.image 169 i.opts.ForceUpgrade = i.forceUpgrade 170 i.opts.ServiceAccount = i.serviceAccount 171 i.opts.MaxHistory = i.maxHistory 172 173 writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { 174 w := i.out 175 if !first { 176 // YAML starting document boundary marker 177 if _, err := fmt.Fprintln(w, "---"); err != nil { 178 return err 179 } 180 } 181 if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil { 182 return err 183 } 184 if _, err := fmt.Fprintln(w, "kind:", kind); err != nil { 185 return err 186 } 187 if _, err := fmt.Fprint(w, body); err != nil { 188 return err 189 } 190 if !last { 191 return nil 192 } 193 // YAML ending document boundary marker 194 _, err := fmt.Fprintln(w, "...") 195 return err 196 } 197 if len(i.opts.Output) > 0 { 198 var body string 199 var err error 200 const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",` 201 if body, err = installer.DeploymentManifest(&i.opts); err != nil { 202 return err 203 } 204 switch i.opts.Output.String() { 205 case "json": 206 var out bytes.Buffer 207 jsonb, err := yaml.ToJSON([]byte(body)) 208 if err != nil { 209 return err 210 } 211 buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1)) 212 buf.WriteString(tm) 213 // Drop the opening object delimiter ('{'). 214 buf.Write(jsonb[1:]) 215 if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { 216 return err 217 } 218 if _, err = i.out.Write(out.Bytes()); err != nil { 219 return err 220 } 221 222 return nil 223 case "yaml": 224 if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { 225 return err 226 } 227 return nil 228 default: 229 return fmt.Errorf("unknown output format: %q", i.opts.Output) 230 } 231 } 232 if settings.Debug { 233 234 var body string 235 var err error 236 237 // write Deployment manifest 238 if body, err = installer.DeploymentManifest(&i.opts); err != nil { 239 return err 240 } 241 if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { 242 return err 243 } 244 245 // write Service manifest 246 if body, err = installer.ServiceManifest(i.namespace); err != nil { 247 return err 248 } 249 if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil { 250 return err 251 } 252 253 // write Secret manifest 254 if i.opts.EnableTLS { 255 if body, err = installer.SecretManifest(&i.opts); err != nil { 256 return err 257 } 258 if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil { 259 return err 260 } 261 } 262 } 263 264 if i.dryRun { 265 return nil 266 } 267 268 if err := ensureDirectories(i.home, i.out); err != nil { 269 return err 270 } 271 if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil { 272 return err 273 } 274 if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil { 275 return err 276 } 277 fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home) 278 279 if !i.clientOnly { 280 if i.kubeClient == nil { 281 _, c, err := getKubeClient(settings.KubeContext) 282 if err != nil { 283 return fmt.Errorf("could not get kubernetes client: %s", err) 284 } 285 i.kubeClient = c 286 } 287 if err := installer.Install(i.kubeClient, &i.opts); err != nil { 288 if !apierrors.IsAlreadyExists(err) { 289 return fmt.Errorf("error installing: %s", err) 290 } 291 if i.upgrade { 292 if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { 293 return fmt.Errorf("error when upgrading: %s", err) 294 } 295 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been upgraded to the current version.") 296 } else { 297 fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+ 298 "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") 299 } 300 } else { 301 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.") 302 } 303 } else { 304 fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set") 305 } 306 307 fmt.Fprintln(i.out, "Happy Helming!") 308 return nil 309 } 310 311 // ensureDirectories checks to see if $HELM_HOME exists. 312 // 313 // If $HELM_HOME does not exist, this function will create it. 314 func ensureDirectories(home helmpath.Home, out io.Writer) error { 315 configDirectories := []string{ 316 home.String(), 317 home.Repository(), 318 home.Cache(), 319 home.LocalRepository(), 320 home.Plugins(), 321 home.Starters(), 322 home.Archive(), 323 } 324 for _, p := range configDirectories { 325 if fi, err := os.Stat(p); err != nil { 326 fmt.Fprintf(out, "Creating %s \n", p) 327 if err := os.MkdirAll(p, 0755); err != nil { 328 return fmt.Errorf("Could not create %s: %s", p, err) 329 } 330 } else if !fi.IsDir() { 331 return fmt.Errorf("%s must be a directory", p) 332 } 333 } 334 335 return nil 336 } 337 338 func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error { 339 repoFile := home.RepositoryFile() 340 if fi, err := os.Stat(repoFile); err != nil { 341 fmt.Fprintf(out, "Creating %s \n", repoFile) 342 f := repo.NewRepoFile() 343 sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh) 344 if err != nil { 345 return err 346 } 347 lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out) 348 if err != nil { 349 return err 350 } 351 f.Add(sr) 352 f.Add(lr) 353 if err := f.WriteFile(repoFile, 0644); err != nil { 354 return err 355 } 356 } else if fi.IsDir() { 357 return fmt.Errorf("%s must be a file, not a directory", repoFile) 358 } 359 return nil 360 } 361 362 func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool) (*repo.Entry, error) { 363 fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL) 364 c := repo.Entry{ 365 Name: stableRepository, 366 URL: stableRepositoryURL, 367 Cache: cacheFile, 368 } 369 r, err := repo.NewChartRepository(&c, getter.All(settings)) 370 if err != nil { 371 return nil, err 372 } 373 374 if skipRefresh { 375 return &c, nil 376 } 377 378 // In this case, the cacheFile is always absolute. So passing empty string 379 // is safe. 380 if err := r.DownloadIndexFile(""); err != nil { 381 return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error()) 382 } 383 384 return &c, nil 385 } 386 387 func initLocalRepo(indexFile, cacheFile string, out io.Writer) (*repo.Entry, error) { 388 if fi, err := os.Stat(indexFile); err != nil { 389 fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL) 390 i := repo.NewIndexFile() 391 if err := i.WriteFile(indexFile, 0644); err != nil { 392 return nil, err 393 } 394 395 //TODO: take this out and replace with helm update functionality 396 createLink(indexFile, cacheFile) 397 } else if fi.IsDir() { 398 return nil, fmt.Errorf("%s must be a file, not a directory", indexFile) 399 } 400 401 return &repo.Entry{ 402 Name: localRepository, 403 URL: localRepositoryURL, 404 Cache: cacheFile, 405 }, nil 406 } 407 408 func ensureRepoFileFormat(file string, out io.Writer) error { 409 r, err := repo.LoadRepositoriesFile(file) 410 if err == repo.ErrRepoOutOfDate { 411 fmt.Fprintln(out, "Updating repository file format...") 412 if err := r.WriteFile(file, 0644); err != nil { 413 return err 414 } 415 } 416 417 return nil 418 }