github.com/koderover/helm@v2.17.0+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/helm" 35 "k8s.io/helm/pkg/helm/helmpath" 36 "k8s.io/helm/pkg/helm/portforwarder" 37 "k8s.io/helm/pkg/version" 38 ) 39 40 const initDesc = ` 41 This command installs Tiller (the Helm server-side component) onto your 42 Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/). 43 44 As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters 45 by reading $KUBECONFIG (default '~/.kube/config') and using the default context. 46 47 To set up just a local environment, use '--client-only'. That will configure 48 $HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller 49 deployment. 50 51 When installing Tiller, 'helm init' will attempt to install the latest released 52 version. You can specify an alternative image with '--tiller-image'. For those 53 frequently working on the latest code, the flag '--canary-image' will install 54 the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub 55 repository on the master branch). 56 57 To dump a manifest containing the Tiller deployment YAML, combine the 58 '--dry-run' and '--debug' flags. 59 ` 60 61 var ( 62 stableRepositoryURL = "https://charts.helm.sh/stable" 63 oldStableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com" 64 // This is the IPv4 loopback, not localhost, because we have to force IPv4 65 // for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410 66 localRepositoryURL = "http://127.0.0.1:8879/charts" 67 tlsServerName string // overrides the server name used to verify the hostname on the returned certificates from the server. 68 tlsCaCertFile string // path to TLS CA certificate file 69 tlsCertFile string // path to TLS certificate file 70 tlsKeyFile string // path to TLS key file 71 tlsVerify bool // enable TLS and verify remote certificates 72 tlsEnable bool // enable TLS 73 ) 74 75 type initCmd struct { 76 image string 77 clientOnly bool 78 canary bool 79 upgrade bool 80 namespace string 81 dryRun bool 82 forceUpgrade bool 83 skipRefresh bool 84 skipRepos bool 85 out io.Writer 86 client helm.Interface 87 home helmpath.Home 88 opts installer.Options 89 kubeClient kubernetes.Interface 90 serviceAccount string 91 maxHistory int 92 replicas int 93 wait bool 94 useDeprecatedRepo bool 95 } 96 97 func newInitCmd(out io.Writer) *cobra.Command { 98 i := &initCmd{out: out} 99 100 cmd := &cobra.Command{ 101 Use: "init", 102 Short: "Initialize Helm on both client and server", 103 Long: initDesc, 104 RunE: func(cmd *cobra.Command, args []string) error { 105 if len(args) != 0 { 106 return errors.New("This command does not accept arguments") 107 } 108 i.namespace = settings.TillerNamespace 109 i.home = settings.Home 110 i.client = ensureHelmClient(i.client) 111 112 return i.run() 113 }, 114 } 115 116 f := cmd.Flags() 117 f.StringVarP(&i.image, "tiller-image", "i", "", "Override Tiller image") 118 f.BoolVar(&i.canary, "canary-image", false, "Use the canary Tiller image") 119 f.BoolVar(&i.upgrade, "upgrade", false, "Upgrade if Tiller is already installed") 120 f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "Force upgrade of Tiller to the current helm version") 121 f.BoolVarP(&i.clientOnly, "client-only", "c", false, "If set does not install Tiller") 122 f.BoolVar(&i.dryRun, "dry-run", false, "Do not install local or remote") 123 f.BoolVar(&i.skipRefresh, "skip-refresh", false, "Do not refresh (download) the local repository cache") 124 f.BoolVar(&i.skipRepos, "skip-repos", false, "Skip adding the stable and local repositories") 125 f.BoolVar(&i.wait, "wait", false, "Block until Tiller is running and ready to receive requests") 126 127 f.BoolVar(&i.useDeprecatedRepo, "use-deprecated-stable-repository", false, "Use the old (googleapis) repository URL even though that URL is being shutdown.") 128 129 // TODO: replace TLS flags with pkg/helm/environment.AddFlagsTLS() in Helm 3 130 // 131 // NOTE (bacongobbler): we can't do this in Helm 2 because the flag names differ, and `helm init --tls-ca-cert` 132 // doesn't conform with the rest of the TLS flag names (should be --tiller-tls-ca-cert in Helm 3) 133 f.BoolVar(&tlsEnable, "tiller-tls", false, "Install Tiller with TLS enabled") 134 f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "Install Tiller with TLS enabled and to verify remote certificates") 135 f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "Path to TLS key file to install with Tiller") 136 f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "Path to TLS certificate file to install with Tiller") 137 f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "Path to CA root certificate") 138 f.StringVar(&tlsServerName, "tiller-tls-hostname", settings.TillerHost, "The server name used to verify the hostname on the returned certificates from Tiller") 139 140 f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") 141 f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") 142 143 f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "Install Tiller with net=host") 144 f.StringVar(&i.serviceAccount, "service-account", "", "Name of service account") 145 f.IntVar(&i.maxHistory, "history-max", 0, "Limit the maximum number of revisions saved per release. Use 0 for no limit.") 146 f.IntVar(&i.replicas, "replicas", 1, "Amount of tiller instances to run on the cluster") 147 148 f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "Labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") 149 f.VarP(&i.opts.Output, "output", "o", "Skip installation and output Tiller's manifest in specified format (json or yaml)") 150 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)") 151 f.BoolVar(&i.opts.AutoMountServiceAccountToken, "automount-service-account-token", true, "Auto-mount the given service account to tiller") 152 153 return cmd 154 } 155 156 // tlsOptions sanitizes the tls flags as well as checks for the existence of required 157 // tls files indicated by those flags, if any. 158 func (i *initCmd) tlsOptions() error { 159 i.opts.EnableTLS = tlsEnable || tlsVerify 160 i.opts.VerifyTLS = tlsVerify 161 162 if i.opts.EnableTLS { 163 missing := func(file string) bool { 164 _, err := os.Stat(file) 165 return os.IsNotExist(err) 166 } 167 if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { 168 return errors.New("missing required TLS key file") 169 } 170 if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { 171 return errors.New("missing required TLS certificate file") 172 } 173 if i.opts.VerifyTLS { 174 if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { 175 return errors.New("missing required TLS CA file") 176 } 177 } 178 179 // FIXME: remove once we use pkg/helm/environment.AddFlagsTLS() in Helm 3 180 settings.TLSEnable = tlsEnable 181 settings.TLSVerify = tlsVerify 182 settings.TLSServerName = tlsServerName 183 settings.TLSCaCertFile = tlsCaCertFile 184 settings.TLSCertFile = tlsCertFile 185 settings.TLSKeyFile = tlsKeyFile 186 } 187 return nil 188 } 189 190 // run initializes local config and installs Tiller to Kubernetes cluster. 191 func (i *initCmd) run() error { 192 if err := i.tlsOptions(); err != nil { 193 return err 194 } 195 i.opts.Namespace = i.namespace 196 i.opts.UseCanary = i.canary 197 i.opts.ImageSpec = i.image 198 i.opts.ForceUpgrade = i.forceUpgrade 199 i.opts.ServiceAccount = i.serviceAccount 200 i.opts.MaxHistory = i.maxHistory 201 i.opts.Replicas = i.replicas 202 203 writeYAMLManifests := func(manifests []string) error { 204 w := i.out 205 for _, manifest := range manifests { 206 if _, err := fmt.Fprintln(w, "---"); err != nil { 207 return err 208 } 209 210 if _, err := fmt.Fprintln(w, manifest); err != nil { 211 return err 212 } 213 } 214 215 // YAML ending document boundary marker 216 _, err := fmt.Fprintln(w, "...") 217 return err 218 } 219 if len(i.opts.Output) > 0 { 220 var manifests []string 221 var err error 222 if manifests, err = installer.TillerManifests(&i.opts); err != nil { 223 return err 224 } 225 switch i.opts.Output.String() { 226 case "json": 227 for _, manifest := range manifests { 228 var out bytes.Buffer 229 jsonb, err := yaml.ToJSON([]byte(manifest)) 230 if err != nil { 231 return err 232 } 233 buf := bytes.NewBuffer(jsonb) 234 if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { 235 return err 236 } 237 if _, err = i.out.Write(out.Bytes()); err != nil { 238 return err 239 } 240 fmt.Fprint(i.out, "\n") 241 } 242 return nil 243 case "yaml": 244 return writeYAMLManifests(manifests) 245 default: 246 return fmt.Errorf("unknown output format: %q", i.opts.Output) 247 } 248 } 249 if settings.Debug { 250 var manifests []string 251 var err error 252 253 // write Tiller manifests 254 if manifests, err = installer.TillerManifests(&i.opts); err != nil { 255 return err 256 } 257 258 if err = writeYAMLManifests(manifests); err != nil { 259 return err 260 } 261 } 262 263 if i.dryRun { 264 return nil 265 } 266 267 if i.skipRepos { 268 if err := installer.InitializeWithoutRepos(i.home, i.out); err != nil { 269 return fmt.Errorf("error initializing: %s", err) 270 } 271 } else { 272 // If this is set, override user config, default config, and set it to the old 273 // URL. 274 if i.useDeprecatedRepo { 275 stableRepositoryURL = oldStableRepositoryURL 276 } 277 if err := installer.Initialize(i.home, i.out, i.skipRefresh, settings, stableRepositoryURL, localRepositoryURL); err != nil { 278 return fmt.Errorf("error initializing: %s", err) 279 } 280 } 281 fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home) 282 283 if !i.clientOnly { 284 if i.kubeClient == nil { 285 _, c, err := getKubeClient(settings.KubeContext, settings.KubeConfig) 286 if err != nil { 287 return fmt.Errorf("could not get kubernetes client: %s", err) 288 } 289 i.kubeClient = c 290 } 291 if err := installer.Install(i.kubeClient, &i.opts); err != nil { 292 if !apierrors.IsAlreadyExists(err) { 293 return fmt.Errorf("error installing: %s", err) 294 } 295 if i.upgrade { 296 if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { 297 return fmt.Errorf("error when upgrading: %s", err) 298 } 299 if err := i.ping(i.opts.SelectImage()); err != nil { 300 return err 301 } 302 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been updated to", i.opts.SelectImage(), ".") 303 } else { 304 debug("The error received while trying to init: %s", err) 305 fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+ 306 "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") 307 } 308 } else { 309 fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.") 310 if !tlsVerify { 311 fmt.Fprintln(i.out, "\nPlease note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ 312 "To prevent this, run `helm init` with the --tiller-tls-verify flag.\n"+ 313 "For more information on securing your installation see: https://v2.helm.sh/docs/securing_installation/") 314 } 315 } 316 if err := i.ping(i.opts.SelectImage()); err != nil { 317 return err 318 } 319 } else { 320 fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set") 321 } 322 323 needsDefaultImage := !i.clientOnly && !i.opts.UseCanary && len(i.opts.ImageSpec) == 0 && version.BuildMetadata == "unreleased" 324 if needsDefaultImage { 325 fmt.Fprintf(i.out, "\nWarning: You appear to be using an unreleased version of Helm. Please either use the\n"+ 326 "--canary-image flag, or specify your desired tiller version with --tiller-image.\n\n"+ 327 "Ex:\n"+ 328 "$ helm init --tiller-image ghcr.io/helm/tiller:v2.17.0\n\n") 329 } 330 331 return nil 332 } 333 334 func (i *initCmd) ping(image string) error { 335 if i.wait { 336 _, kubeClient, err := getKubeClient(settings.KubeContext, settings.KubeConfig) 337 if err != nil { 338 return err 339 } 340 if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout, image) { 341 return fmt.Errorf("tiller was not found. polling deadline exceeded") 342 } 343 344 // establish a connection to Tiller now that we've effectively guaranteed it's available 345 if err := setupConnection(); err != nil { 346 return err 347 } 348 i.client = newClient() 349 if err := i.client.PingTiller(); err != nil { 350 return fmt.Errorf("could not ping Tiller: %s", err) 351 } 352 } 353 354 return nil 355 } 356 357 // watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we 358 // want to wait before we call New(). 359 // 360 // Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false. 361 func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64, newImage string) bool { 362 deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C 363 checkTillerPodTicker := time.NewTicker(500 * time.Millisecond) 364 doneChan := make(chan bool) 365 366 defer checkTillerPodTicker.Stop() 367 368 go func() { 369 for range checkTillerPodTicker.C { 370 image, err := portforwarder.GetTillerPodImage(client.CoreV1(), namespace) 371 if err == nil && image == newImage { 372 doneChan <- true 373 break 374 } 375 } 376 }() 377 378 for { 379 select { 380 case <-deadlinePollingChan: 381 return false 382 case <-doneChan: 383 return true 384 } 385 } 386 }