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