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