github.com/replicatedhq/ship@v0.55.0/pkg/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  /*
    18  This file was edited by Replicated in 2018 to remove some functionality and to expose `helm init` as a function.
    19  Among other things, clientOnly has been set as the default (and only) option, and the cli interface code has been removed.
    20  */
    21  
    22  package helm
    23  
    24  import (
    25  	"bufio"
    26  	"bytes"
    27  	"encoding/json"
    28  	"fmt"
    29  	"io"
    30  	"os"
    31  	"path/filepath"
    32  
    33  	"github.com/pkg/errors"
    34  	"k8s.io/apimachinery/pkg/util/yaml"
    35  	"k8s.io/helm/cmd/helm/installer"
    36  	"k8s.io/helm/pkg/getter"
    37  	"k8s.io/helm/pkg/helm/environment"
    38  	"k8s.io/helm/pkg/helm/helmpath"
    39  	"k8s.io/helm/pkg/repo"
    40  )
    41  
    42  // const initDesc = `
    43  // This command installs Tiller (the Helm server-side component) onto your
    44  // Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/).
    45  //
    46  // As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters
    47  // by reading $KUBECONFIG (default '~/.kube/config') and using the default context.
    48  //
    49  // To set up just a local environment, use '--client-only'. That will configure
    50  // $HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller
    51  // deployment.
    52  //
    53  // When installing Tiller, 'helm init' will attempt to install the latest released
    54  // version. You can specify an alternative image with '--tiller-image'. For those
    55  // frequently working on the latest code, the flag '--canary-image' will install
    56  // the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub
    57  // repository on the master branch).
    58  //
    59  // To dump a manifest containing the Tiller deployment YAML, combine the
    60  // '--dry-run' and '--debug' flags.
    61  // `
    62  
    63  const (
    64  	stableRepository         = "stable"
    65  	localRepository          = "local"
    66  	localRepositoryIndexFile = "index.yaml"
    67  )
    68  
    69  var (
    70  	stableRepositoryURL = "https://charts.helm.sh/stable"
    71  	// This is the IPv4 loopback, not localhost, because we have to force IPv4
    72  	// for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410
    73  	localRepositoryURL = "http://127.0.0.1:8879/charts"
    74  )
    75  
    76  type initCmd struct {
    77  	skipRefresh bool // do not refresh (download) the local repository cache
    78  	out         io.Writer
    79  	home        helmpath.Home
    80  	opts        installer.Options
    81  }
    82  
    83  func Init(home string) (string, error) {
    84  	var buf bytes.Buffer
    85  	bufWriter := bufio.NewWriter(&buf)
    86  
    87  	toInit := initCmd{
    88  		out: bufWriter,
    89  	}
    90  
    91  	var path string
    92  	var err error
    93  	if home != "" {
    94  		path, err = filepath.Abs(home)
    95  	} else {
    96  		path, err = helmHome()
    97  	}
    98  	if err != nil {
    99  		return "", errors.Wrap(err, "unable to find home directory")
   100  	}
   101  	toInit.home = helmpath.Home(path)
   102  
   103  	err = toInit.run()
   104  	return buf.String(), err
   105  }
   106  
   107  // run initializes local config and installs Tiller to Kubernetes cluster.
   108  func (i *initCmd) run() error {
   109  
   110  	writeYAMLManifests := func(manifests []string) error {
   111  		w := i.out
   112  		for _, manifest := range manifests {
   113  			if _, err := fmt.Fprintln(w, "---"); err != nil {
   114  				return err
   115  			}
   116  
   117  			if _, err := fmt.Fprintln(w, manifest); err != nil {
   118  				return err
   119  			}
   120  		}
   121  
   122  		// YAML ending document boundary marker
   123  		_, err := fmt.Fprintln(w, "...")
   124  		return err
   125  	}
   126  	if len(i.opts.Output) > 0 {
   127  		var manifests []string
   128  		var err error
   129  		if manifests, err = installer.TillerManifests(&i.opts); err != nil {
   130  			return err
   131  		}
   132  		switch i.opts.Output.String() {
   133  		case "json":
   134  			for _, manifest := range manifests {
   135  				var out bytes.Buffer
   136  				jsonb, err := yaml.ToJSON([]byte(manifest))
   137  				if err != nil {
   138  					return err
   139  				}
   140  				buf := bytes.NewBuffer(jsonb)
   141  				if err := json.Indent(&out, buf.Bytes(), "", "    "); err != nil {
   142  					return err
   143  				}
   144  				if _, err = i.out.Write(out.Bytes()); err != nil {
   145  					return err
   146  				}
   147  				fmt.Fprint(i.out, "\n")
   148  			}
   149  			return nil
   150  		case "yaml":
   151  			return writeYAMLManifests(manifests)
   152  		default:
   153  			return fmt.Errorf("unknown output format: %q", i.opts.Output)
   154  		}
   155  	}
   156  	if settings.Debug {
   157  		var manifests []string
   158  		var err error
   159  
   160  		// write Tiller manifests
   161  		if manifests, err = installer.TillerManifests(&i.opts); err != nil {
   162  			return err
   163  		}
   164  
   165  		if err = writeYAMLManifests(manifests); err != nil {
   166  			return err
   167  		}
   168  	}
   169  
   170  	if err := ensureDirectories(i.home, i.out); err != nil {
   171  		return err
   172  	}
   173  	if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil {
   174  		return err
   175  	}
   176  	if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil {
   177  		return err
   178  	}
   179  	fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", i.home)
   180  
   181  	fmt.Fprintln(i.out, "Happy Helming!")
   182  	return nil
   183  }
   184  
   185  // ensureDirectories checks to see if $HELM_HOME exists.
   186  //
   187  // If $HELM_HOME does not exist, this function will create it.
   188  func ensureDirectories(home helmpath.Home, out io.Writer) error {
   189  	configDirectories := []string{
   190  		home.String(),
   191  		home.Repository(),
   192  		home.Cache(),
   193  		home.LocalRepository(),
   194  		home.Plugins(),
   195  		home.Starters(),
   196  		home.Archive(),
   197  	}
   198  	for _, p := range configDirectories {
   199  		if fi, err := os.Stat(p); err != nil {
   200  			fmt.Fprintf(out, "Creating %s \n", p)
   201  			if err := os.MkdirAll(p, 0755); err != nil {
   202  				return fmt.Errorf("Could not create %s: %s", p, err)
   203  			}
   204  		} else if !fi.IsDir() {
   205  			return fmt.Errorf("%s must be a directory", p)
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error {
   213  	repoFile := home.RepositoryFile()
   214  	if fi, err := os.Stat(repoFile); err != nil {
   215  		fmt.Fprintf(out, "Creating %s \n", repoFile)
   216  		f := repo.NewRepoFile()
   217  		sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh, home)
   218  		if err != nil {
   219  			return err
   220  		}
   221  		lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out, home)
   222  		if err != nil {
   223  			return err
   224  		}
   225  		f.Add(sr)
   226  		f.Add(lr)
   227  		if err := f.WriteFile(repoFile, 0644); err != nil {
   228  			return err
   229  		}
   230  	} else if fi.IsDir() {
   231  		return fmt.Errorf("%s must be a file, not a directory", repoFile)
   232  	}
   233  	return nil
   234  }
   235  
   236  func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) {
   237  	fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL)
   238  	c := repo.Entry{
   239  		Name:  stableRepository,
   240  		URL:   stableRepositoryURL,
   241  		Cache: cacheFile,
   242  	}
   243  	r, err := repo.NewChartRepository(&c, getter.All(environment.EnvSettings{Home: home}))
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	if skipRefresh {
   249  		return &c, nil
   250  	}
   251  
   252  	// In this case, the cacheFile is always absolute. So passing empty string
   253  	// is safe.
   254  	if err := r.DownloadIndexFile(""); err != nil {
   255  		return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error())
   256  	}
   257  
   258  	return &c, nil
   259  }
   260  
   261  func initLocalRepo(indexFile, cacheFile string, out io.Writer, home helmpath.Home) (*repo.Entry, error) {
   262  	if fi, err := os.Stat(indexFile); err != nil {
   263  		fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL)
   264  		i := repo.NewIndexFile()
   265  		if err := i.WriteFile(indexFile, 0644); err != nil {
   266  			return nil, err
   267  		}
   268  
   269  		//TODO: take this out and replace with helm update functionality
   270  		if err := createLink(indexFile, cacheFile, home); err != nil {
   271  			return nil, err
   272  		}
   273  	} else if fi.IsDir() {
   274  		return nil, fmt.Errorf("%s must be a file, not a directory", indexFile)
   275  	}
   276  
   277  	return &repo.Entry{
   278  		Name:  localRepository,
   279  		URL:   localRepositoryURL,
   280  		Cache: cacheFile,
   281  	}, nil
   282  }
   283  
   284  func ensureRepoFileFormat(file string, out io.Writer) error {
   285  	r, err := repo.LoadRepositoriesFile(file)
   286  	if err == repo.ErrRepoOutOfDate {
   287  		fmt.Fprintln(out, "Updating repository file format...")
   288  		if err := r.WriteFile(file, 0644); err != nil {
   289  			return err
   290  		}
   291  	}
   292  
   293  	return nil
   294  }