From f8193c25c242a148780be15f9d72f808234e2eee Mon Sep 17 00:00:00 2001 From: jackgr Date: Thu, 17 Mar 2016 16:36:35 -0700 Subject: [PATCH] Prepare to download charts --- pkg/chart/chart.go | 89 +++++++++++++++++++------ pkg/chart/chart_test.go | 91 ++++++++++++++++++++++++++ pkg/chart/chartfile.go | 10 +++ pkg/chart/chartfile_test.go | 17 +++++ pkg/chart/testdata/frobnitz/Chart.yaml | 5 ++ pkg/common/types.go | 22 ------- 6 files changed, 192 insertions(+), 42 deletions(-) diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 21df43408..8857bf441 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -20,6 +20,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "encoding/base64" "errors" "fmt" "io" @@ -243,25 +244,7 @@ func LoadDir(chart string) (*Chart, error) { // LoadData loads a chart from data, where data is a []byte containing a gzipped tar file. func LoadData(data []byte) (*Chart, error) { - b := bytes.NewBuffer(data) - unzipped, err := gzip.NewReader(b) - if err != nil { - return nil, err - } - defer unzipped.Close() - - untarred := tar.NewReader(unzipped) - c, err := loadTar(untarred) - if err != nil { - return nil, err - } - - cf, err := LoadChartfile(filepath.Join(c.tmpDir, ChartfileName)) - if err != nil { - return nil, err - } - c.chartyaml = cf - return &Chart{loader: c}, nil + return LoadDataFromReader(bytes.NewBuffer(data)) } // Load loads a chart from a chart archive. @@ -281,7 +264,11 @@ func Load(archive string) (*Chart, error) { } defer raw.Close() - unzipped, err := gzip.NewReader(raw) + return LoadDataFromReader(raw) +} + +func LoadDataFromReader(r io.Reader) (*Chart, error) { + unzipped, err := gzip.NewReader(r) if err != nil { return nil, err } @@ -367,3 +354,65 @@ func loadTar(r *tar.Reader) (*tarChart, error) { return c, nil } + +// ChartMember is a file in a chart. +type ChartMember struct { + Path string `json:"path"` // Path from the root of the chart. + Content []byte `json:"content"` // Base64 encoded content. +} + +// LoadTemplates loads the members of TemplatesDir(). +func (c *Chart) LoadTemplates() ([]*ChartMember, error) { + dir := c.TemplatesDir() + return c.loadDirectory(dir) +} + +// loadDirectory loads the members of a directory. +func (c *Chart) loadDirectory(dir string) ([]*ChartMember, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + members := []*ChartMember{} + for _, file := range files { + filename := filepath.Join(dir, file.Name()) + member, err := c.loadMember(filename) + if err != nil { + return nil, err + } + + members = append(members, member) + } + + return members, nil +} + +// path is from the root of the chart. +func (c *Chart) LoadMember(path string) (*ChartMember, error) { + filename := filepath.Join(c.loader.dir(), path) + return c.loadMember(filename) +} + +// loadMember loads and base 64 encodes a file. +func (c *Chart) loadMember(filename string) (*ChartMember, error) { + dir := c.Dir() + if !strings.HasPrefix(filename, dir) { + err := fmt.Errorf("File %s is outside chart directory %s", filename, dir) + return nil, err + } + + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + path := strings.TrimPrefix(filename, dir) + content := base64.StdEncoding.EncodeToString(b) + result := &ChartMember{ + Path: path, + Content: []byte(content), + } + + return result, nil +} diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index 36ac856d2..cb9408de0 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -17,6 +17,8 @@ limitations under the License. package chart import ( + "encoding/base64" + "fmt" "io/ioutil" "path/filepath" "testing" @@ -30,6 +32,7 @@ const ( testarchive = "testdata/frobnitz-0.0.1.tgz" testill = "testdata/ill-1.2.3.tgz" testnochart = "testdata/nochart.tgz" + testmember = "templates/wordpress.jinja" ) // Type canaries. If these fail, they will fail at compile time. @@ -160,3 +163,91 @@ func TestChart(t *testing.T) { t.Errorf("Unexpectedly, icon is in %s", i) } } + +func TestLoadTemplates(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + + members, err := c.LoadTemplates() + if members == nil { + t.Fatalf("Cannot load templates: unknown error") + } + + if err != nil { + t.Fatalf("Cannot load templates: %s", err) + } + + dir := c.TemplatesDir() + files, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatalf("Cannot read template directory: %s", err) + } + + if len(members) != len(files) { + t.Fatalf("Expected %s templates, got %d", len(files), len(members)) + } + + root := c.loader.dir() + for _, file := range files { + path := filepath.Join(preTemplates, file.Name()) + if err := findMember(root, path, members); err != nil { + t.Fatal(err) + } + } +} + +func findMember(root, path string, members []*ChartMember) error { + for _, member := range members { + if member.Path == path { + filename := filepath.Join(root, path) + if err := compareContent(filename, string(member.Content)); err != nil { + return err + } + + return nil + } + } + + return fmt.Errorf("Template not found: %s", path) +} + +func TestLoadMember(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + + member, err := c.LoadMember(testmember) + if member == nil { + t.Fatalf("Cannot load member %s: unknown error", testmember) + } + + if err != nil { + t.Fatalf("Cannot load member %s: %s", testmember, err) + } + + if member.Path != testmember { + t.Errorf("Expected member path %s, got %s", testmember, member.Path) + } + + filename := filepath.Join(c.loader.dir(), testmember) + if err := compareContent(filename, string(member.Content)); err != nil { + t.Fatal(err) + } +} + +func compareContent(filename, content string) error { + b, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("Cannot read test file %s: %s", filename, err) + } + + compare := base64.StdEncoding.EncodeToString(b) + if content != compare { + return fmt.Errorf("Expected member content\n%s\ngot\n%s", compare, content) + } + + return nil +} diff --git a/pkg/chart/chartfile.go b/pkg/chart/chartfile.go index b694a3734..1332ef805 100644 --- a/pkg/chart/chartfile.go +++ b/pkg/chart/chartfile.go @@ -34,6 +34,8 @@ type Chartfile struct { Home string `yaml:"home"` Dependencies []*Dependency `yaml:"dependencies,omitempty"` Environment []*EnvConstraint `yaml:"environment,omitempty"` + Expander *Expander `yaml:"expander,omitempty"` + Schema string `yaml:"schema,omitempty"` } // Maintainer describes a chart maintainer. @@ -57,6 +59,14 @@ type EnvConstraint struct { APIGroups []string `yaml:"apiGroups,omitempty"` } +// Expander controls how template/ is evaluated. +type Expander struct { + // Currently just Expandybird or GoTemplate + Name string `json:"name"` + // During evaluation, which file to start from. + Entrypoint string `json:"entrypoint"` +} + // LoadChartfile loads a Chart.yaml file into a *Chart. func LoadChartfile(filename string) (*Chartfile, error) { b, err := ioutil.ReadFile(filename) diff --git a/pkg/chart/chartfile_test.go b/pkg/chart/chartfile_test.go index b5e46dfa6..3c4042db7 100644 --- a/pkg/chart/chartfile_test.go +++ b/pkg/chart/chartfile_test.go @@ -50,6 +50,23 @@ func TestLoadChartfile(t *testing.T) { if f.Source[0] != "https://example.com/foo/bar" { t.Errorf("Expected https://example.com/foo/bar, got %s", f.Source) } + + expander := f.Expander + if expander == nil { + t.Errorf("No expander found in %s", testfile) + } else { + if expander.Name != "Expandybird" { + t.Errorf("Expected expander name Expandybird, got %s", expander.Name) + } + + if expander.Entrypoint != "templates/wordpress.jinja" { + t.Errorf("Expected expander entrypoint templates/wordpress.jinja, got %s", expander.Entrypoint) + } + } + + if f.Schema != "wordpress.jinja.schema" { + t.Errorf("Expected schema wordpress.jinja.schema, got %s", f.Schema) + } } func TestVersionOK(t *testing.T) { diff --git a/pkg/chart/testdata/frobnitz/Chart.yaml b/pkg/chart/testdata/frobnitz/Chart.yaml index 9572f010c..b1e67a038 100644 --- a/pkg/chart/testdata/frobnitz/Chart.yaml +++ b/pkg/chart/testdata/frobnitz/Chart.yaml @@ -26,3 +26,8 @@ environment: - extensions/v1beta1/daemonset apiGroups: - 3rdParty +expander: + name: Expandybird + entrypoint: templates/wordpress.jinja +schema: wordpress.jinja.schema + \ No newline at end of file diff --git a/pkg/common/types.go b/pkg/common/types.go index 6e6b23bb1..df030da87 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -97,28 +97,6 @@ type Manifest struct { Layout *Layout `json:"layout,omitempty"` } -// Expander controls how template/ is evaluated. -type Expander struct { - // Currently just Expandybird or GoTemplate - Name string `json:"name"` - // During evaluation, which file to start from. - Entrypoint string `json:"entry_point"` -} - -// ChartFile is a file in a chart that is not chart.yaml. -type ChartFile struct { - Path string `json:"path"` // Path from the root of the chart. - Content string `json:"content"` // Base64 encoded file content. -} - -// Chart is our internal representation of the chart.yaml (in structured form) + all supporting files. -type Chart struct { - Name string `json:"name"` - Expander *Expander `json:"expander"` - Schema interface{} `json:"schema"` - Files []*ChartFile `json:"files"` -} - // Template describes a set of resources to be deployed. // Manager expands a Template into a Configuration, which // describes the set in a form that can be instantiated.