From 25053e6adabd4d31edd036514b21527a384cea4f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 24 May 2016 17:21:41 -0600 Subject: [PATCH 1/2] feat(pkg/chartutil): add chartutil package --- pkg/chartutil/chartfile.go | 26 ++ pkg/chartutil/chartfile_test.go | 69 +++++ pkg/chartutil/doc.go | 28 ++ pkg/chartutil/load.go | 274 ++++++++++++++++++ pkg/chartutil/load_test.go | 81 ++++++ pkg/chartutil/testdata/albatross/Chart.yaml | 4 + pkg/chartutil/testdata/albatross/values.toml | 1 + pkg/chartutil/testdata/chartfiletest.yaml | 15 + pkg/chartutil/testdata/frobnitz-1.2.3.tgz | Bin 0 -> 3491 bytes pkg/chartutil/testdata/frobnitz/Chart.yaml | 15 + pkg/chartutil/testdata/frobnitz/INSTALL.txt | 1 + pkg/chartutil/testdata/frobnitz/LICENSE | 1 + pkg/chartutil/testdata/frobnitz/README.md | 11 + .../frobnitz/charts/alpine/Chart.yaml | 4 + .../testdata/frobnitz/charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.toml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 435 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../frobnitz/charts/alpine/values.toml | 2 + .../frobnitz/charts/mariner-4.3.2.tgz | Bin 0 -> 905 bytes .../testdata/frobnitz/docs/README.md | 1 + pkg/chartutil/testdata/frobnitz/icon.svg | 8 + .../testdata/frobnitz/templates/template.tpl | 1 + pkg/chartutil/testdata/frobnitz/values.toml | 6 + pkg/chartutil/testdata/genfrob.sh | 12 + pkg/chartutil/testdata/mariner/Chart.yaml | 4 + .../mariner/charts/albatross-0.1.0.tgz | Bin 0 -> 321 bytes .../mariner/templates/placeholder.tpl | 1 + pkg/chartutil/testdata/mariner/values.toml | 4 + 30 files changed, 602 insertions(+) create mode 100644 pkg/chartutil/chartfile.go create mode 100644 pkg/chartutil/chartfile_test.go create mode 100644 pkg/chartutil/doc.go create mode 100644 pkg/chartutil/load.go create mode 100644 pkg/chartutil/load_test.go create mode 100644 pkg/chartutil/testdata/albatross/Chart.yaml create mode 100644 pkg/chartutil/testdata/albatross/values.toml create mode 100644 pkg/chartutil/testdata/chartfiletest.yaml create mode 100644 pkg/chartutil/testdata/frobnitz-1.2.3.tgz create mode 100644 pkg/chartutil/testdata/frobnitz/Chart.yaml create mode 100644 pkg/chartutil/testdata/frobnitz/INSTALL.txt create mode 100644 pkg/chartutil/testdata/frobnitz/LICENSE create mode 100644 pkg/chartutil/testdata/frobnitz/README.md create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/README.md create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.toml create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml create mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/values.toml create mode 100644 pkg/chartutil/testdata/frobnitz/charts/mariner-4.3.2.tgz create mode 100644 pkg/chartutil/testdata/frobnitz/docs/README.md create mode 100644 pkg/chartutil/testdata/frobnitz/icon.svg create mode 100644 pkg/chartutil/testdata/frobnitz/templates/template.tpl create mode 100644 pkg/chartutil/testdata/frobnitz/values.toml create mode 100755 pkg/chartutil/testdata/genfrob.sh create mode 100644 pkg/chartutil/testdata/mariner/Chart.yaml create mode 100644 pkg/chartutil/testdata/mariner/charts/albatross-0.1.0.tgz create mode 100644 pkg/chartutil/testdata/mariner/templates/placeholder.tpl create mode 100644 pkg/chartutil/testdata/mariner/values.toml diff --git a/pkg/chartutil/chartfile.go b/pkg/chartutil/chartfile.go new file mode 100644 index 000000000..cd5efbc7b --- /dev/null +++ b/pkg/chartutil/chartfile.go @@ -0,0 +1,26 @@ +package chartutil + +import ( + "io/ioutil" + + "github.com/ghodss/yaml" + "github.com/kubernetes/helm/pkg/proto/hapi/chart" +) + +func UnmarshalChartfile(data []byte) (*chart.Metadata, error) { + y := &chart.Metadata{} + err := yaml.Unmarshal(data, y) + if err != nil { + return nil, err + } + return y, nil +} + +// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. +func LoadChartfile(filename string) (*chart.Metadata, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + return UnmarshalChartfile(b) +} diff --git a/pkg/chartutil/chartfile_test.go b/pkg/chartutil/chartfile_test.go new file mode 100644 index 000000000..4d6dbf6e1 --- /dev/null +++ b/pkg/chartutil/chartfile_test.go @@ -0,0 +1,69 @@ +package chartutil + +import ( + "testing" + + "github.com/kubernetes/helm/pkg/proto/hapi/chart" +) + +const testfile = "testdata/chartfiletest.yaml" + +func TestLoadChartfile(t *testing.T) { + f, err := LoadChartfile(testfile) + if err != nil { + t.Errorf("Failed to open %s: %s", testfile, err) + return + } + verifyChartfile(t, f) +} + +func verifyChartfile(t *testing.T, f *chart.Metadata) { + + if f.Name != "frobnitz" { + t.Errorf("Expected frobnitz, got %s", f.Name) + } + + if f.Description != "This is a frobnitz." { + t.Errorf("Unexpected description %q", f.Description) + } + + if f.Version != "1.2.3" { + t.Errorf("Unexpected version %q", f.Version) + } + + if len(f.Maintainers) != 2 { + t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) + } + + if f.Maintainers[0].Name != "The Helm Team" { + t.Errorf("Unexpected maintainer name.") + } + + if f.Maintainers[1].Email != "nobody@example.com" { + t.Errorf("Unexpected maintainer email.") + } + + if len(f.Sources) != 1 { + t.Fatalf("Unexpected number of sources") + } + + if f.Sources[0] != "https://example.com/foo/bar" { + t.Errorf("Expected https://example.com/foo/bar, got %s", f.Sources) + } + + if f.Home != "http://example.com" { + t.Error("Unexpected home.") + } + + if len(f.Keywords) != 3 { + t.Error("Unexpected keywords") + } + + kk := []string{"frobnitz", "sprocket", "dodad"} + for i, k := range f.Keywords { + if kk[i] != k { + t.Errorf("Expected %q, got %q", kk[i], k) + } + } + +} diff --git a/pkg/chartutil/doc.go b/pkg/chartutil/doc.go new file mode 100644 index 000000000..0191c3800 --- /dev/null +++ b/pkg/chartutil/doc.go @@ -0,0 +1,28 @@ +/*Package chartutil contains tools for working with charts. + +Charts are described in the protocol buffer definition (pkg/proto/hapi/charts). +This packe provides utilities for serializing and deserializing charts. + +A chart can be represented on the file system in one of two ways: + + - As a directory that contains a Chart.yaml file and other chart things. + - As a tarred gzipped file containing a directory that then contains a + Chart.yaml file. + +This package provides utilitites for working with those file formats. + +The preferred way of loading a chart is using 'chartutil.Load`: + + chart, err := chartutil.Load(filename) + +This will attempt to discover whether the file at 'filename' is a directory or +a chart archive. It will then load accordingly. + +For accepting raw compressed tar file data from an io.Reader, the +'chartutil.LoadArchive()' will read in the data, uncompress it, and unpack it +into a Chart. + +When creating charts in memory, use the 'github.com/kubernetes/helm/pkg/proto/happy/chart' +package directly. +*/ +package chartutil diff --git a/pkg/chartutil/load.go b/pkg/chartutil/load.go new file mode 100644 index 000000000..00ca9489e --- /dev/null +++ b/pkg/chartutil/load.go @@ -0,0 +1,274 @@ +package chartutil + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/golang/protobuf/ptypes/any" + "github.com/kubernetes/helm/pkg/proto/hapi/chart" +) + +// Load takes a string name, tries to resolve it to a file or directory, and then loads it. +// +// This is the preferred way to load a chart. It will discover the chart encoding +// and hand off to the appropriate chart reader. +func Load(name string) (*chart.Chart, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + return LoadDir(name) + } + return LoadFile(name) +} + +// subchart is an intermediate representation of a dependency. +// +// It is used to temporarily store a dependency while we process the outer +// file. +type subchart []*afile + +func newSubchart() subchart { + return []*afile{} +} + +func (s subchart) add(name string, data []byte, arch bool) subchart { + s = append(s, &afile{name, data, arch}) + return s +} + +// afile represents an archive file buffered for later processing. +type afile struct { + name string + data []byte + archive bool +} + +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (*chart.Chart, error) { + sc := map[string]subchart{} + unzipped, err := gzip.NewReader(in) + if err != nil { + return nil, err + } + defer unzipped.Close() + + c := &chart.Chart{} + b := bytes.NewBuffer(nil) + + tr := tar.NewReader(unzipped) + for { + hd, err := tr.Next() + if err == io.EOF { + // We're done with the reader. Now add subcharts and exit. + e := addSubcharts(c, sc) + return c, e + } + if err != nil { + return c, err + } + + if hd.FileInfo().IsDir() { + // Use this instead of hd.Typeflag because we don't have to do any + // inference chasing. + continue + } + + parts := strings.Split(hd.Name, "/") + n := strings.Join(parts[1:], "/") + + if _, err := io.Copy(b, tr); err != nil { + return c, err + } + + if strings.HasPrefix(n, "charts/") { + // If there are subcharts, we put those into a temporary holding + // array for later processing. + fmt.Printf("Appending %s to chart %s:\n%s\n", n, c.Metadata.Name, b.String()) + appendSubchart(sc, n, b.Bytes()) + b.Reset() + continue + } + + addToChart(c, n, b.Bytes()) + b.Reset() + } + +} + +// LoadFile loads from an archive file. +func LoadFile(name string) (*chart.Chart, error) { + if fi, err := os.Stat(name); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory") + } + + raw, err := os.Open(name) + if err != nil { + return nil, err + } + defer raw.Close() + + return LoadArchive(raw) +} + +// LoadDir loads from a directory. +// +// This loads charts only from directories. +func LoadDir(dir string) (*chart.Chart, error) { + topdir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + topdir += string(filepath.Separator) + sc := map[string]subchart{} + c := &chart.Chart{} + err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error { + n := strings.TrimPrefix(name, topdir) + if err != nil { + return err + } + if fi.IsDir() { + return nil + } + + data, err := ioutil.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading %s: %s", n, err) + } + + if strings.HasPrefix(n, "charts/") { + appendSubchart(sc, n, data) + return nil + } + + return addToChart(c, n, data) + }) + if err != nil { + return c, err + } + + // Ensure that we had a Chart.yaml file + if c.Metadata == nil || c.Metadata.Name == "" { + return c, errors.New("chart metadata (Chart.yaml) missing") + } + + err = addSubcharts(c, sc) + return c, err +} + +func addToChart(c *chart.Chart, n string, data []byte) error { + fmt.Printf("--> Scanning %s\n", n) + if n == "Chart.yaml" { + md, err := UnmarshalChartfile(data) + if err != nil { + return err + } + if md.Name == "" { + fmt.Printf("Chart:\n%s\n", string(data)) + } + fmt.Printf("--> Adding %s as Chart.yaml\n", md.Name) + c.Metadata = md + } else if n == "values.toml" { + c.Values = &chart.Config{Raw: string(data)} + fmt.Printf("--> Adding to values:\n%s\n", string(data)) + } else if strings.HasPrefix(n, "charts/") { + // SKIP THESE. These are handled elsewhere, because they need context + // to process. + return nil + } else if strings.HasPrefix(n, "templates/") { + c.Templates = append(c.Templates, &chart.Template{Name: n, Data: data}) + } else { + c.Files = append(c.Files, &any.Any{TypeUrl: n, Value: data}) + } + return nil +} + +func addSubcharts(c *chart.Chart, s map[string]subchart) error { + for n, sc := range s { + fmt.Printf("===> Unpacking %s\n", n) + if err := addSubchart(c, sc); err != nil { + return fmt.Errorf("error adding %q: %s", n, err) + } + } + return nil +} + +// addSubchart transforms a subchart to a new chart, and then embeds it into the given chart. +func addSubchart(c *chart.Chart, sc subchart) error { + nc := &chart.Chart{} + deps := map[string]subchart{} + + // The sc paths are all relative to the sc itself. + for _, sub := range sc { + if sub.archive { + b := bytes.NewBuffer(sub.data) + var err error + nc, err = LoadArchive(b) + if err != nil { + fmt.Printf("Bad data in %s: %q", sub.name, string(sub.data)) + return err + } + break + } else if strings.HasPrefix(sub.name, "charts/") { + appendSubchart(deps, sub.name, sub.data) + } else { + fmt.Printf("Adding %s to subchart in %s\n", sub.name, c.Metadata.Name) + addToChart(nc, sub.name, sub.data) + } + } + + if nc.Metadata == nil || nc.Metadata.Name == "" { + return errors.New("embedded chart is not well-formed") + } + + fmt.Printf("Added dependency: %q\n", nc.Metadata.Name) + c.Dependencies = append(c.Dependencies, nc) + return nil +} + +func appendSubchart(sc map[string]subchart, n string, b []byte) { + fmt.Printf("Append subchart %s\n", n) + // TODO: Do we need to filter out 0 byte files? + // TODO: If this finds a dependency that is a tarball, we need to untar it, + // and express it as a subchart. + parts := strings.SplitN(n, "/", 3) + lp := len(parts) + switch lp { + case 2: + if filepath.Ext(parts[1]) == ".tgz" { + fmt.Printf("--> Adding archive %s\n", n) + // Basically, we delay expanding tar files until the last minute, + // which helps (a little) keep memory usage down. + bn := strings.TrimSuffix(parts[1], ".tgz") + cc := newSubchart() + sc[bn] = cc.add(parts[1], b, true) + return + } else { + // Skip directory entries and non-charts. + return + } + case 3: + if _, ok := sc[parts[1]]; !ok { + sc[parts[1]] = newSubchart() + } + //fmt.Printf("Adding file %q to %s\n", parts[2], parts[1]) + sc[parts[1]] = sc[parts[1]].add(parts[2], b, false) + return + default: + // Skip 1 or 0. + return + } + +} diff --git a/pkg/chartutil/load_test.go b/pkg/chartutil/load_test.go new file mode 100644 index 000000000..dae3ee438 --- /dev/null +++ b/pkg/chartutil/load_test.go @@ -0,0 +1,81 @@ +package chartutil + +import ( + "testing" + + "github.com/kubernetes/helm/pkg/proto/hapi/chart" +) + +func TestLoadDir(t *testing.T) { + c, err := Load("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) +} + +func TestLoadFile(t *testing.T) { + c, err := Load("testdata/frobnitz-1.2.3.tgz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) +} + +func verifyChart(t *testing.T, c *chart.Chart) { + if len(c.Templates) != 1 { + t.Errorf("Expected 1 template, got %d", len(c.Templates)) + } + + if len(c.Files) != 5 { + t.Errorf("Expected 5 extra files, got %d", len(c.Files)) + for _, n := range c.Files { + t.Logf("\t%s", n.TypeUrl) + } + } + + if len(c.Dependencies) != 2 { + t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies), c.Dependencies) + for _, d := range c.Dependencies { + t.Logf("\tSubchart: %s\n", d.Metadata.Name) + } + } + + expect := map[string]map[string]string{ + "alpine": map[string]string{ + "version": "0.1.0", + }, + "mariner": map[string]string{ + "version": "4.3.2", + }, + } + + for _, dep := range c.Dependencies { + exp, ok := expect[dep.Metadata.Name] + if !ok { + t.Fatalf("Unknown dependency %s", dep.Metadata.Name) + } + if exp["version"] != dep.Metadata.Version { + t.Errorf("Expected %s version %s, got %s", dep.Metadata.Name, exp["version"], dep.Metadata.Version) + } + } + +} + +func verifyFrobnitz(t *testing.T, c *chart.Chart) { + verifyChartfile(t, c.Metadata) + + if len(c.Templates) != 1 { + t.Fatalf("Expected 1 template, got %d", len(c.Templates)) + } + + if c.Templates[0].Name != "templates/template.tpl" { + t.Errorf("Unexpected template: %s", c.Templates[0].Name) + } + + if len(c.Templates[0].Data) == 0 { + t.Error("No template data.") + } +} diff --git a/pkg/chartutil/testdata/albatross/Chart.yaml b/pkg/chartutil/testdata/albatross/Chart.yaml new file mode 100644 index 000000000..eeef737ff --- /dev/null +++ b/pkg/chartutil/testdata/albatross/Chart.yaml @@ -0,0 +1,4 @@ +name: albatross +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chartutil/testdata/albatross/values.toml b/pkg/chartutil/testdata/albatross/values.toml new file mode 100644 index 000000000..0ef7eb2f9 --- /dev/null +++ b/pkg/chartutil/testdata/albatross/values.toml @@ -0,0 +1 @@ +albatross = "true" diff --git a/pkg/chartutil/testdata/chartfiletest.yaml b/pkg/chartutil/testdata/chartfiletest.yaml new file mode 100644 index 000000000..9f255b9bd --- /dev/null +++ b/pkg/chartutil/testdata/chartfiletest.yaml @@ -0,0 +1,15 @@ +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com diff --git a/pkg/chartutil/testdata/frobnitz-1.2.3.tgz b/pkg/chartutil/testdata/frobnitz-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..c908b22642d0aac2394809200e6baa2bc7600b21 GIT binary patch literal 3491 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 R1O$W{_&@1@#;gE%004h72QmNv literal 0 HcmV?d00001 diff --git a/pkg/chartutil/testdata/frobnitz/Chart.yaml b/pkg/chartutil/testdata/frobnitz/Chart.yaml new file mode 100644 index 000000000..9f255b9bd --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/Chart.yaml @@ -0,0 +1,15 @@ +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com diff --git a/pkg/chartutil/testdata/frobnitz/INSTALL.txt b/pkg/chartutil/testdata/frobnitz/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/frobnitz/LICENSE b/pkg/chartutil/testdata/frobnitz/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chartutil/testdata/frobnitz/README.md b/pkg/chartutil/testdata/frobnitz/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml new file mode 100644 index 000000000..cab858d0a --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://github.com/kubernetes/helm diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md b/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.toml b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.toml new file mode 100644 index 000000000..f0cab9e08 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.toml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a TOML-formatted file. https://github.com/toml-lang/toml +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..232322a261bbcfd9e8c2ce7fcc00414081f84e71 GIT binary patch literal 435 zcmV;k0ZjfMiwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL3CYQrED$8(*hIJDPI)W!q~yBOQWU>m#X3)s{)0TY9oLf?LV z&@l#Mlr0%!{sCqsHbDvkYrjADo{nKwFp&Zu!t1F1`d2p(BJTG5X^4vNn%{DKOt)x^`C>F|A~o_>&(bW3V^Tf ze?sX;{V9(3|12!wo*&pK41^r;SG`Y)EM3C3X*Jk~!S?0(X=T54%ox6hLo0X&jWL7X z&{6;Z literal 0 HcmV?d00001 diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/values.toml b/pkg/chartutil/testdata/frobnitz/charts/alpine/values.toml new file mode 100644 index 000000000..504e6e1be --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/charts/alpine/values.toml @@ -0,0 +1,2 @@ +# The pod name +name = "my-alpine" diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/frobnitz/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d4f19e6243532eba705a306a0c7cca462aa41345 GIT binary patch literal 905 zcmV;419to$iwFQDCPr5P1MQYsXcIvg$Ad+YsCc|s@fusN2fLZw>}I7ZQtN%-RgDjd zPSQ!Uy2*y!iFgzg4^S&8Y8B51kJbkdisCIQ)#61_DYZmvM6rrR3TnM-U3b$&>siGn zBF>M%H%Vq^X6OH1KBP*1tlBJb1VShZ0*F{ggM}R5V50z*6FHV;p@ZW9WO-KPEI^3o zp<)Czf>h%uK17HsNZekmes`WR&ExsWI??y|Wd9L-QT;Q$NF~fdhEAVhXugqKgzfK=lc7R52u5u7g)2g=2oQukraPA$N;0U>;rDoGk`}? z!MLCctA0$dW}S~!Ei#E`>&k5pZ{n<$U zw~LV0|3r`qhGP#3$^|mk+Tg4Ac5w{22I63w6kC4e`6%FH2xDo;|Oo8 zJdL#fgRlC3B)bq%6-`Tn49h@<1JKsmzJ*nbC#g#eEjL>^lEXzb*$C-d5hZlN|0QaaDH_9{*)*%#Wss!W}_jg-#oT5!u- zlUKSWzsOm)(toZ>@jiJy0=58hu*!MCf44qX&p}8`26AfllL<}?6Bt-tzYtF z*QkqCyDkg*V12p&`sR{Z9nK{?L-jXn?i6qC@#IQbyZpyHGftP^(wxbQ;k^@YvJX@b zD%w;y;85trz%iZl+GY1g6}$)+o*CWc&8zyMJM~E|0{UTeHN3rQMs?t5PPoOn%d=Ov zeQW%r@2xI9QFi=@ZpoddZ>=~}r(aWhP1bM2s)?^REx6MqE717TmTs?Ym2qjQX6gU8 fWTQ|h6bgkxp-?Ck3WY+UP=1MzGA=TJ04M+ejvmV5 literal 0 HcmV?d00001 diff --git a/pkg/chartutil/testdata/frobnitz/docs/README.md b/pkg/chartutil/testdata/frobnitz/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/frobnitz/icon.svg b/pkg/chartutil/testdata/frobnitz/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/pkg/chartutil/testdata/frobnitz/templates/template.tpl b/pkg/chartutil/testdata/frobnitz/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/frobnitz/values.toml b/pkg/chartutil/testdata/frobnitz/values.toml new file mode 100644 index 000000000..6fc24051f --- /dev/null +++ b/pkg/chartutil/testdata/frobnitz/values.toml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name = "Some Name" + +[section] +name = "Name in a section" diff --git a/pkg/chartutil/testdata/genfrob.sh b/pkg/chartutil/testdata/genfrob.sh new file mode 100755 index 000000000..38fc1b22c --- /dev/null +++ b/pkg/chartutil/testdata/genfrob.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Pack the albatross chart into the mariner chart. +echo "Packing albatross into mariner" +tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross + +echo "Packing mariner into frobnitz" +tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner + +# Pack the frobnitz chart. +echo "Packing frobnitz" +tar -zcvf frobnitz-1.2.3.tgz frobnitz diff --git a/pkg/chartutil/testdata/mariner/Chart.yaml b/pkg/chartutil/testdata/mariner/Chart.yaml new file mode 100644 index 000000000..4d52794c6 --- /dev/null +++ b/pkg/chartutil/testdata/mariner/Chart.yaml @@ -0,0 +1,4 @@ +name: mariner +description: A Helm chart for Kubernetes +version: 4.3.2 +home: "" diff --git a/pkg/chartutil/testdata/mariner/charts/albatross-0.1.0.tgz b/pkg/chartutil/testdata/mariner/charts/albatross-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ed3c1aee9d62a5860abd1069ed0d4863047e1ca7 GIT binary patch literal 321 zcmV-H0lxkpiwFQDCPr5P1MSw`YJ)Hs24Ii9iVrWqC268i*umJzU{?`ciUqADiG|($ zQY{Lj`$ZjP?{ks^Lri>=eCoQ;-nP!&MT=pK7 z)N)grbk1uVrqXEdi_Vvou~++rZoAzl<6PD;wl_kL`9D_L`t(&d_1y0W-y@Uq8*#i46I#Vaorc>(02;x3m8Md{zIM*u4Ay%W@%6|JN|)Pxq8~+Zj}F0000000000 T00000fSdIOLB)V`04M+e6EUjz literal 0 HcmV?d00001 diff --git a/pkg/chartutil/testdata/mariner/templates/placeholder.tpl b/pkg/chartutil/testdata/mariner/templates/placeholder.tpl new file mode 100644 index 000000000..29c11843a --- /dev/null +++ b/pkg/chartutil/testdata/mariner/templates/placeholder.tpl @@ -0,0 +1 @@ +# This is a placeholder. diff --git a/pkg/chartutil/testdata/mariner/values.toml b/pkg/chartutil/testdata/mariner/values.toml new file mode 100644 index 000000000..4a7bbf8e4 --- /dev/null +++ b/pkg/chartutil/testdata/mariner/values.toml @@ -0,0 +1,4 @@ +# Default values for mariner. +# This is a TOML-formatted file. https://github.com/toml-lang/toml +# Declare name/value pairs to be passed into your templates. +# name = "value" From 2802fcbd87b6e164f871f9a48f8c91e8044825a1 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 1 Jun 2016 17:02:32 -0600 Subject: [PATCH 2/2] feat(chartutil): optimize for re-use This is a refactor of the loader to use in-memory buffers instead of trying to optimize for memory usage by delaying reads until the last possible moment. Since charts tend to stay well below 1M in size, this makes more sense, and is easier to work with. --- pkg/chartutil/chartfile.go | 1 + pkg/chartutil/chartfile_test.go | 4 + pkg/chartutil/load.go | 243 +++++++++++--------------------- pkg/chartutil/load_test.go | 11 +- 4 files changed, 100 insertions(+), 159 deletions(-) diff --git a/pkg/chartutil/chartfile.go b/pkg/chartutil/chartfile.go index cd5efbc7b..995a496a1 100644 --- a/pkg/chartutil/chartfile.go +++ b/pkg/chartutil/chartfile.go @@ -7,6 +7,7 @@ import ( "github.com/kubernetes/helm/pkg/proto/hapi/chart" ) +// UnmarshalChartfile takes raw Chart.yaml data and unmarshals it. func UnmarshalChartfile(data []byte) (*chart.Metadata, error) { y := &chart.Metadata{} err := yaml.Unmarshal(data, y) diff --git a/pkg/chartutil/chartfile_test.go b/pkg/chartutil/chartfile_test.go index 4d6dbf6e1..8df26b058 100644 --- a/pkg/chartutil/chartfile_test.go +++ b/pkg/chartutil/chartfile_test.go @@ -19,6 +19,10 @@ func TestLoadChartfile(t *testing.T) { func verifyChartfile(t *testing.T, f *chart.Metadata) { + if f == nil { + t.Fatal("Failed verifyChartfile because f is nil") + } + if f.Name != "frobnitz" { t.Errorf("Expected frobnitz, got %s", f.Name) } diff --git a/pkg/chartutil/load.go b/pkg/chartutil/load.go index 00ca9489e..0a8757205 100644 --- a/pkg/chartutil/load.go +++ b/pkg/chartutil/load.go @@ -31,50 +31,30 @@ func Load(name string) (*chart.Chart, error) { return LoadFile(name) } -// subchart is an intermediate representation of a dependency. -// -// It is used to temporarily store a dependency while we process the outer -// file. -type subchart []*afile - -func newSubchart() subchart { - return []*afile{} -} - -func (s subchart) add(name string, data []byte, arch bool) subchart { - s = append(s, &afile{name, data, arch}) - return s -} - // afile represents an archive file buffered for later processing. type afile struct { - name string - data []byte - archive bool + name string + data []byte } // LoadArchive loads from a reader containing a compressed tar archive. func LoadArchive(in io.Reader) (*chart.Chart, error) { - sc := map[string]subchart{} unzipped, err := gzip.NewReader(in) if err != nil { - return nil, err + return &chart.Chart{}, err } defer unzipped.Close() - c := &chart.Chart{} - b := bytes.NewBuffer(nil) - + files := []*afile{} tr := tar.NewReader(unzipped) for { + b := bytes.NewBuffer(nil) hd, err := tr.Next() if err == io.EOF { - // We're done with the reader. Now add subcharts and exit. - e := addSubcharts(c, sc) - return c, e + break } if err != nil { - return c, err + return &chart.Chart{}, err } if hd.FileInfo().IsDir() { @@ -87,22 +67,84 @@ func LoadArchive(in io.Reader) (*chart.Chart, error) { n := strings.Join(parts[1:], "/") if _, err := io.Copy(b, tr); err != nil { - return c, err + return &chart.Chart{}, err } - if strings.HasPrefix(n, "charts/") { - // If there are subcharts, we put those into a temporary holding - // array for later processing. - fmt.Printf("Appending %s to chart %s:\n%s\n", n, c.Metadata.Name, b.String()) - appendSubchart(sc, n, b.Bytes()) - b.Reset() - continue + files = append(files, &afile{name: n, data: b.Bytes()}) + b.Reset() + } + + if len(files) == 0 { + return nil, errors.New("no files in chart archive") + } + + return loadFiles(files) +} + +func loadFiles(files []*afile) (*chart.Chart, error) { + c := &chart.Chart{} + subcharts := map[string][]*afile{} + + for _, f := range files { + if f.name == "Chart.yaml" { + m, err := UnmarshalChartfile(f.data) + if err != nil { + return c, err + } + c.Metadata = m + } else if f.name == "values.toml" || f.name == "values.yaml" { + c.Values = &chart.Config{Raw: string(f.data)} + } else if strings.HasPrefix(f.name, "templates/") { + c.Templates = append(c.Templates, &chart.Template{Name: f.name, Data: f.data}) + } else if strings.HasPrefix(f.name, "charts/") { + cname := strings.TrimPrefix(f.name, "charts/") + parts := strings.SplitN(cname, "/", 2) + scname := parts[0] + subcharts[scname] = append(subcharts[scname], &afile{name: cname, data: f.data}) + } else { + c.Files = append(c.Files, &any.Any{TypeUrl: f.name, Value: f.data}) } + } - addToChart(c, n, b.Bytes()) - b.Reset() + // Ensure that we got a Chart.yaml file + if c.Metadata == nil || c.Metadata.Name == "" { + return c, errors.New("chart metadata (Chart.yaml) missing") + } + + for n, files := range subcharts { + var sc *chart.Chart + var err error + if filepath.Ext(n) == ".tgz" { + file := files[0] + if file.name != n { + return c, fmt.Errorf("error unpacking tar in %s: expected %s, got %s", c.Metadata.Name, n, file.name) + } + // Untar the chart and add to c.Dependencies + b := bytes.NewBuffer(file.data) + sc, err = LoadArchive(b) + } else { + // We have to trim the prefix off of every file, and ignore any file + // that is in charts/, but isn't actually a chart. + buff := make([]*afile, 0, len(files)) + for _, f := range files { + parts := strings.SplitN(f.name, "/", 2) + if len(parts) < 2 { + continue + } + f.name = parts[1] + buff = append(buff, f) + } + sc, err = loadFiles(buff) + } + + if err != nil { + return c, fmt.Errorf("error unpacking %s in %s: %s", n, c.Metadata.Name, err) + } + + c.Dependencies = append(c.Dependencies, sc) } + return c, nil } // LoadFile loads from an archive file. @@ -131,9 +173,11 @@ func LoadDir(dir string) (*chart.Chart, error) { return nil, err } - topdir += string(filepath.Separator) - sc := map[string]subchart{} + // Just used for errors. c := &chart.Chart{} + + files := []*afile{} + topdir += string(filepath.Separator) err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) if err != nil { @@ -148,127 +192,12 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("error reading %s: %s", n, err) } - if strings.HasPrefix(n, "charts/") { - appendSubchart(sc, n, data) - return nil - } - - return addToChart(c, n, data) + files = append(files, &afile{name: n, data: data}) + return nil }) if err != nil { return c, err } - // Ensure that we had a Chart.yaml file - if c.Metadata == nil || c.Metadata.Name == "" { - return c, errors.New("chart metadata (Chart.yaml) missing") - } - - err = addSubcharts(c, sc) - return c, err -} - -func addToChart(c *chart.Chart, n string, data []byte) error { - fmt.Printf("--> Scanning %s\n", n) - if n == "Chart.yaml" { - md, err := UnmarshalChartfile(data) - if err != nil { - return err - } - if md.Name == "" { - fmt.Printf("Chart:\n%s\n", string(data)) - } - fmt.Printf("--> Adding %s as Chart.yaml\n", md.Name) - c.Metadata = md - } else if n == "values.toml" { - c.Values = &chart.Config{Raw: string(data)} - fmt.Printf("--> Adding to values:\n%s\n", string(data)) - } else if strings.HasPrefix(n, "charts/") { - // SKIP THESE. These are handled elsewhere, because they need context - // to process. - return nil - } else if strings.HasPrefix(n, "templates/") { - c.Templates = append(c.Templates, &chart.Template{Name: n, Data: data}) - } else { - c.Files = append(c.Files, &any.Any{TypeUrl: n, Value: data}) - } - return nil -} - -func addSubcharts(c *chart.Chart, s map[string]subchart) error { - for n, sc := range s { - fmt.Printf("===> Unpacking %s\n", n) - if err := addSubchart(c, sc); err != nil { - return fmt.Errorf("error adding %q: %s", n, err) - } - } - return nil -} - -// addSubchart transforms a subchart to a new chart, and then embeds it into the given chart. -func addSubchart(c *chart.Chart, sc subchart) error { - nc := &chart.Chart{} - deps := map[string]subchart{} - - // The sc paths are all relative to the sc itself. - for _, sub := range sc { - if sub.archive { - b := bytes.NewBuffer(sub.data) - var err error - nc, err = LoadArchive(b) - if err != nil { - fmt.Printf("Bad data in %s: %q", sub.name, string(sub.data)) - return err - } - break - } else if strings.HasPrefix(sub.name, "charts/") { - appendSubchart(deps, sub.name, sub.data) - } else { - fmt.Printf("Adding %s to subchart in %s\n", sub.name, c.Metadata.Name) - addToChart(nc, sub.name, sub.data) - } - } - - if nc.Metadata == nil || nc.Metadata.Name == "" { - return errors.New("embedded chart is not well-formed") - } - - fmt.Printf("Added dependency: %q\n", nc.Metadata.Name) - c.Dependencies = append(c.Dependencies, nc) - return nil -} - -func appendSubchart(sc map[string]subchart, n string, b []byte) { - fmt.Printf("Append subchart %s\n", n) - // TODO: Do we need to filter out 0 byte files? - // TODO: If this finds a dependency that is a tarball, we need to untar it, - // and express it as a subchart. - parts := strings.SplitN(n, "/", 3) - lp := len(parts) - switch lp { - case 2: - if filepath.Ext(parts[1]) == ".tgz" { - fmt.Printf("--> Adding archive %s\n", n) - // Basically, we delay expanding tar files until the last minute, - // which helps (a little) keep memory usage down. - bn := strings.TrimSuffix(parts[1], ".tgz") - cc := newSubchart() - sc[bn] = cc.add(parts[1], b, true) - return - } else { - // Skip directory entries and non-charts. - return - } - case 3: - if _, ok := sc[parts[1]]; !ok { - sc[parts[1]] = newSubchart() - } - //fmt.Printf("Adding file %q to %s\n", parts[2], parts[1]) - sc[parts[1]] = sc[parts[1]].add(parts[2], b, false) - return - default: - // Skip 1 or 0. - return - } - + return loadFiles(files) } diff --git a/pkg/chartutil/load_test.go b/pkg/chartutil/load_test.go index dae3ee438..2ed052c69 100644 --- a/pkg/chartutil/load_test.go +++ b/pkg/chartutil/load_test.go @@ -25,6 +25,10 @@ func TestLoadFile(t *testing.T) { } func verifyChart(t *testing.T, c *chart.Chart) { + if c.Metadata.Name == "" { + t.Fatalf("No chart metadata found on %v", c) + } + t.Logf("Verifying chart %s", c.Metadata.Name) if len(c.Templates) != 1 { t.Errorf("Expected 1 template, got %d", len(c.Templates)) } @@ -44,15 +48,18 @@ func verifyChart(t *testing.T, c *chart.Chart) { } expect := map[string]map[string]string{ - "alpine": map[string]string{ + "alpine": { "version": "0.1.0", }, - "mariner": map[string]string{ + "mariner": { "version": "4.3.2", }, } for _, dep := range c.Dependencies { + if dep.Metadata == nil { + t.Fatalf("expected metadata on dependency: %v", dep) + } exp, ok := expect[dep.Metadata.Name] if !ok { t.Fatalf("Unknown dependency %s", dep.Metadata.Name)