From f8193c25c242a148780be15f9d72f808234e2eee Mon Sep 17 00:00:00 2001
From: jackgr <jackgr@google.com>
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.
-- 
GitLab