func TestLintEmptyChartYaml(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) chartName := "badChart" Create(chartName, tmpHome) badChartYaml, _ := yaml.Marshal(make(map[string]string)) chartYaml := util.WorkspaceChartDirectory(tmpHome, chartName, Chartfile) os.Remove(chartYaml) ioutil.WriteFile(chartYaml, badChartYaml, 0644) output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartName)) }) test.ExpectContains(t, output, "Chart.yaml has a name field : false") test.ExpectContains(t, output, "Chart.yaml has a version field : false") test.ExpectContains(t, output, "Chart.yaml has a description field : false") test.ExpectContains(t, output, "Chart.yaml has a maintainers field : false") test.ExpectContains(t, output, fmt.Sprintf("Chart [%s] has failed some necessary checks", chartName)) }
// 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") }
// 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, force) } 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) }
func TestLintMissingReadme(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) chartName := "badChart" Create(chartName, tmpHome) os.Remove(filepath.Join(util.WorkspaceChartDirectory(tmpHome, chartName), "README.md")) output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartName)) }) test.ExpectContains(t, output, "README.md is present and not empty : false") }
// 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) } }
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, in %s. Running `helmc 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) } }
func lint(c *cli.Context) { home := home(c) all := c.Bool("all") if all { action.LintAll(home) return } minArgs(c, 1, "lint") a := c.Args() chartNameOrPath := a[0] fromHome := util.WorkspaceChartDirectory(home, chartNameOrPath) fromAbs := filepath.Clean(chartNameOrPath) _, err := os.Stat(fromAbs) if err == nil { action.Lint(fromAbs) } else { action.Lint(fromHome) } }
// 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") }
func createWithChart(chart *chart.Chartfile, chartName, homeDir string) { chartDir := helm.WorkspaceChartDirectory(homeDir, 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, Chartfile)); 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) }
func TestLintMissingChartYaml(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) chartName := "badChart" Create(chartName, tmpHome) os.Remove(filepath.Join(util.WorkspaceChartDirectory(tmpHome, chartName), Chartfile)) output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartName)) }) test.ExpectContains(t, output, "Chart.yaml is present : false") test.ExpectContains(t, output, "Chart [badChart] has failed some necessary checks.") }
func TestLintMissingManifestDirectory(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) chartName := "brokeChart" Create(chartName, tmpHome) os.RemoveAll(filepath.Join(util.WorkspaceChartDirectory(tmpHome, chartName), "manifests")) output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartName)) }) test.ExpectMatches(t, output, "Manifests directory is present : false") test.ExpectContains(t, output, "Chart ["+chartName+"] has failed some necessary checks") }
// Check by chart directory name whether a chart is fetched into the workspace. // // This does NOT check the Chart.yaml file. func chartFetched(chartName, home string) bool { p := helm.WorkspaceChartDirectory(home, chartName, Chartfile) log.Debug("Looking for %q", p) if fi, err := os.Stat(p); err != nil || fi.IsDir() { log.Debug("No chart: %s", err) return false } return true }
// Edit charts using the shell-defined $EDITOR // // - chartName being edited // - homeDir is the Helm Classic 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) }
func TestLintBadPath(t *testing.T) { tmpHome := test.CreateTmpHome() chartName := "badChart" output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartName)) }) msg := "Chart found at " + tmpHome + "/workspace/charts/" + chartName + " : false" test.ExpectContains(t, output, msg) }
func TestFetch(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) chartName := "kitchensink" actual := test.CaptureOutput(func() { Fetch(chartName, "", tmpHome) }) workspacePath := util.WorkspaceChartDirectory(tmpHome, chartName) test.ExpectContains(t, actual, "Fetched chart into workspace "+workspacePath) }
func TestLintChartByPath(t *testing.T) { home1 := test.CreateTmpHome() home2 := test.CreateTmpHome() chartName := "goodChart" action.Create(chartName, home1) output := test.CaptureOutput(func() { Cli().Run([]string{"helmc", "--home", home2, "lint", util.WorkspaceChartDirectory(home1, chartName)}) }) test.ExpectContains(t, output, fmt.Sprintf("Chart [%s] has passed all necessary checks", chartName)) }
func TestLintMismatchedChartNameAndDir(t *testing.T) { tmpHome := test.CreateTmpHome() chartName := "chart-0" chartDir := "chart-1" chart := newSkelChartfile(chartName) createWithChart(chart, chartDir, tmpHome) output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartDir)) }) test.ExpectContains(t, output, "Name declared in Chart.yaml is the same as directory name. : false") }
// List lists all of the local charts. func List(homedir string) { md := helm.WorkspaceChartDirectory(homedir, "*") 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, Chartfile)); err == nil { log.Info("\t%s (%s %s) - %s", cname, ch.Name, ch.Version, ch.Description) continue } log.Info("\t%s (unknown)", cname) } }
// LintAll vlaidates all charts are well-formed // // - homedir is the home directory for the user func LintAll(homedir string) { md := util.WorkspaceChartDirectory(homedir, "*") chartPaths, err := filepath.Glob(md) if err != nil { log.Warn("Could not find any charts in %q: %s", md, err) } if len(chartPaths) == 0 { log.Warn("Could not find any charts in %q", md) } else { for _, chartPath := range chartPaths { Lint(chartPath) } } }
func TestLintSuccess(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) chartName := "goodChart" Create(chartName, tmpHome) output := test.CaptureOutput(func() { Lint(util.WorkspaceChartDirectory(tmpHome, chartName)) }) expected := "Chart [goodChart] has passed all necessary checks" test.ExpectContains(t, output, expected) }
func TestGenerate(t *testing.T) { ch := "generate" homedir := test.CreateTmpHome() test.FakeUpdate(homedir) Fetch(ch, ch, homedir) Generate(ch, homedir, []string{"ignore"}, true) // Now we should be able to load and read the `pod.yaml` file. path := util.WorkspaceChartDirectory(homedir, "generate/manifests/pod.yaml") d, err := ioutil.ReadFile(path) if err != nil { t.Fatal(err) } pod := string(d) test.ExpectContains(t, pod, "image: ozo") test.ExpectContains(t, pod, "name: www-server") }
func TestLintAll(t *testing.T) { tmpHome := test.CreateTmpHome() test.FakeUpdate(tmpHome) missingReadmeChart := "missingReadme" action.Create(missingReadmeChart, tmpHome) os.Remove(util.WorkspaceChartDirectory(tmpHome, missingReadmeChart, "README.md")) action.Create("goodChart", tmpHome) output := test.CaptureOutput(func() { Cli().Run([]string{"helmc", "--home", tmpHome, "lint", "--all"}) }) test.ExpectMatches(t, output, "A README file was not found.*"+missingReadmeChart) test.ExpectContains(t, output, "Chart [goodChart] has passed all necessary checks") test.ExpectContains(t, output, "Chart [missingReadme] failed some checks") }
// Generate runs generators on the entire chart. // // By design, this only operates on workspaces, as it should never be run // on the cache. func Generate(chart, homedir string, exclude []string, force bool) { if abs, err := filepath.Abs(homedir); err == nil { homedir = abs } chartPath := util.WorkspaceChartDirectory(homedir, chart) // Although helmc itself may use the new HELMC_HOME environment variable to optionally define its // home directory, to maintain compatibility with charts created for the ORIGINAL helm, we // continue to support expansion of these "legacy" environment variables, including HELM_HOME. os.Setenv("HELM_HOME", homedir) os.Setenv("HELM_DEFAULT_REPO", mustConfig(homedir).Repos.Default) os.Setenv("HELM_FORCE_FLAG", strconv.FormatBool(force)) count, err := generator.Walk(chartPath, exclude, force) if err != nil { log.Die("Failed to complete generation: %s", err) } log.Info("Ran %d generators.", count) }
// PrintREADME prints the README file (if it exists) to the console. func PrintREADME(chart, home string) { p := helm.WorkspaceChartDirectory(home, chart, "README.*") files, err := filepath.Glob(p) if err != nil || len(files) == 0 { // No README. Skip. log.Debug("No readme in %s", p) return } f, err := os.Open(files[0]) if err != nil { log.Warn("Could not read README: %s", err) return } log.Msg(strings.Repeat("=", 40)) io.Copy(log.Stdout, f) log.Msg(strings.Repeat("=", 40)) f.Close() }
// 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 := helm.WorkspaceChartDirectory(homedir, chart) if _, err := os.Stat(chartPath); err != nil { log.Err("Chart not found. %s", err) return } if !force { 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 connectionFailure { log.Err("Could not determine if %s is installed. To remove the chart --force flag must be set.", chart) return } else if 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) return } } // 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) }
func TestCreate(t *testing.T) { tmpHome := test.CreateTmpHome() Create("mychart", tmpHome) // assert chartfile chartfile, err := ioutil.ReadFile(util.WorkspaceChartDirectory(tmpHome, "mychart/Chart.yaml")) if err != nil { t.Errorf("Could not read chartfile: %s", err) } actualChartfile := string(chartfile) expectedChartfile := `name: mychart home: http://example.com/your/project/home version: 0.1.0 description: Provide a brief description of your application here. maintainers: - Your Name <email@address> details: |- This section allows you to provide additional details about your application. Provide any information that would be useful to users at a glance. ` test.ExpectEquals(t, actualChartfile, expectedChartfile) // asset readme readme, err := ioutil.ReadFile(util.WorkspaceChartDirectory(tmpHome, "mychart/README.md")) if err != nil { t.Errorf("Could not read README.md: %s", err) } actualReadme := string(readme) expectedReadme := `# mychart Describe your chart here. Link to upstream repositories, Docker images or any external documentation. If your application requires any specific configuration like Secrets, you may include that information here. ` test.ExpectEquals(t, expectedReadme, actualReadme) // assert example manifest manifest, err := ioutil.ReadFile(util.WorkspaceChartDirectory(tmpHome, "mychart/manifests/example-pod.yaml")) if err != nil { t.Errorf("Could not read manifest: %s", err) } actualManifest := string(manifest) expectedManifest := `--- apiVersion: v1 kind: Pod metadata: name: example-pod labels: heritage: helm spec: restartPolicy: Never containers: - name: example image: "alpine:3.2" command: ["/bin/sleep","9000"] ` test.ExpectEquals(t, actualManifest, expectedManifest) }