github.com/Beeketing/helm@v2.12.1+incompatible/cmd/helm/init.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 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 "time" 27 28 "github.com/spf13/cobra" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 "k8s.io/client-go/kubernetes" 31 32 "k8s.io/apimachinery/pkg/util/yaml" 33 "k8s.io/helm/cmd/helm/installer" 34 "k8s.io/helm/pkg/getter" 35 "k8s.io/helm/pkg/helm" 36 "k8s.io/helm/pkg/helm/helmpath" 37 "k8s.io/helm/pkg/helm/portforwarder" 38 "k8s.io/helm/pkg/repo" 39 ) 40 41 const initDesc = ` 42 This command installs Tiller (the Helm server-side component) onto your 43 Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/). 44 45 As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters 46 by reading $KUBECONFIG (default '~/.kube/config') and using the default context. 47 48 To set up just a local environment, use '--client-only'. That will configure 49 $HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller 50 deployment. 51 52 When installing Tiller, 'helm init' will attempt to install the latest released 53 version. You can specify an alternative image with '--tiller-image'. For those 54 frequently working on the latest code, the flag '--canary-image' will install 55 the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub 56 repository on the master branch). 57 58 To dump a manifest containing the Tiller deployment YAML, combine the 59 '--dry-run' and '--debug' flags. 60 ` 61 62 const ( 63 stableRepository = "stable" 64 localRepository = "local" 65 localRepositoryIndexFile = "index.yaml" 66 ) 67 68 var ( 69 stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com" 70 // This is the IPv4 loopback, not localhost, because we have to force IPv4 71 // for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410 72 localRepositoryURL = "http://127.0.0.1:8879/charts" 73 tlsServerName string // overrides the server name used to verify the hostname on the returned certificates from the server. 74 tlsCaCertFile string // path to TLS CA certificate file 75 tlsCertFile string // path to TLS certificate file 76 tlsKeyFile string // path to TLS key file 77 tlsVerify bool // enable TLS and verify remote certificates 78 tlsEnable bool // enable TLS 79 ) 80 81 type initCmd struct { 82 image string 83 clientOnly bool 84 canary bool 85 upgrade bool 86 namespace string 87 dryRun bool 88 forceUpgrade bool 89 skipRefresh bool 90 out io.Writer 91 client helm.Interface 92 home helmpath.Home 93 opts installer.Options 94 kubeClient kubernetes.Interface 95 serviceAccount string 96 maxHistory int 97 replicas int 98 wait bool 99 } 100 101 func newInitCmd(out io.Writer) *cobra.Command { 102 i := &initCmd{out: out} 103 104 cmd := &cobra.Command{ 105 Use: "init", 106 Short: "initialize Helm on both client and server", 107 Long: initDesc, 108 RunE: func(cmd *cobra.Command, args []string) error { 109 if len(args) != 0 { 110 return errors.New("This command does not accept arguments") 111 } 112 i.namespace = settings.TillerNamespace 113 i.home = settings.Home 114 i.client = ensureHelmClient(i.client) 115 116 return i.run() 117 }, 118 } 119 120 f := cmd.Flags() 121 f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image") 122 f.BoolVar(&i.canary, "canary-image", false, "use the canary Tiller image") 123 f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed") 124 f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version") 125 f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Tiller") 126 f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") 127 f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") 128 f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests") 129 130 // TODO: replace TLS flags with pkg/helm/environment.AddFlagsTLS() in Helm 3 131 // 132 // NOTE (bacongobbler): we can't do this in Helm 2 because the flag names differ, and `helm init --tls-ca-cert` 133 // doesn't conform with the rest of the TLS flag names (should be --tiller-tls-ca-cert in Helm 3) 134 f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") 135 f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates") 136 f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller") 137 f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller") 138 f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") 139 f.StringVar(&tlsServerName, "tiller-tls-hostname", settings.TillerHost, "the server name used to verify the hostname on the returned certificates from Tiller") 140 141 f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") 142 f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") 143 144 f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host") 145 f.StringVar(&i.serviceAccount, "service-account", "", "name of service account") 146 f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") 147 f.IntVar(&i.replicas, "replicas", 1, "amount of tiller instances to run on the cluster") 148 149 f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") 150 f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)") 151 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)") 152 f.BoolVar(&i.opts.AutoMountServiceAccountToken, "automount-service-account-token", true, "auto-mount the given service account to tiller") 153 154 return cmd 155 } 156 157 // tlsOptions sanitizes the tls flags as well as checks for the existence of required 158 // tls files indicated by those flags, if any. 159 func (i *initCmd) tlsOptions() error { 160 i.opts.EnableTLS = tlsEnable || tlsVerify 161 i.opts.VerifyTLS = tlsVerify 162 163 if i.opts.EnableTLS { 164 missing := func(file string) bool { 165 _, err := os.Stat(file) 166 return os.IsNotExist(err) 167 } 168 if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { 169 return errors.New("missing required TLS key file") 170 } 171 if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { 172 return errors.New("missing required TLS certificate file") 173 } 174 if i.opts.VerifyTLS { 175 if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { 176 return errors.New("missing required TLS CA file") 177 } 178 } 179 180 // FIXME: remove once we use pkg/helm/environment.AddFlagsTLS() in Helm 3 181 settings.TLSEnable = tlsEnable 182 settings.TLSVerify = tlsVerify 183 settings.TLSServerName = tlsServerName 184 settings.TLSCaCertFile = tlsCaCertFile 185 settings.TLSCertFile = tlsCertFile 186 settings.TLSKeyFile = tlsKeyFile 187 } 188 return nil 189 } 190 191 // run initializes local config and installs Tiller to Kubernetes cluster. 192 func (i *initCmd) run() error { 193 if err := i.tlsOptions(); err != nil { 194 return err 195 } 196 i.opts.Namespace = i.namespace 197 i.opts.UseCanary = i.canary 198 i.opts.ImageSpec = i.image 199 i.opts.ForceUpgrade = i.forceUpgrade 200 i.opts.ServiceAccount = i.serviceAccount 201 i.opts.MaxHistory = i.maxHistory 202 i.opts.Replicas = i.replicas 203 204 writeYAMLManifests := func(manifests []string) error { 205 w := i.out 206 for _, manifest := range manifests { 207 if _, err := fmt.Fprintln(w, "---"); err != nil { 208 return err 209 } 210 211 if _, err := fmt.Fprintln(w, manifest); err != nil { 212 return err 213 } 214 } 215 216 // YAML ending document boundary marker 217 _, err := fmt.Fprintln(w, "...") 218 return err 219 } 220 if len(i.opts.Output) > 0 { 221 var manifests []string 222 var err error 223 if manifests, err = installer.TillerManifests(&i.opts); err != nil { 224 return err 225 } 226 switch i.opts.Output.String() { 227 case "json": 228 for _, manifest := range manifests { 229 var out bytes.Buffer 230 jsonb, err := yaml.ToJSON([]byte(manifest)) 231 if err != nil { 232 return err 233 } 234 buf := bytes.NewBuffer(jsonb) 235 if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { 236 return err 237 } 238 if _, err = i.out.Write(out.Bytes()); err != nil { 239 return err 240 } 241 fmt.Fprint(i.out, "\n") 242 } 243 return nil 244 case "yaml": 245 return writeYAMLManifests(manifests) 246 default: 247 return fmt.Errorf("unknown output format: %q", i.opts.Output) 248 } 249 } 250 if settings.Debug { 251 var manifests []string 252 var err error 253 254 // write Tiller manifests 255 if manifests, err = installer.TillerManifests(&i.opts); err != nil { 256 return err 257 } 258 259 if err = writeYAMLManifests(manifests); err != nil { 260 return err 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, settings.KubeConfig) 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 if err := i.ping(i.opts.SelectImage()); err != nil { 296 return err 297 } 298 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been upgraded to the current version.") 299 } else { 300 fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+ 301 "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") 302 } 303 } else { 304 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.") 305 if !tlsVerify { 306 fmt.Fprintln(i.out, "\nPlease note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ 307 "To prevent this, run `helm init` with the --tiller-tls-verify flag.\n"+ 308 "For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation") 309 } 310 } 311 if err := i.ping(i.opts.SelectImage()); err != nil { 312 return err 313 } 314 } else { 315 fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set") 316 } 317 318 fmt.Fprintln(i.out, "Happy Helming!") 319 return nil 320 } 321 322 func (i *initCmd) ping(image string) error { 323 if i.wait { 324 _, kubeClient, err := getKubeClient(settings.KubeContext, settings.KubeConfig) 325 if err != nil { 326 return err 327 } 328 if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout, image) { 329 return fmt.Errorf("tiller was not found. polling deadline exceeded") 330 } 331 332 // establish a connection to Tiller now that we've effectively guaranteed it's available 333 if err := setupConnection(); err != nil { 334 return err 335 } 336 i.client = newClient() 337 if err := i.client.PingTiller(); err != nil { 338 return fmt.Errorf("could not ping Tiller: %s", err) 339 } 340 } 341 342 return nil 343 } 344 345 // ensureDirectories checks to see if $HELM_HOME exists. 346 // 347 // If $HELM_HOME does not exist, this function will create it. 348 func ensureDirectories(home helmpath.Home, out io.Writer) error { 349 configDirectories := []string{ 350 home.String(), 351 home.Repository(), 352 home.Cache(), 353 home.LocalRepository(), 354 home.Plugins(), 355 home.Starters(), 356 home.Archive(), 357 } 358 for _, p := range configDirectories { 359 if fi, err := os.Stat(p); err != nil { 360 fmt.Fprintf(out, "Creating %s \n", p) 361 if err := os.MkdirAll(p, 0755); err != nil { 362 return fmt.Errorf("Could not create %s: %s", p, err) 363 } 364 } else if !fi.IsDir() { 365 return fmt.Errorf("%s must be a directory", p) 366 } 367 } 368 369 return nil 370 } 371 372 func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error { 373 repoFile := home.RepositoryFile() 374 if fi, err := os.Stat(repoFile); err != nil { 375 fmt.Fprintf(out, "Creating %s \n", repoFile) 376 f := repo.NewRepoFile() 377 sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh, home) 378 if err != nil { 379 return err 380 } 381 lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out, home) 382 if err != nil { 383 return err 384 } 385 f.Add(sr) 386 f.Add(lr) 387 if err := f.WriteFile(repoFile, 0644); err != nil { 388 return err 389 } 390 } else if fi.IsDir() { 391 return fmt.Errorf("%s must be a file, not a directory", repoFile) 392 } 393 return nil 394 } 395 396 func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) { 397 fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL) 398 c := repo.Entry{ 399 Name: stableRepository, 400 URL: stableRepositoryURL, 401 Cache: cacheFile, 402 } 403 r, err := repo.NewChartRepository(&c, getter.All(settings)) 404 if err != nil { 405 return nil, err 406 } 407 408 if skipRefresh { 409 return &c, nil 410 } 411 412 // In this case, the cacheFile is always absolute. So passing empty string 413 // is safe. 414 if err := r.DownloadIndexFile(""); err != nil { 415 return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error()) 416 } 417 418 return &c, nil 419 } 420 421 func initLocalRepo(indexFile, cacheFile string, out io.Writer, home helmpath.Home) (*repo.Entry, error) { 422 if fi, err := os.Stat(indexFile); err != nil { 423 fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL) 424 i := repo.NewIndexFile() 425 if err := i.WriteFile(indexFile, 0644); err != nil { 426 return nil, err 427 } 428 429 //TODO: take this out and replace with helm update functionality 430 if err := createLink(indexFile, cacheFile, home); err != nil { 431 return nil, err 432 } 433 } else if fi.IsDir() { 434 return nil, fmt.Errorf("%s must be a file, not a directory", indexFile) 435 } 436 437 return &repo.Entry{ 438 Name: localRepository, 439 URL: localRepositoryURL, 440 Cache: cacheFile, 441 }, nil 442 } 443 444 func ensureRepoFileFormat(file string, out io.Writer) error { 445 r, err := repo.LoadRepositoriesFile(file) 446 if err == repo.ErrRepoOutOfDate { 447 fmt.Fprintln(out, "Updating repository file format...") 448 if err := r.WriteFile(file, 0644); err != nil { 449 return err 450 } 451 } 452 453 return nil 454 } 455 456 // watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we 457 // want to wait before we call New(). 458 // 459 // Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false. 460 func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64, newImage string) bool { 461 deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C 462 checkTillerPodTicker := time.NewTicker(500 * time.Millisecond) 463 doneChan := make(chan bool) 464 465 defer checkTillerPodTicker.Stop() 466 467 go func() { 468 for range checkTillerPodTicker.C { 469 image, err := portforwarder.GetTillerPodImage(client.CoreV1(), namespace) 470 if err == nil && image == newImage { 471 doneChan <- true 472 break 473 } 474 } 475 }() 476 477 for { 478 select { 479 case <-deadlinePollingChan: 480 return false 481 case <-doneChan: 482 return true 483 } 484 } 485 }