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