// 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 := filepath.Join(homedir, WorkspaceChartPath, lname, "Chart.yaml") 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, 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) } } log.Info("Fetched chart into workspace %s", filepath.Join(homedir, WorkspaceChartPath, lname)) log.Info("Done") }
// List lists all of the local charts. func List(homedir string) { md := filepath.Join(homedir, WorkspaceChartPath, "*") charts, err := filepath.Glob(md) if err != nil { log.Warn("Could not find any charts in %q: %s", md, err) } for _, c := range charts { cname := filepath.Base(c) if ch, err := chart.LoadChartfile(filepath.Join(c, "Chart.yaml")); err == nil { log.Info("\t%s (%s %s) - %s", cname, ch.Name, ch.Version, ch.Description) continue } log.Info("\t%s (unknown)", cname) } }
// CheckLatest checks whether this version of Helm is the latest version. // // This does not ensure that this is the latest. If a newer version is found, // this generates a message indicating that. // // The passed-in version is the base version that will be checked against the // remote release list. func CheckLatest(version string) { ver, err := release.LatestVersion() if err != nil { log.Warn("Skipped Helm version check: %s", err) return } current, err := semver.NewVersion(version) if err != nil { log.Warn("Local version %s is not well-formed", version) return } remote, err := semver.NewVersion(ver) if err != nil { log.Warn("Remote version %s is not well-formed", ver) return } if remote.GreaterThan(current) { log.Warn("A new version of Helm is available. You have %s. The latest is %v", version, ver) if dl, err := release.LatestDownloadURL(); err == nil { log.Info("Download version %s here: %s", ver, dl) } } }
// 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) } }
// 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) }
// 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) }
// 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 (r *Repos) deleteRepo(name string) error { rpath := filepath.Join(r.Dir, name) if fi, err := os.Stat(rpath); err != nil || !fi.IsDir() { log.Info("Deleted nothing. No repo named %s", name) return nil } log.Debug("Deleting %s", rpath) return os.RemoveAll(rpath) }
// 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) }
// 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) } // Copy the source chart to the workspace. We ruthlessly overwrite in // this case. dest := filepath.Join(home, WorkspaceChartPath, chartName) 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) } }
// UpdateAll does a git fast-forward pull from each remote repo. func (r *Repos) UpdateAll() error { for _, table := range r.Tables { log.Info("Updating %s", table.Name) rpath := filepath.Join(r.Dir, table.Name) g, err := ensureRepo(table.Repo, rpath) if err != nil { return err } if err := g.Update(); err != nil { return err } } return nil }
// 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") }
// 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 string, force bool) { src := path.Join(homeDir, WorkspaceChartPath, chartName) dst := path.Join(homeDir, CacheChartPath, chartName) if _, err := os.Stat(dst); err == nil { if force != true { log.Info("chart already exists, use -f to force") return } } if err := copyDir(src, dst); err != nil { log.Die("failed to publish directory: %v", err) } }
// kubectlCreate calls `kubectl create` and sends the data via Stdin. // // If dryRun is set to true, then we just output the command that was // going to be run to os.Stdout and return nil. func kubectlCreate(data []byte, ns string, dryRun bool) error { a := []string{"create", "-f", "-"} if ns != "" { a = append([]string{"--namespace=" + ns}, a...) } if dryRun { cmd := "kubectl" for _, arg := range a { cmd = fmt.Sprintf("%s %s", cmd, arg) } cmd = fmt.Sprintf("%s < %s", cmd, data) log.Info(cmd) return nil } c := exec.Command("kubectl", a...) in, err := c.StdinPipe() if err != nil { return err } c.Stdout = os.Stdout c.Stderr = os.Stderr if err := c.Start(); err != nil { return err } log.Debug("File: %s", string(data)) in.Write(data) in.Close() return c.Wait() }