Commit f8193c25 authored by jackgr's avatar jackgr

Prepare to download charts

parent 54abaf7e
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
...@@ -243,25 +244,7 @@ func LoadDir(chart string) (*Chart, error) { ...@@ -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. // LoadData loads a chart from data, where data is a []byte containing a gzipped tar file.
func LoadData(data []byte) (*Chart, error) { func LoadData(data []byte) (*Chart, error) {
b := bytes.NewBuffer(data) return LoadDataFromReader(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
} }
// Load loads a chart from a chart archive. // Load loads a chart from a chart archive.
...@@ -281,7 +264,11 @@ func Load(archive string) (*Chart, error) { ...@@ -281,7 +264,11 @@ func Load(archive string) (*Chart, error) {
} }
defer raw.Close() 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -367,3 +354,65 @@ func loadTar(r *tar.Reader) (*tarChart, error) { ...@@ -367,3 +354,65 @@ func loadTar(r *tar.Reader) (*tarChart, error) {
return c, nil 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
}
...@@ -17,6 +17,8 @@ limitations under the License. ...@@ -17,6 +17,8 @@ limitations under the License.
package chart package chart
import ( import (
"encoding/base64"
"fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"testing" "testing"
...@@ -30,6 +32,7 @@ const ( ...@@ -30,6 +32,7 @@ const (
testarchive = "testdata/frobnitz-0.0.1.tgz" testarchive = "testdata/frobnitz-0.0.1.tgz"
testill = "testdata/ill-1.2.3.tgz" testill = "testdata/ill-1.2.3.tgz"
testnochart = "testdata/nochart.tgz" testnochart = "testdata/nochart.tgz"
testmember = "templates/wordpress.jinja"
) )
// Type canaries. If these fail, they will fail at compile time. // Type canaries. If these fail, they will fail at compile time.
...@@ -160,3 +163,91 @@ func TestChart(t *testing.T) { ...@@ -160,3 +163,91 @@ func TestChart(t *testing.T) {
t.Errorf("Unexpectedly, icon is in %s", i) 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
}
...@@ -34,6 +34,8 @@ type Chartfile struct { ...@@ -34,6 +34,8 @@ type Chartfile struct {
Home string `yaml:"home"` Home string `yaml:"home"`
Dependencies []*Dependency `yaml:"dependencies,omitempty"` Dependencies []*Dependency `yaml:"dependencies,omitempty"`
Environment []*EnvConstraint `yaml:"environment,omitempty"` Environment []*EnvConstraint `yaml:"environment,omitempty"`
Expander *Expander `yaml:"expander,omitempty"`
Schema string `yaml:"schema,omitempty"`
} }
// Maintainer describes a chart maintainer. // Maintainer describes a chart maintainer.
...@@ -57,6 +59,14 @@ type EnvConstraint struct { ...@@ -57,6 +59,14 @@ type EnvConstraint struct {
APIGroups []string `yaml:"apiGroups,omitempty"` 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. // LoadChartfile loads a Chart.yaml file into a *Chart.
func LoadChartfile(filename string) (*Chartfile, error) { func LoadChartfile(filename string) (*Chartfile, error) {
b, err := ioutil.ReadFile(filename) b, err := ioutil.ReadFile(filename)
......
...@@ -50,6 +50,23 @@ func TestLoadChartfile(t *testing.T) { ...@@ -50,6 +50,23 @@ func TestLoadChartfile(t *testing.T) {
if f.Source[0] != "https://example.com/foo/bar" { if f.Source[0] != "https://example.com/foo/bar" {
t.Errorf("Expected https://example.com/foo/bar, got %s", f.Source) 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) { func TestVersionOK(t *testing.T) {
......
...@@ -26,3 +26,8 @@ environment: ...@@ -26,3 +26,8 @@ environment:
- extensions/v1beta1/daemonset - extensions/v1beta1/daemonset
apiGroups: apiGroups:
- 3rdParty - 3rdParty
expander:
name: Expandybird
entrypoint: templates/wordpress.jinja
schema: wordpress.jinja.schema
\ No newline at end of file
...@@ -97,28 +97,6 @@ type Manifest struct { ...@@ -97,28 +97,6 @@ type Manifest struct {
Layout *Layout `json:"layout,omitempty"` 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. // Template describes a set of resources to be deployed.
// Manager expands a Template into a Configuration, which // Manager expands a Template into a Configuration, which
// describes the set in a form that can be instantiated. // describes the set in a form that can be instantiated.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment