func TestUpgradeCmd(t *testing.T) { tmpChart, _ := ioutil.TempDir("testdata", "tmp") defer os.RemoveAll(tmpChart) cfile := &chart.Metadata{ Name: "testUpgradeChart", Description: "A Helm chart for Kubernetes", Version: "0.1.0", } chartPath, err := chartutil.Create(cfile, tmpChart) if err != nil { t.Errorf("Error creating chart for upgrade: %v", err) } ch, _ := chartutil.Load(chartPath) _ = releaseMock(&releaseOptions{ name: "funny-bunny", chart: ch, }) // update chart version cfile = &chart.Metadata{ Name: "testUpgradeChart", Description: "A Helm chart for Kubernetes", Version: "0.1.2", } chartPath, err = chartutil.Create(cfile, tmpChart) if err != nil { t.Errorf("Error creating chart: %v", err) } ch, err = chartutil.Load(chartPath) if err != nil { t.Errorf("Error loading updated chart: %v", err) } tests := []releaseCase{ { name: "upgrade a release", args: []string{"funny-bunny", chartPath}, resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}), expected: "funny-bunny has been upgraded. Happy Helming!\n", }, { name: "install a release with 'upgrade --install'", args: []string{"zany-bunny", chartPath}, flags: []string{"-i"}, resp: releaseMock(&releaseOptions{name: "zany-bunny", version: 1, chart: ch}), expected: "zany-bunny has been upgraded. Happy Helming!\n", }, } cmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command { return newUpgradeCmd(c, out) } runReleaseCases(t, tests, cmd) }
// InstallRelease installs a new chart and returns the release response. func (h *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { // load the chart to install chart, err := chartutil.Load(chstr) if err != nil { return nil, err } // apply the install options for _, opt := range opts { opt(&h.opts) } req := &h.opts.instReq req.Chart = chart req.Namespace = ns req.DryRun = h.opts.dryRun req.DisableHooks = h.opts.disableHooks req.ReuseName = h.opts.reuseName ctx := NewContext() if h.opts.before != nil { if err := h.opts.before(ctx, req); err != nil { return nil, err } } return h.install(ctx, req) }
// UpdateRelease updates a release to a new/different chart func (h *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { // load the chart to update chart, err := chartutil.Load(chstr) if err != nil { return nil, err } // apply the update options for _, opt := range opts { opt(&h.opts) } req := &h.opts.updateReq req.Chart = chart req.DryRun = h.opts.dryRun req.Name = rlsName req.DisableHooks = h.opts.disableHooks req.Recreate = h.opts.recreate ctx := NewContext() if h.opts.before != nil { if err := h.opts.before(ctx, req); err != nil { return nil, err } } return h.update(ctx, req) }
// printMissing prints warnings about charts that are present on disk, but are not in the requirements. func (l *dependencyListCmd) printMissing(reqs *chartutil.Requirements, out io.Writer) { folder := filepath.Join(l.chartpath, "charts/*") files, err := filepath.Glob(folder) if err != nil { fmt.Fprintln(l.out, err) return } for _, f := range files { fi, err := os.Stat(f) if err != nil { fmt.Fprintf(l.out, "Warning: %s\n", err) } // Skip anything that is not a directory and not a tgz file. if !fi.IsDir() && filepath.Ext(f) != ".tgz" { continue } c, err := chartutil.Load(f) if err != nil { fmt.Fprintf(l.out, "WARNING: %q is not a chart.\n", f) continue } found := false for _, d := range reqs.Dependencies { if d.Name == c.Metadata.Name { found = true break } } if !found { fmt.Fprintf(l.out, "WARNING: %q is not in requirements.yaml.\n", f) } } }
func loadChart(t *testing.T, name string) *cpb.Chart { c, err := chartutil.Load(filepath.Join(chartsDir, name)) if err != nil { t.Fatalf("failed to load test chart (%q): %s\n", name, err) } return c }
func (l *dependencyListCmd) dependencyStatus(dep *chartutil.Dependency) string { filename := fmt.Sprintf("%s-%s.tgz", dep.Name, dep.Version) archive := filepath.Join(l.chartpath, "charts", filename) if _, err := os.Stat(archive); err == nil { c, err := chartutil.Load(archive) if err != nil { return "corrupt" } if c.Metadata.Name != dep.Name { return "misnamed" } if c.Metadata.Version != dep.Version { return "wrong version" } return "ok" } folder := filepath.Join(l.chartpath, "charts", dep.Name) if fi, err := os.Stat(folder); err != nil { return "missing" } else if !fi.IsDir() { return "mispackaged" } c, err := chartutil.Load(folder) if err != nil { return "corrupt" } if c.Metadata.Name != dep.Name { return "misnamed" } if c.Metadata.Version != dep.Version { return "wrong version" } return "unpacked" }
// Index generates an index for the chart repository and writes an index.yaml file func (r *ChartRepository) Index() error { if r.IndexFile == nil { r.IndexFile = &IndexFile{Entries: make(map[string]*ChartRef)} } existCharts := map[string]bool{} for _, path := range r.ChartPaths { ch, err := chartutil.Load(path) if err != nil { return err } chartfile := ch.Metadata digest, err := generateDigest(path) if err != nil { return err } key := chartfile.Name + "-" + chartfile.Version if r.IndexFile.Entries == nil { r.IndexFile.Entries = make(map[string]*ChartRef) } ref, ok := r.IndexFile.Entries[key] var created string if ok && ref.Created != "" { created = ref.Created } else { created = nowString() } url, _ := url.Parse(r.URL) url.Path = filepath.Join(url.Path, key+".tgz") entry := &ChartRef{Chartfile: chartfile, Name: chartfile.Name, URL: url.String(), Created: created, Digest: digest, Removed: false} r.IndexFile.Entries[key] = entry // chart is existing existCharts[key] = true } // update deleted charts with Removed = true for k := range r.IndexFile.Entries { if _, ok := existCharts[k]; !ok { r.IndexFile.Entries[k].Removed = true } } return r.saveIndexFile() }
// InstallRelease installs a new chart and returns the release response. func (h *Client) InstallRelease(chStr string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) if err != nil { return nil, err } defer c.Close() chart, err := chartutil.Load(chStr) if err != nil { return nil, err } return h.opts.rpcInstallRelease(chart, rls.NewReleaseServiceClient(c), opts...) }
func (l *dependencyListCmd) run() error { c, err := chartutil.Load(l.chartpath) if err != nil { return err } r, err := chartutil.LoadRequirements(c) if err != nil { if err == chartutil.ErrRequirementsNotFound { fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts", l.chartpath) return nil } return err } l.printRequirements(r, l.out) fmt.Fprintln(l.out) l.printMissing(r, l.out) return nil }
func (r *ChartRepository) Index() error { if r.IndexFile == nil { r.IndexFile = &IndexFile{Entries: make(map[string]*ChartRef)} } for _, path := range r.ChartPaths { ch, err := chartutil.Load(path) if err != nil { return err } chartfile := ch.Metadata hash, err := generateChecksum(path) if err != nil { return err } key := chartfile.Name + "-" + chartfile.Version if r.IndexFile.Entries == nil { r.IndexFile.Entries = make(map[string]*ChartRef) } ref, ok := r.IndexFile.Entries[key] var created string if ok && ref.Created != "" { created = ref.Created } else { created = time.Now().UTC().String() } url, _ := url.Parse(r.URL) url.Path = filepath.Join(url.Path, key+".tgz") entry := &ChartRef{Chartfile: chartfile, Name: chartfile.Name, URL: url.String(), Created: created, Checksum: hash, Removed: false} r.IndexFile.Entries[key] = entry } return r.saveIndexFile() }
// IndexDirectory reads a (flat) directory and generates an index. // // It indexes only charts that have been packaged (*.tgz). // // It writes the results to dir/index.yaml. func IndexDirectory(dir, baseURL string) (*IndexFile, error) { archives, err := filepath.Glob(filepath.Join(dir, "*.tgz")) if err != nil { return nil, err } index := NewIndexFile() for _, arch := range archives { fname := filepath.Base(arch) c, err := chartutil.Load(arch) if err != nil { // Assume this is not a chart. continue } hash, err := provenance.DigestFile(arch) if err != nil { return index, err } index.Add(c.Metadata, fname, baseURL, hash) } return index, nil }
func (i *inspectCmd) run() error { chrt, err := chartutil.Load(i.chartpath) if err != nil { return err } cf, err := yaml.Marshal(chrt.Metadata) if err != nil { return err } if i.output == chartOnly || i.output == both { fmt.Fprintln(i.out, string(cf)) } if (i.output == valuesOnly || i.output == both) && chrt.Values != nil { if i.output == both { fmt.Fprintln(i.out, "---") } fmt.Fprintln(i.out, chrt.Values.Raw) } return nil }
func (r *ChartRepository) generateIndex() error { if r.IndexFile == nil { r.IndexFile = NewIndexFile() } for _, path := range r.ChartPaths { ch, err := chartutil.Load(path) if err != nil { return err } digest, err := provenance.DigestFile(path) if err != nil { return err } if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) { r.IndexFile.Add(ch.Metadata, path, r.URL, digest) } // TODO: If a chart exists, but has a different Digest, should we error? } r.IndexFile.SortEntries() return nil }
// Templates lints the templates in the Linter. func Templates(linter *support.Linter) { templatesPath := filepath.Join(linter.ChartDir, "templates") templatesDirExist := linter.RunLinterRule(support.WarningSev, validateTemplatesDir(templatesPath)) // Templates directory is optional for now if !templatesDirExist { return } // Load chart and parse templates, based on tiller/release_server chart, err := chartutil.Load(linter.ChartDir) chartLoaded := linter.RunLinterRule(support.ErrorSev, validateNoError(err)) if !chartLoaded { return } options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: "testNamespace"} valuesToRender, err := chartutil.ToRenderValues(chart, chart.Values, options) if err != nil { // FIXME: This seems to generate a duplicate, but I can't find where the first // error is coming from. //linter.RunLinterRule(support.ErrorSev, err) return } renderedContentMap, err := engine.New().Render(chart, valuesToRender) renderOk := linter.RunLinterRule(support.ErrorSev, validateNoError(err)) if !renderOk { return } /* Iterate over all the templates to check: - It is a .yaml file - All the values in the template file is defined - {{}} include | quote - Generated content is a valid Yaml file - Metadata.Namespace is not set */ for _, template := range chart.Templates { fileName, preExecutedTemplate := template.Name, template.Data linter.RunLinterRule(support.ErrorSev, validateAllowedExtension(fileName)) // We only apply the following lint rules to yaml files if filepath.Ext(fileName) != ".yaml" { continue } // Check that all the templates have a matching value linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, templatesPath, valuesToRender, preExecutedTemplate)) linter.RunLinterRule(support.WarningSev, validateQuotes(fileName, string(preExecutedTemplate))) renderedContent := renderedContentMap[fileName] var yamlStruct K8sYamlStruct // Even though K8sYamlStruct only defines Metadata namespace, an error in any other // key will be raised as well err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) validYaml := linter.RunLinterRule(support.ErrorSev, validateYamlContent(fileName, err)) if !validYaml { continue } linter.RunLinterRule(support.ErrorSev, validateNoNamespace(fileName, yamlStruct)) } }