func fetch(chartName, lname, homedir, chartpath string) { src := helm.CacheDirectory(homedir, chartpath, chartName) dest := helm.WorkspaceChartDirectory(homedir, lname) fi, err := os.Stat(src) if err != nil { log.Warn("Oops. Looks like there was an issue finding the chart, %s, n %s. Running `helm update` to ensure you have the latest version of all Charts from Github...", lname, src) Update(homedir) fi, err = os.Stat(src) if err != nil { log.Die("Chart %s not found in %s", lname, src) } log.Info("Good news! Looks like that did the trick. Onwards and upwards!") } if !fi.IsDir() { log.Die("Malformed chart %s: Chart must be in a directory.", chartName) } if err := os.MkdirAll(dest, 0755); err != nil { log.Die("Could not create %q: %s", dest, err) } log.Debug("Fetching %s to %s", src, dest) if err := helm.CopyDir(src, dest); err != nil { log.Die("Failed copying %s to %s", src, dest) } if err := updateChartfile(src, dest, lname); err != nil { log.Die("Failed to update Chart.yaml: %s", err) } }
// Uninstall removes a chart from Kubernetes. // // Manifests are removed from Kubernetes in the following order: // // - Services (to shut down traffic) // - Pods (which can be part of RCs) // - ReplicationControllers // - Volumes // - Secrets // - Namespaces func Uninstall(chartName, home, namespace string, force bool) { if !chartFetched(chartName, home) { log.Info("No chart named %q in your workspace. Nothing to delete.", chartName) return } cd := filepath.Join(home, WorkspaceChartPath, chartName) c, err := chart.Load(cd) if err != nil { log.Die("Failed to load chart: %s", err) } if err := deleteChart(c, namespace, true); err != nil { log.Die("Failed to list charts: %s", err) } if !force && !promptConfirm("Uninstall the listed objects?") { log.Info("Aborted uninstall") return } log.Info("Running `kubectl delete` ...") if err := deleteChart(c, namespace, false); err != nil { log.Die("Failed to completely delete chart: %s", err) } log.Info("Done") }
// ensureHome ensures that a HELM_HOME exists. func ensureHome(home string) { must := []string{home, filepath.Join(home, CachePath), filepath.Join(home, WorkspacePath), filepath.Join(home, CacheChartPath)} for _, p := range must { if fi, err := os.Stat(p); err != nil { log.Debug("Creating %s", p) if err := os.MkdirAll(p, 0755); err != nil { log.Die("Could not create %q: %s", p, err) } } else if !fi.IsDir() { log.Die("%s must be a directory.", home) } } refi := filepath.Join(home, Configfile) if _, err := os.Stat(refi); err != nil { log.Info("Creating %s", refi) // Attempt to create a Repos.yaml if err := ioutil.WriteFile(refi, []byte(config.DefaultConfigfile), 0755); err != nil { log.Die("Could not create %s: %s", refi, err) } } if err := os.Chdir(home); err != nil { log.Die("Could not change to directory %q: %s", home, err) } }
// Remove removes a chart from the workdir. // // - chart is the source // - homedir is the home directory for the user // - force will remove installed charts from workspace func Remove(chart, homedir string, force bool) { chartPath := filepath.Join(homedir, WorkspaceChartPath, chart) if _, err := os.Stat(chartPath); err != nil { log.Die("Chart not found. %s", err) } var connectionFailure bool // check if any chart manifests are installed installed, err := checkManifests(chartPath) if err != nil { if strings.Contains(err.Error(), "unable to connect") { connectionFailure = true } else { log.Die(err.Error()) } } if !force && connectionFailure { log.Err("Could not determine if %s is installed. To remove the chart --force flag must be set.", chart) } else if !force && len(installed) > 0 { log.Err("Found %d installed manifests for %s. To remove a chart that has been installed the --force flag must be set.", len(installed), chart) } else { // remove local chart files if err := os.RemoveAll(chartPath); err != nil { log.Die("Could not remove chart. %s", err) } log.Info("All clear! You have successfully removed %s from your workspace.", chart) } }
// Uninstall removes a chart from Kubernetes. // // Manifests are removed from Kubernetes in the order specified by // chart.UninstallOrder. Any unknown types are removed before that sequence // is run. func Uninstall(chartName, home, namespace string, force bool, client kubectl.Runner) { // This is a stop-gap until kubectl respects namespaces in manifests. if namespace == "" { log.Die("This command requires a namespace. Did you mean '-n default'?") } if !chartFetched(chartName, home) { log.Info("No chart named %q in your workspace. Nothing to delete.", chartName) return } cd := helm.WorkspaceChartDirectory(home, chartName) c, err := chart.Load(cd) if err != nil { log.Die("Failed to load chart: %s", err) } if err := deleteChart(c, namespace, true, client); err != nil { log.Die("Failed to list charts: %s", err) } if !force && !promptConfirm("Uninstall the listed objects?") { log.Info("Aborted uninstall") return } CheckKubePrereqs() log.Info("Running `kubectl delete` ...") if err := deleteChart(c, namespace, false, client); err != nil { log.Die("Failed to completely delete chart: %s", err) } log.Info("Done") }
// Template renders a template to an output file. func Template(out, in, data string) { var dest io.Writer if out != "" { f, err := os.Create(out) if err != nil { log.Die("Failed to open %s: %s", out, err) } defer func() { if err := f.Close(); err != nil { log.Err("Error closing file: %s", err) } }() dest = f } else { dest = log.Stdout } var vals interface{} if data != "" { var err error vals, err = openValues(data) if err != nil { log.Die("Error opening value file: %s", err) } } tpl, err := ioutil.ReadFile(in) if err != nil { log.Die("Failed to read template file: %s", err) } if err := renderTemplate(dest, string(tpl), vals); err != nil { log.Die("Failed: %s", err) } }
// Create a chart // // - chartName being created // - homeDir is the helm home directory for the user func Create(chartName, homeDir string) { chart := newSkelChartfile(chartName) chartDir := filepath.Join(homeDir, WorkspaceChartPath, chartName) // create directories if err := os.MkdirAll(filepath.Join(chartDir, "manifests"), 0755); err != nil { log.Die("Could not create %q: %s", chartDir, err) } // create Chartfile.yaml if err := chart.Save(filepath.Join(chartDir, "Chart.yaml")); err != nil { log.Die("Could not create Chart.yaml: err", err) } // create README.md if err := createReadme(chartDir, chart); err != nil { log.Die("Could not create README.md: err", err) } // create example-pod if err := createExampleManifest(chartDir); err != nil { log.Die("Could not create example manifest: err", err) } log.Info("Created chart in %s", chartDir) }
// ensurePrereqs verifies that Git and Kubectl are both available. func ensurePrereqs() { if _, err := exec.LookPath("git"); err != nil { log.Die("Could not find 'git' on $PATH: %s", err) } if _, err := exec.LookPath("kubectl"); err != nil { log.Die("Could not find 'kubectl' on $PATH: %s", err) } }
// DeleteRepo deletes a repository. func DeleteRepo(homedir, name string) { cfg := mustConfig(homedir) if err := cfg.Repos.Delete(name); err != nil { log.Die("Failed to delete repository: %s", err) } if err := cfg.Save(""); err != nil { log.Die("Deleted repo, but could not save settings: %s", err) } }
// AddRepo adds a repo to the list of repositories. func AddRepo(homedir, name, repository string) { cfg := mustConfig(homedir) if err := cfg.Repos.Add(name, repository); err != nil { log.Die(err.Error()) } if err := cfg.Save(""); err != nil { log.Die("Could not save configuration: %s", err) } }
// Remove removes a chart from the workdir. // // - chart is the source // - homedir is the home directory for the user func Remove(chart string, homedir string) { chartPath := filepath.Join(homedir, WorkspaceChartPath, chart) if _, err := os.Stat(chartPath); err != nil { log.Die("Chart not found. %s", err) } if err := os.RemoveAll(chartPath); err != nil { log.Die("%s", err) } log.Info("All clear! You have successfully removed %s from your workspace.", chart) }
// AddRepo adds a repo to the list of repositories. func AddRepo(homedir, name, repository string) { cfg := mustConfig(homedir) if err := cfg.Repos.Add(name, repository); err != nil { log.Die(err.Error()) } if err := cfg.Save(""); err != nil { log.Die("Could not save configuration: %s", err) } log.Info("Hooray! Successfully added the repo.") }
// Update fetches the remote repo into the home directory. func Update(home string) { home, err := filepath.Abs(home) if err != nil { log.Die("Could not generate absolute path for %q: %s", home, err) } CheckLocalPrereqs(home) rc := mustConfig(home).Repos if err := rc.UpdateAll(); err != nil { log.Die("Not all repos could be updated: %s", err) } log.Info("Done") }
// AltInstall allows loading a chart from the current directory. // // It does not directly support chart tables (repos). func AltInstall(chartName, cachedir, home, namespace string, force bool, dryRun bool) { // Make sure there is a chart in the cachedir. if _, err := os.Stat(filepath.Join(cachedir, "Chart.yaml")); err != nil { log.Die("Expected a Chart.yaml in %s: %s", cachedir, err) } // Make sure there is a manifests dir. if fi, err := os.Stat(filepath.Join(cachedir, "manifests")); err != nil { log.Die("Expected 'manifests/' in %s: %s", cachedir, err) } else if !fi.IsDir() { log.Die("Expected 'manifests/' to be a directory in %s: %s", cachedir, err) } dest := filepath.Join(home, WorkspaceChartPath, chartName) if ok, err := isSamePath(dest, cachedir); err != nil || ok { log.Die("Cannot read from and write to the same place: %s. %v", cachedir, err) } // Copy the source chart to the workspace. We ruthlessly overwrite in // this case. if err := copyDir(cachedir, dest); err != nil { log.Die("Failed to copy %s to %s: %s", cachedir, dest, err) } // Load the chart. c, err := chart.Load(dest) if err != nil { log.Die("Failed to load chart: %s", err) } // Give user the option to bale if dependencies are not satisfied. nope, err := dependency.Resolve(c.Chartfile, filepath.Join(home, WorkspaceChartPath)) if err != nil { log.Warn("Failed to check dependencies: %s", err) if !force { log.Die("Re-run with --force to install anyway.") } } else if len(nope) > 0 { log.Warn("Unsatisfied dependencies:") for _, d := range nope { log.Msg("\t%s %s", d.Name, d.Version) } if !force { log.Die("Stopping install. Re-run with --force to install anyway.") } } msg := "Running `kubectl create -f` ..." if dryRun { msg = "Performing a dry run of `kubectl create -f` ..." } log.Info(msg) if err := uploadManifests(c, namespace, dryRun); err != nil { log.Die("Failed to upload manifests: %s", err) } }
// Install loads a chart into Kubernetes. // // If the chart is not found in the workspace, it is fetched and then installed. // // During install, manifests are sent to Kubernetes in the ordered specified by InstallOrder. func Install(chartName, home, namespace string, force bool, generate bool, exclude []string, client kubectl.Runner) { ochart := chartName r := mustConfig(home).Repos table, chartName := r.RepoChart(chartName) if !chartFetched(chartName, home) { log.Info("No chart named %q in your workspace. Fetching now.", ochart) fetch(chartName, chartName, home, table) } cd := helm.WorkspaceChartDirectory(home, chartName) c, err := chart.Load(cd) if err != nil { log.Die("Failed to load chart: %s", err) } // Give user the option to bale if dependencies are not satisfied. nope, err := dependency.Resolve(c.Chartfile, helm.WorkspaceChartDirectory(home)) if err != nil { log.Warn("Failed to check dependencies: %s", err) if !force { log.Die("Re-run with --force to install anyway.") } } else if len(nope) > 0 { log.Warn("Unsatisfied dependencies:") for _, d := range nope { log.Msg("\t%s %s", d.Name, d.Version) } if !force { log.Die("Stopping install. Re-run with --force to install anyway.") } } // Run the generator if -g is set. if generate { Generate(chartName, home, exclude) } CheckKubePrereqs() log.Info("Running `kubectl create -f` ...") if err := uploadManifests(c, namespace, client); err != nil { log.Die("Failed to upload manifests: %s", err) } log.Info("Done") PrintREADME(chartName, home) }
// Update fetches the remote repo into the home directory. func Update(home string) { home, err := filepath.Abs(home) if err != nil { log.Die("Could not generate absolute path for %q: %s", home, err) } // Basically, install if this is the first run. ensurePrereqs() ensureHome(home) rc := mustConfig(home).Repos if err := rc.UpdateAll(); err != nil { log.Die("Not all repos could be updated: %s", err) } log.Info("Done") }
func search(term, base, table string, charts map[string]*chart.Chartfile) error { dirs, err := filepath.Glob(base) if err != nil { return fmt.Errorf("No results found. %s", err) } else if len(dirs) == 0 { return errors.New("No results found.") } r, err := regexp.Compile(term) if err != nil { log.Die("Invalid expression %q: %s", term, err) } for _, dir := range dirs { cname := filepath.Join(table, filepath.Base(dir)) chrt, err := chart.LoadChartfile(filepath.Join(dir, "Chart.yaml")) if err != nil { // This dir is not a chart. Skip it. continue } else if r.MatchString(chrt.Name) || r.MatchString(chrt.Description) { charts[cname] = chrt } } return nil }
// Publish a chart from the workspace to the cache directory // // - chartName being published // - homeDir is the helm home directory for the user // - force publishing even if the chart directory already exists func Publish(chartName, homeDir, repo string, force bool) { if repo == "" { repo = "charts" } if !mustConfig(homeDir).Repos.Exists(repo) { log.Err("Repo %s does not exist", repo) log.Info("Available repositories") ListRepos(homeDir) return } src := helm.WorkspaceChartDirectory(homeDir, chartName) dst := helm.CacheDirectory(homeDir, repo, chartName) if _, err := os.Stat(dst); err == nil { if force != true { log.Info("chart already exists, use -f to force") return } } if err := helm.CopyDir(src, dst); err != nil { log.Die("failed to publish directory: %v", err) } }
// RepoName gets the name of the Git repo, or an empty string if none is found. func RepoName(chartpath string) string { wd, err := os.Getwd() if err != nil { log.Err("Could not get working directory: %s", err) return "" } defer func() { if err := os.Chdir(wd); err != nil { log.Die("Unrecoverable error: %s", err) } }() if err := os.Chdir(chartpath); err != nil { log.Err("Could not find chartpath %s: %s", chartpath, err) return "" } out, err := exec.Command("git", "config", "--get", "remote.origin.url").CombinedOutput() if err != nil { log.Err("Git failed to get the origin name: %s %s", err, string(out)) return "" } return strings.TrimSpace(string(out)) }
// Fetch gets a chart from the source repo and copies to the workdir. // // - chartName is the source // - lname is the local name for that chart (chart-name); if blank, it is set to the chart. // - homedir is the home directory for the user func Fetch(chartName, lname, homedir string) { r := mustConfig(homedir).Repos repository, chartName := r.RepoChart(chartName) if lname == "" { lname = chartName } fetch(chartName, lname, homedir, repository) chartFilePath := helm.WorkspaceChartDirectory(homedir, lname, Chartfile) cfile, err := chart.LoadChartfile(chartFilePath) if err != nil { log.Die("Source is not a valid chart. Missing Chart.yaml: %s", err) } deps, err := dependency.Resolve(cfile, helm.WorkspaceChartDirectory(homedir)) if err != nil { log.Warn("Could not check dependencies: %s", err) return } if len(deps) > 0 { log.Warn("Unsatisfied dependencies:") for _, d := range deps { log.Msg("\t%s %s", d.Name, d.Version) } } log.Info("Fetched chart into workspace %s", helm.WorkspaceChartDirectory(homedir, lname)) log.Info("Done") }
// openEditor opens the given filename in an interactive editor func openEditor(path string) { editor := os.Getenv("EDITOR") if editor == "" { log.Die("must set shell $EDITOR") } editorPath, err := exec.LookPath(editor) if err != nil { log.Die("Could not find %s in PATH", editor) } cmd := exec.Command(editorPath, path) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := cmd.Run(); err != nil { log.Die("Could not open $EDITOR: %s", err) } }
// Install loads a chart into Kubernetes. // // If the chart is not found in the workspace, it is fetched and then installed. // // During install, manifests are sent to Kubernetes in the following order: // // - Namespaces // - Secrets // - Volumes // - Services // - Pods // - ReplicationControllers func Install(chartName, home, namespace string, force bool, dryRun bool) { ochart := chartName r := mustConfig(home).Repos table, chartName := r.RepoChart(chartName) if !chartFetched(chartName, home) { log.Info("No chart named %q in your workspace. Fetching now.", ochart) fetch(chartName, chartName, home, table) } cd := filepath.Join(home, WorkspaceChartPath, chartName) c, err := chart.Load(cd) if err != nil { log.Die("Failed to load chart: %s", err) } // Give user the option to bale if dependencies are not satisfied. nope, err := dependency.Resolve(c.Chartfile, filepath.Join(home, WorkspaceChartPath)) if err != nil { log.Warn("Failed to check dependencies: %s", err) if !force { log.Die("Re-run with --force to install anyway.") } } else if len(nope) > 0 { log.Warn("Unsatisfied dependencies:") for _, d := range nope { log.Msg("\t%s %s", d.Name, d.Version) } if !force { log.Die("Stopping install. Re-run with --force to install anyway.") } } msg := "Running `kubectl create -f` ..." if dryRun { msg = "Performing a dry run of `kubectl create -f` ..." } log.Info(msg) if err := uploadManifests(c, namespace, dryRun); err != nil { log.Die("Failed to upload manifests: %s", err) } log.Info("Done") PrintREADME(chartName, home) }
// mustConfig parses a config file or dies trying. func mustConfig(homedir string) *config.Configfile { rpath := filepath.Join(homedir, helm.Configfile) cfg, err := config.Load(rpath) if err != nil { log.Die("Could not load %s: %s", rpath, err) } return cfg }
// Target displays information about the cluster func Target() { if _, err := exec.LookPath("kubectl"); err != nil { log.Die("Could not find 'kubectl' on $PATH: %s", err) } c, _ := exec.Command("kubectl", "cluster-info").Output() fmt.Println(string(c)) }
// Uninstall removes a chart from Kubernetes. // // Manifests are removed from Kubernetes in the following order: // // - Services (to shut down traffic) // - Pods (which can be part of RCs) // - ReplicationControllers // - Volumes // - Secrets // - Namespaces func Uninstall(chartName, home, namespace string) { if !chartFetched(chartName, home) { log.Info("No chart named %q in your workspace. Nothing to delete.", chartName) return } cd := filepath.Join(home, WorkspaceChartPath, chartName) c, err := chart.Load(cd) if err != nil { log.Die("Failed to load chart: %s", err) } log.Info("Running `kubectl delete` ...") if err := deleteChart(c, namespace); err != nil { log.Die("Failed to completely delete chart: %s", err) } log.Info("Done") }
func execPlugin(name string, args []string) { cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { log.Die(err.Error()) } }
func fetch(chartName, lname, homedir, chartpath string) { src := filepath.Join(homedir, CachePath, chartpath, chartName) dest := filepath.Join(homedir, WorkspaceChartPath, lname) if fi, err := os.Stat(src); err != nil { log.Die("Chart %s not found in %s", lname, src) } else if !fi.IsDir() { log.Die("Malformed chart %s: Chart must be in a directory.", chartName) } if err := os.MkdirAll(dest, 0755); err != nil { log.Die("Could not create %q: %s", dest, err) } log.Debug("Fetching %s to %s", src, dest) if err := copyDir(src, dest); err != nil { log.Die("Failed copying %s to %s", src, dest) } }
// Edit charts using the shell-defined $EDITOR // // - chartName being edited // - homeDir is the helm home directory for the user func Edit(chartName, homeDir string) { chartDir := util.WorkspaceChartDirectory(homeDir, chartName) if _, err := os.Stat(chartDir); os.IsNotExist(err) { log.Die("Could not find chart: %s", chartName) } openEditor(chartDir) }
// minArgs checks to see if the right number of args are passed. // // If not, it prints an error and quits. func minArgs(c *cli.Context, i int, name string) { if len(c.Args()) < i { m := "arguments" if i == 1 { m = "argument" } log.Err("Expected %d %s", i, m) cli.ShowCommandHelp(c, name) log.Die("") } }
// mustConfig parses a config file or dies trying. func mustConfig(homedir string) *config.Configfile { rpath := filepath.Join(homedir, helm.Configfile) cfg, err := config.Load(rpath) if err != nil { log.Warn("Oops! Looks like we had some issues running your command! Running `helm doctor` to ensure we have all the necessary prerequisites in place...") Doctor(homedir) cfg, err = config.Load(rpath) if err != nil { log.Die("Oops! Could not load %s. Error: %s", rpath, err) } log.Info("Continuing onwards and upwards!") } return cfg }