From cfaca7ffb58c6544d1e0a4c579ca9966362bdfd7 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 1 Feb 2016 18:04:47 -0700 Subject: [PATCH] Refactor Chart to be a struct, with more methods. This refactors the Chart to be a struct that can have numerous properties and methods. Now, a chartLoader implements the loading and closing of charts. --- chart/chart.go | 107 ++++++++++++++++++++++-------- chart/chart_test.go | 51 ++++++++++++++ chart/testdata/frobnitz-0.0.1.tgz | Bin 6074 -> 6246 bytes chart/testdata/frobnitz/icon.svg | 8 +++ 4 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 chart/testdata/frobnitz/icon.svg diff --git a/chart/chart.go b/chart/chart.go index 0c8f377d6..e386c1ece 100644 --- a/chart/chart.go +++ b/chart/chart.go @@ -36,6 +36,7 @@ const ( preTemplates string = "templates/" preHooks string = "hooks/" preDocs string = "docs/" + preIcon string = "icon.svg" ) // Chart represents a complete chart. @@ -54,47 +55,93 @@ const ( // // Optionally, a chart might also locate a provenance (.prov) file that it // can use for cryptographic signing. -type Chart interface { +type Chart struct { + loader chartLoader +} + +// Close the chart. +// +// Charts should always be closed when no longer needed. +func (c *Chart) Close() error { + return c.loader.close() +} + +// Chartfile gets the Chartfile (Chart.yaml) for this chart. +func (c *Chart) Chartfile() *Chartfile { + return c.loader.chartfile() +} + +// Dir() returns the directory where the charts are located. +func (c *Chart) Dir() string { + return c.loader.dir() +} + +// DocsDir returns the directory where the chart's documentation is stored. +func (c *Chart) DocsDir() string { + return filepath.Join(c.loader.dir(), preDocs) +} + +// HooksDir returns the directory where the hooks are stored. +func (c *Chart) HooksDir() string { + return filepath.Join(c.loader.dir(), preHooks) +} + +// TemplatesDir returns the directory where the templates are stored. +func (c *Chart) TemplatesDir() string { + return filepath.Join(c.loader.dir(), preTemplates) +} + +// Icon returns the path to the icon.svg file. +// +// If an icon is not found in the chart, this will return an error. +func (c *Chart) Icon() (string, error) { + i := filepath.Join(c.Dir(), preIcon) + _, err := os.Stat(i) + return i, err +} + +// chartLoader provides load, close, and save implementations for a chart. +type chartLoader interface { // Chartfile resturns a *Chartfile for this chart. - Chartfile() *Chartfile + chartfile() *Chartfile // Dir returns a directory where the chart can be accessed. - Dir() string + dir() string // Close cleans up a chart. - Close() error + close() error } type dirChart struct { - chartfile *Chartfile - dir string + chartyaml *Chartfile + chartdir string } -func (d *dirChart) Chartfile() *Chartfile { - return d.chartfile +func (d *dirChart) chartfile() *Chartfile { + return d.chartyaml } -func (d *dirChart) Dir() string { - return "." +func (d *dirChart) dir() string { + return d.chartdir } -func (d *dirChart) Close() error { +func (d *dirChart) close() error { return nil } type tarChart struct { - chartfile *Chartfile + chartyaml *Chartfile tmpDir string } -func (t *tarChart) Chartfile() *Chartfile { - return t.chartfile +func (t *tarChart) chartfile() *Chartfile { + return t.chartyaml } -func (t *tarChart) Dir() string { - return "." +func (t *tarChart) dir() string { + return t.tmpDir } -func (t *tarChart) Close() error { +func (t *tarChart) close() error { // Remove the temp directory. return os.RemoveAll(t.tmpDir) } @@ -105,7 +152,7 @@ func (t *tarChart) Close() error { // // If you are just reading the Chart.yaml file, it is substantially more // performant to use LoadChartfile. -func LoadDir(chart string) (Chart, error) { +func LoadDir(chart string) (*Chart, error) { if fi, err := os.Stat(chart); err != nil { return nil, err } else if !fi.IsDir() { @@ -117,19 +164,21 @@ func LoadDir(chart string) (Chart, error) { return nil, err } - c := &dirChart{ - chartfile: cf, - dir: chart, + cl := &dirChart{ + chartyaml: cf, + chartdir: chart, } - return c, nil + return &Chart{ + loader: cl, + }, nil } // Load loads a chart from a chart archive. // // A chart archive is a gzipped tar archive that follows the Chart format // specification. -func Load(archive string) (Chart, error) { +func Load(archive string) (*Chart, error) { if fi, err := os.Stat(archive); err != nil { return nil, err } else if fi.IsDir() { @@ -151,15 +200,15 @@ func Load(archive string) (Chart, error) { untarred := tar.NewReader(unzipped) c, err := loadTar(untarred) if err != nil { - return c, err + return nil, err } cf, err := LoadChartfile(filepath.Join(c.tmpDir, ChartfileName)) if err != nil { - return c, err + return nil, err } - c.chartfile = cf - return c, nil + c.chartyaml = cf + return &Chart{loader: c}, nil } func loadTar(r *tar.Reader) (*tarChart, error) { @@ -168,7 +217,7 @@ func loadTar(r *tar.Reader) (*tarChart, error) { return nil, err } c := &tarChart{ - chartfile: &Chartfile{}, + chartyaml: &Chartfile{}, tmpDir: td, } @@ -221,7 +270,7 @@ func loadTar(r *tar.Reader) (*tarChart, error) { if err != nil && err != io.EOF { log.Warn("Unexpected error reading tar: %s", err) - c.Close() + c.close() return c, err } log.Info("Reached end of Tar file") diff --git a/chart/chart_test.go b/chart/chart_test.go index 83289c916..542664729 100644 --- a/chart/chart_test.go +++ b/chart/chart_test.go @@ -17,6 +17,7 @@ limitations under the License. package chart import ( + "path/filepath" "testing" "github.com/kubernetes/deployment-manager/log" @@ -30,11 +31,16 @@ const ( testnochart = "testdata/nochart.tgz" ) +// Type canaries. If these fail, they will fail at compile time. +var _ chartLoader = &dirChart{} +var _ chartLoader = &tarChart{} + func init() { log.IsDebugging = true } func TestLoadDir(t *testing.T) { + c, err := LoadDir(testdir) if err != nil { t.Errorf("Failed to load chart: %s", err) @@ -51,6 +57,7 @@ func TestLoadDir(t *testing.T) { } func TestLoad(t *testing.T) { + c, err := Load(testarchive) if err != nil { t.Errorf("Failed to load chart: %s", err) @@ -80,6 +87,11 @@ func TestLoadIll(t *testing.T) { t.Error("No chartfile was loaded.") return } + + // Ill does not have an icon. + if i, err := c.Icon(); err == nil { + t.Errorf("Expected icon to be missing. Got %s", i) + } } func TestLoadNochart(t *testing.T) { @@ -88,3 +100,42 @@ func TestLoadNochart(t *testing.T) { t.Error("Nochart should not have loaded at all.") } } + +func TestChart(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + defer c.Close() + + if c.Dir() != c.loader.dir() { + t.Errorf("Unexpected location for directory: %s", c.Dir()) + } + + if c.Chartfile().Name != c.loader.chartfile().Name { + t.Errorf("Unexpected chart file name: %s", c.Chartfile().Name) + } + + d := c.DocsDir() + if d != filepath.Join(testdir, preDocs) { + t.Errorf("Unexpectedly, docs are in %s", d) + } + + d = c.TemplatesDir() + if d != filepath.Join(testdir, preTemplates) { + t.Errorf("Unexpectedly, templates are in %s", d) + } + + d = c.HooksDir() + if d != filepath.Join(testdir, preHooks) { + t.Errorf("Unexpectedly, hooks are in %s", d) + } + + i, err := c.Icon() + if err != nil { + t.Errorf("No icon found in test chart: %s", err) + } + if i != filepath.Join(testdir, preIcon) { + t.Errorf("Unexpectedly, icon is in %s", i) + } +} diff --git a/chart/testdata/frobnitz-0.0.1.tgz b/chart/testdata/frobnitz-0.0.1.tgz index 79d611a5d98468e7ee84bf8dbddbb3abe2a58c71..78fb3bc176af80192b52fb4fec619249905dda80 100644 GIT binary patch literal 6246 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 A3IG5A diff --git a/chart/testdata/frobnitz/icon.svg b/chart/testdata/frobnitz/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/chart/testdata/frobnitz/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + +