// 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) }
// 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) }
// joinChart reads chart files and joins them with YAML delimiters func joinChart(chartDir string, files []string) (bytes.Buffer, error) { var output bytes.Buffer for _, f := range files { contents, err := ioutil.ReadFile(f) if err != nil { return output, err } rf, err := filepath.Rel(chartDir, f) if err != nil { log.Warn("Could not find relative path: %s", err) return output, err } delimiter := fmt.Sprintf("--- # %s\n", rf) output.WriteString(delimiter) output.Write(contents) output.WriteString("--- # end\n") } return output, nil }
// listChart enumerates all of the relevant files in a chart func listChart(chartDir string) ([]string, error) { var files []string metadataFile := path.Join(chartDir, "Chart.yaml") manifestDir := path.Join(chartDir, "manifests") // check for existence of important files and directories for _, path := range []string{chartDir, metadataFile, manifestDir} { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } } // add metadata file to front of list files = append(files, metadataFile) // add manifest files walker := func(fname string, fi os.FileInfo, e error) error { if e != nil { log.Warn("Encounter error walking %q: %s", fname, e) return nil } if filepath.Ext(fname) == ".yaml" { files = append(files, fname) } return nil } filepath.Walk(manifestDir, walker) return files, nil }
// Files gets a list of all manifest files inside of a chart. // // chartDir should contain the path to a chart (the directory which // holds a Chart.yaml file). // // This returns an error if it can't access the directory. func Files(chartDir string) ([]string, error) { dir := filepath.Join(chartDir, "manifests") files := []string{} if _, err := os.Stat(dir); err != nil { return files, err } // add manifest files walker := func(fname string, fi os.FileInfo, e error) error { if e != nil { log.Warn("Encountered error walking %q: %s", fname, e) return nil } if fi.IsDir() { return nil } if filepath.Ext(fname) == ".yaml" { files = append(files, fname) } return nil } filepath.Walk(dir, walker) return files, nil }
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) } }
// Copy a directory and its subdirectories. func copyDir(src, dst string) error { var failure error walker := func(fname string, fi os.FileInfo, e error) error { if e != nil { log.Warn("Encounter error walking %q: %s", fname, e) failure = e return nil } log.Info("Copying %s", fname) rf, err := filepath.Rel(src, fname) if err != nil { log.Warn("Could not find relative path: %s", err) return nil } df := filepath.Join(dst, rf) // Handle directories by creating mirrors. if fi.IsDir() { if err := os.MkdirAll(df, fi.Mode()); err != nil { log.Warn("Could not create %q: %s", df, err) failure = err } return nil } // Otherwise, copy files. in, err := os.Open(fname) if err != nil { log.Warn("Skipping file %s: %s", fname, err) return nil } out, err := os.Create(df) if err != nil { in.Close() log.Warn("Skipping file copy %s: %s", fname, err) return nil } if _, err = io.Copy(out, in); err != nil { log.Warn("Copy from %s to %s failed: %s", fname, df, err) } if err := out.Close(); err != nil { log.Warn("Failed to close %q: %s", df, err) } if err := in.Close(); err != nil { log.Warn("Failed to close reader %q: %s", fname, err) } return nil } filepath.Walk(src, walker) return failure }
// sortManifests sorts manifests into their respective categories, adding to the Chart. func sortManifests(chart *Chart, manifests []*manifest.Manifest) { for _, m := range manifests { vo := m.VersionedObject if m.Version != "v1" { log.Warn("Unsupported version %q", m.Version) continue } switch m.Kind { default: log.Warn("No support for kind %s. Ignoring.", m.Kind) case "Pod": o := vo.(*v1.Pod) o.Annotations = setOriginFile(o.Annotations, m.Source) chart.Pods = append(chart.Pods, o) case "ReplicationController": o := vo.(*v1.ReplicationController) o.Annotations = setOriginFile(o.Annotations, m.Source) chart.ReplicationControllers = append(chart.ReplicationControllers, o) case "Service": o := vo.(*v1.Service) o.Annotations = setOriginFile(o.Annotations, m.Source) chart.Services = append(chart.Services, o) case "Secret": o := vo.(*v1.Secret) o.Annotations = setOriginFile(o.Annotations, m.Source) chart.Secrets = append(chart.Secrets, o) case "PersistentVolume": o := vo.(*v1.PersistentVolume) o.Annotations = setOriginFile(o.Annotations, m.Source) chart.PersistentVolumes = append(chart.PersistentVolumes, o) case "Namespace": o := vo.(*v1.Namespace) o.Annotations = setOriginFile(o.Annotations, m.Source) chart.Namespaces = append(chart.Namespaces, o) } } }
// 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 := model.Load(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) } }
// PrintREADME prints the README file (if it exists) to the console. func PrintREADME(chart, home string) { p := filepath.Join(home, WorkspaceChartPath, 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() }
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) } } }
func deleteChart(c *model.Chart, ns string) error { // We delete charts in the ALMOST reverse order that we created them. We // start with services to effectively shut down traffic. Then we delete // rcs and pods. ktype := "service" for _, o := range c.Services { if err := kubectlDelete(o.Name, ktype, ns); err != nil { log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err) } } ktype = "rc" for _, o := range c.ReplicationControllers { if err := kubectlDelete(o.Name, ktype, ns); err != nil { log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err) } } ktype = "pod" for _, o := range c.Pods { if err := kubectlDelete(o.Name, ktype, ns); err != nil { log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err) } } ktype = "secret" for _, o := range c.Secrets { if err := kubectlDelete(o.Name, ktype, ns); err != nil { log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err) } } ktype = "persistentvolume" for _, o := range c.PersistentVolumes { if err := kubectlDelete(o.Name, ktype, ns); err != nil { log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err) } } ktype = "namespace" for _, o := range c.Namespaces { if err := kubectlDelete(o.Name, ktype, ns); err != nil { log.Warn("Could not delete %s %s (Skipping): %s", ktype, o.Name, err) } } return nil }