Example #1
0
// 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)
	}
}
Example #2
0
// 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")
}
Example #3
0
// 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")
}
Example #4
0
// Doctor helps you see what's wrong with your Helm Classic setup
func Doctor(home string) {
	log.Info("Checking things locally...")
	CheckLocalPrereqs(home)
	CheckKubePrereqs()

	log.Info("Everything looks good! Happy helming!")
}
Example #5
0
// 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)
}
Example #6
0
// 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)
	}
}
Example #7
0
// EnsureHome ensures that a HELMC_HOME exists.
func EnsureHome(home string) {

	must := []string{home, CacheDirectory(home), filepath.Join(home, workspacePath)}

	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(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)
	}
}
Example #8
0
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)
}
Example #9
0
// UpdateAll does a git fast-forward pull from each remote repo.
func (r *Repos) UpdateAll() error {
	for _, table := range r.Tables {
		log.Info("Checking repository %s", table.Name)
		rpath := filepath.Join(r.Dir, table.Name)
		g, err := ensureRepo(table.Repo, rpath)
		if err != nil {
			return err
		}

		if g.IsDirty() {
			return fmt.Errorf("Repository '%s' is dirty.  Commit changes before updating", table.Name)
		}

		initialVersion, err := g.Version()
		if err != nil {
			return fmt.Errorf("Could not get current sha of repository '%s'.", table.Name)
		}

		if err := g.Update(); err != nil {
			return err
		}
		diff, err := repoChartDiff(rpath, initialVersion)
		if err != nil {
			return err
		}
		printSummary(diff)
	}
	return nil
}
Example #10
0
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)
	}
}
Example #11
0
// Valid returns true if every validation passes.
func (cv *ChartValidation) Valid() bool {
	var valid = true

	fmt.Printf("\nVerifying %s chart is a valid chart...\n", cv.ChartName())
	cv.walk(func(v *Validation) bool {
		v.path = cv.Path
		vv := v.valid()
		if !vv {
			switch v.level {
			case 2:
				cv.ErrorCount = cv.ErrorCount + 1
				msg := v.Message + " : " + strconv.FormatBool(vv)
				log.Err(msg)
			case 1:
				cv.WarningCount = cv.WarningCount + 1
				msg := v.Message + " : " + strconv.FormatBool(vv)
				log.Warn(msg)
			}
		} else {
			msg := v.Message + " : " + strconv.FormatBool(vv)
			log.Info(msg)
		}

		valid = valid && vv
		return valid
	})

	return valid
}
Example #12
0
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)
}
Example #13
0
// 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.")
}
Example #14
0
// 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")
}
Example #15
0
// 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)
}
Example #16
0
// Resolve takes a chart and a location and checks whether the chart's dependencies are satisfied.
//
// The `installdir` is the location where installed charts are located. Typically
// this is in $HELMC_HOME/workspace/charts.
//
// This returns a list of unsatisfied dependencies (NOT an error condition).
//
// It returns an error only if it cannot perform the task of resolving dependencies.
// Failed dependencies to not constitute an error.
func Resolve(cf *chart.Chartfile, installdir string) ([]*chart.Dependency, error) {
	if len(cf.Dependencies) == 0 {
		log.Debug("No dependencies to check. :achievement-unlocked:")
		return []*chart.Dependency{}, nil
	}

	cache, err := dependencyCache(installdir)
	if err != nil {
		log.Debug("Failed to build dependency cache: %s", err)
		return []*chart.Dependency{}, err
	}

	res := []*chart.Dependency{}

	// TODO: This could be made more efficient.
	for _, check := range cf.Dependencies {
		resolved := false
		for n, chart := range cache {
			log.Debug("Checking if %s (%s) %s meets %s %s", chart.Name, n, chart.Version, check.Name, check.Version)
			if chart.From != nil {
				log.Debug("✔︎")
				if satisfies(chart.From, check) {
					resolved = true
					break
				}
			} else {
				log.Info("Chart %s is pre-0.2.0. Legacy mode enabled.", chart.Name)
				if chart.Name == check.Name && check.VersionOK(chart.Version) {
					log.Debug("✔︎")
					resolved = true
					break
				}
			}
		}
		if !resolved {
			log.Debug("No matches found for %s %s", check.Name, check.Version)
			res = append(res, check)
		}
	}
	return res, nil
}
Example #17
0
func uninstallKind(kind []*manifest.Manifest, ns, ktype string, dry bool, client kubectl.Runner) {
	for _, o := range kind {
		if dry {
			log.Msg("%s/%s", ktype, o.Name)
		} else {
			// If it's a keeper manifest, skip uninstall.
			if data, err := o.VersionedObject.JSON(); err == nil {
				if manifest.IsKeeper(data) {
					log.Warn("Not uninstalling %s %s because of \"helm-keep\" annotation.\n"+
						"---> Use kubectl to uninstall keeper manifests.\n", ktype, o.Name)
					continue
				}
			}
			out, err := client.Delete(o.Name, ktype, ns)
			if err != nil {
				log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err)
			}
			log.Info(string(out))
		}
	}
}
Example #18
0
// CheckLatest checks whether this version of Helm Classic 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 Classic 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 Classic is available. You have %s. The latest is %v", version, ver)
		log.Info("Download version %s by running: %s", ver, "curl -s https://get.helm.sh | bash")
	}

}
Example #19
0
// 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)
}
Example #20
0
func printOrQuiet(c *cli.Context, msg string, v ...interface{}) {
	if !c.Bool("quiet") {
		log.Info(msg, v...)
	}
}
Example #21
0
// Lint validates that a chart is well-formed
//
// - chartPath path to chart directory
func Lint(chartPath string) {
	cv := new(validation.ChartValidation)

	chartPresenceValidation := cv.AddError("Chart found at "+chartPath, func(path string, v *validation.Validation) bool {
		stat, err := os.Stat(chartPath)
		cv.Path = chartPath

		return err == nil && stat.Mode().IsDir()
	})

	chartYamlPresenceValidation := chartPresenceValidation.AddError("Chart.yaml is present", func(path string, v *validation.Validation) bool {
		stat, err := os.Stat(v.ChartYamlPath())

		return err == nil && stat.Mode().IsRegular()
	})

	chartYamlValidation := chartYamlPresenceValidation.AddError("Chart.yaml is valid yaml", func(path string, v *validation.Validation) bool {
		chartfile, err := v.Chartfile()
		if err == nil {
			cv.Chartfile = chartfile
		}

		return err == nil
	})

	chartYamlNameValidation := chartYamlValidation.AddError("Chart.yaml has a name field", func(path string, v *validation.Validation) bool {
		return cv.Chartfile.Name != ""
	})

	chartYamlNameValidation.AddError("Name declared in Chart.yaml is the same as directory name.", func(path string, v *validation.Validation) bool {
		return cv.Chartfile.Name == cv.ChartName()
	})

	chartYamlValidation.AddError("Chart.yaml has a version field", func(path string, v *validation.Validation) bool {
		return cv.Chartfile.Version != ""
	})

	chartYamlValidation.AddWarning("Chart.yaml has a description field", func(path string, v *validation.Validation) bool {
		return cv.Chartfile.Description != ""
	})

	chartYamlValidation.AddWarning("Chart.yaml has a maintainers field", func(path string, v *validation.Validation) bool {
		return cv.Chartfile.Maintainers != nil
	})

	chartPresenceValidation.AddWarning("README.md is present and not empty", func(path string, v *validation.Validation) bool {
		readmePath := filepath.Join(path, "README.md")
		stat, err := os.Stat(readmePath)

		return err == nil && stat.Mode().IsRegular() && stat.Size() > 0
	})

	manifestsValidation := chartPresenceValidation.AddError("Manifests directory is present", func(path string, v *validation.Validation) bool {
		stat, err := os.Stat(v.ChartManifestsPath())

		return err == nil && stat.Mode().IsDir()
	})

	manifestsParsingValidation := manifestsValidation.AddError("Manifests are valid yaml", func(path string, v *validation.Validation) bool {
		manifests, err := manifest.ParseDir(cv.Path)
		if err == nil {
			cv.Manifests = manifests
		}

		return err == nil && cv.Manifests != nil
	})

	manifestsParsingValidation.AddWarning("Manifests have correct and valid metadata", func(path string, v *validation.Validation) bool {

		success := true
		validKinds := InstallOrder

		for _, m := range cv.Manifests {
			meta, _ := m.VersionedObject.Meta()
			if meta.Name == "" || len(meta.Name) > MaxMetadataNameLength {
				success = false
			}

			if match, _ := regexp.MatchString(`[a-z]([-a-z0-9]*[a-z0-9])?`, meta.Name); !match {
				success = false
			}

			val, ok := meta.Labels["heritage"]
			if !ok || (val != "helm") {
				success = false
			}

			kind := meta.Kind
			validManifestKind := false

			for _, validKind := range validKinds {
				if kind == validKind {
					validManifestKind = true
				}
			}

			if validManifestKind == false {
				success = false
			}
		}

		return success
	})

	if cv.Valid() {
		log.Info("Chart [%s] has passed all necessary checks", cv.ChartName())
	} else {
		if cv.ErrorCount > 0 {
			log.Err("Chart [%s] has failed some necessary checks. Check out the error and warning messages listed.", cv.ChartName())
		} else {
			log.Warn("Chart [%s] has passed all necessary checks but failed some checks as well. Proceed with caution. Check out the warnings listed.", cv.ChartName())
		}
	}
}