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