// 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(chart, home, namespace string, force bool) { if !chartInstalled(chart, home) { log.Info("No installed chart named %q. Installing now.", chart) fetch(chart, chart, home) } cd := filepath.Join(home, WorkspaceChartPath, chart) c, err := model.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.") } } if err := uploadManifests(c, namespace); err != nil { log.Die("Failed to upload manifests: %s", err) } PrintREADME(chart, home) }
// 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 := path.Join(homeDir, "workspace", "charts", chartName) // enumerate chart files files, err := listChart(chartDir) if err != nil { log.Die("could not list chart: %v", err) } // join chart with YAML delimeters contents, err := joinChart(chartDir, files) if err != nil { log.Die("could not join chart data: %v", err) } // write chart to temporary file f, err := ioutil.TempFile(os.TempDir(), "helm-edit") if err != nil { log.Die("could not open tempfile: %v", err) } defer os.Remove(f.Name()) f.Write(contents.Bytes()) f.Close() openEditor(f.Name()) saveChart(chartDir, f.Name()) }
// 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) } }
// saveChart reads a delimited chart and write out its parts // to the workspace directory func saveChart(chartDir string, filename string) error { // read the serialized chart file contents, err := ioutil.ReadFile(filename) if err != nil { return err } chartData := make(map[string][]byte) // use a regular expression to read file paths and content match := delimeterRegexp.FindAllSubmatch(contents, -1) for _, m := range match { chartData[string(m[1])] = m[2] } // save edited chart data to the workspace for k, v := range chartData { fp := path.Join(chartDir, k) if err := ioutil.WriteFile(fp, v, 0644); err != nil { log.Die("could not write chart file", err) } } return nil }
// Update fetches the remote repo into the home directory. func Update(repo, 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) gitrepo := filepath.Join(home, CachePath) git := ensureRepo(repo, gitrepo) if err := gitUpdate(git); err != nil { log.Die("Failed to update from Git: %s", err) } }
// Fetch gets a chart from the source repo and copies to the workdir. // // - chart 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 // - ns is the namespace for this package. If blank, it is set to the DefaultNS. func Fetch(chart, lname, homedir string) { if lname == "" { lname = chart } fetch(chart, lname, homedir) cfile, err := model.LoadChartfile(filepath.Join(homedir, WorkspaceChartPath, chart, "Chart.yaml")) if err != nil { log.Die("Source is not a valid chart. Missing Chart.yaml: %s", err) } deps, err := dependency.Resolve(cfile, filepath.Join(homedir, WorkspaceChartPath)) 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) } } PrintREADME(lname, homedir) }
func Uninstall(chart, home, namespace string) { if !chartInstalled(chart, home) { log.Info("No installed chart named %q. Nothing to delete.", chart) return } cd := filepath.Join(home, WorkspaceChartPath, chart) c, err := model.Load(cd) if err != nil { log.Die("Failed to load chart: %s", err) } if err := deleteChart(c, namespace); err != nil { log.Die("Failed to completely delete chart: %s", err) } }
// ensureHome ensures that a HELM_HOME exists. func ensureHome(home string) { if fi, err := os.Stat(home); err != nil { log.Info("Creating %s", home) for _, p := range helmpaths { pp := filepath.Join(home, p) if err := os.MkdirAll(pp, 0755); err != nil { log.Die("Could not create %q: %s", pp, err) } } } else if !fi.IsDir() { log.Die("%s must be a directory.", home) } if err := os.Chdir(home); err != nil { log.Die("Could not change to directory %q: %s", home, err) } }
// 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)) }
// Create a chart // // - chartName being created // - homeDir is the helm home directory for the user func Create(chartName, homeDir string) { skeletonDir, _ := filepath.Abs("skel") if fi, err := os.Stat(skeletonDir); err != nil { log.Die("Could not find %s: %s", skeletonDir, err) } else if !fi.IsDir() { log.Die("Malformed skeleton: %s: Must be a directory.", skeletonDir) } chartDir := path.Join(homeDir, "workspace", "charts", chartName) // copy skeleton to chart directory if err := copyDir(skeletonDir, chartDir); err != nil { log.Die("failed to copy skeleton directory: %v", err) } }
// ensureRepo ensures that the repo exists and is checked out. func ensureRepo(repo, home string) *vcs.GitRepo { if err := os.Chdir(home); err != nil { log.Die("Could not change to directory %q: %s", home, err) } git, err := vcs.NewGitRepo(repo, home) if err != nil { log.Die("Could not get repository %q: %s", repo, err) } if !git.CheckLocal() { log.Info("Cloning repo into %q. Please wait.", home) if err := git.Get(); err != nil { log.Die("Could not create repository in %q: %s", home, err) } } return git }
func fetch(chart, lname, homedir string) { src := filepath.Join(homedir, CacheChartPath, chart) dest := filepath.Join(homedir, WorkspaceChartPath, lname) if fi, err := os.Stat(src); err != nil { } else if !fi.IsDir() { log.Die("Malformed chart %s: Chart must be in a directory.", chart) } if err := os.MkdirAll(dest, 0755); err != nil { log.Die("Could not create %q: %s", dest, err) } log.Info("Fetching %s to %s", src, dest) if err := copyDir(src, dest); err != nil { log.Die("Failed copying %s to %s", src, dest) } }
func info(c *cli.Context) { a := c.Args() if len(a) == 0 { log.Die("Info requires at least a Chart name") } action.Info(c.Args()[0], home(c)) }
// ensureHome ensures that a HELM_HOME exists. func ensureHome(home string) { must := []string{home, filepath.Join(home, CachePath), filepath.Join(home, WorkspacePath)} for _, p := range must { if fi, err := os.Stat(p); err != nil { log.Info("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) } } if err := os.Chdir(home); err != nil { log.Die("Could not change to directory %q: %s", home, err) } }
// 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("") } }
func AltInstall(chart, cachedir, home, namespace string, force 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) } // Copy the source chart to the workspace. We ruthlessly overwrite in // this case. dest := filepath.Join(home, WorkspaceChartPath, chart) if err := copyDir(cachedir, dest); err != nil { log.Die("Failed to copy %s to %s: %s", cachedir, dest, err) } // Load the chart. c, err := model.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.") } } if err := uploadManifests(c, namespace); err != nil { log.Die("Failed to upload manifests: %s", err) } }
// openEditor opens the given filename in an interactive editor func openEditor(filename string) { var cmd *exec.Cmd editor := os.ExpandEnv("$EDITOR") if editor == "" { log.Die("must set shell $EDITOR") } args := []string{filename} cmd = exec.Command(editor, args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Run() }
// Search looks for packages with 'term' in their name. func Search(term, homedir string) { charts, err := search(term, homedir) if err != nil { log.Die(err.Error()) } log.Info("\n=================") log.Info("Available Charts") log.Info("=================\n") log.Info("") for dir, chart := range charts { log.Info("\t%s (%s %s) - %s", filepath.Base(dir), chart.Name, chart.Version, chart.Description) } }
// openEditor opens the given filename in an interactive editor func openEditor(filename string) { var cmd *exec.Cmd editor := os.ExpandEnv("$EDITOR") if editor == "" { log.Die("must set shell $EDITOR") } cmd = exec.Command(editor, filename) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // TODO: figure out why editor always exits non-zero cmd.Run() }
func Info(chart, homedir string) { chartPath := filepath.Join(homedir, CacheChartPath, chart, "Chart.yaml") log.Info("%s", chartPath) chartModel, err := model.LoadChartfile(chartPath) if err != nil { log.Die("%s - UNKNOWN", chart) } log.Info("Chart: %s", chartModel.Name) log.Info("Description: %s", chartModel.Description) log.Info("Details: %s", chartModel.Details) log.Info("Version: %s", chartModel.Version) log.Info("Website: %s", chartModel.Home) log.Info("From: %s", chartPath) log.Info("Dependencies: %s", chartModel.Dependencies) }
func fetch(c *cli.Context) { home := home(c) a := c.Args() if len(a) == 0 { log.Die("Fetch requires at least a Chart name") } chart := a[0] var lname string if len(a) == 2 { lname = a[1] } action.Fetch(chart, lname, home) }
func Install(chart, home, namespace string) { Fetch(chart, chart, home) log.Info("kubectl --namespace=%q create -f %s.yaml", namespace, chart) if !chartInstalled(chart, home) { log.Info("No installed chart named %q. Installing now.", chart) Fetch(chart, chart, home) } d := filepath.Join(home, WorkspaceChartPath, chart, "manifests") log.Debug("Looking for manifests in %q", d) files, err := manifestFiles(d) if err != nil { log.Die("No manifests to install: %s", err) } for _, f := range files { if err := kubectlCreate(f, namespace); err != nil { log.Warn("Failed to install manifest %q: %s", f, err) } } }
// Search looks for packages with 'term' in their name. func Search(term, homedir string) { dirs, err := search(term, homedir) if err != nil { log.Die(err.Error()) } log.Info("\n=================") log.Info("Available Charts") log.Info("=================\n") for _, d := range dirs { y, err := model.LoadChartfile(filepath.Join(d, "Chart.yaml")) if err != nil { log.Info("\t%s - UNKNOWN", filepath.Base(d)) continue } log.Info("\t%s (%s %s) - %s", filepath.Base(d), y.Name, y.Version, y.Description) } log.Info("") }
// Search looks for packages with 'term' in their name. func Search(term, homedir string) { term = sanitizeTerm(term) sp := filepath.Join(homedir, CacheChartPath, "*"+term+"*") dirs, err := filepath.Glob(sp) if err != nil { log.Die("No results found. %s", err) } log.Info("\n=================") log.Info("Available Charts") log.Info("=================\n") for _, d := range dirs { y, err := model.Load(filepath.Join(d, "Chart.yaml")) if err != nil { log.Info("\t%s - UNKNOWN", filepath.Base(d)) continue } log.Info("\t%s (%s %s) - %s", filepath.Base(d), y.Name, y.Version, y.Description) } log.Info("") }