diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 73b948e26..b64823ced 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -98,6 +98,23 @@ func (c *Chart) LoadValues() (Values, error) { return ReadValuesFile(filepath.Join(c.loader.dir(), preValues)) } +// ChartDepNames returns the list of chart names found in ChartsDir. +func (c *Chart) ChartDepNames() ([]string, error) { + files, err := ioutil.ReadDir(c.ChartsDir()) + if err != nil { + return nil, err + } + + var deps []string + for _, file := range files { + if file.IsDir() { + deps = append(deps, filepath.Join(c.ChartsDir(), file.Name())) + } + } + + return deps, nil +} + // chartLoader provides load, close, and save implementations for a chart. type chartLoader interface { // Chartfile resturns a *Chartfile for this chart. @@ -238,6 +255,32 @@ func LoadDir(chart string) (*Chart, error) { }, nil } +// LoadChart loads an entire chart archive. +// +// The following are valid values for 'chfi': +// +// - relative path to the chart archive +// - absolute path to the chart archive +// - name of the chart directory +// +func LoadChart(chfi string) (*Chart, error) { + path, err := filepath.Abs(chfi) + if err != nil { + return nil, err + } + + fi, err := os.Stat(path) + if err != nil { + return nil, err + } + + if fi.IsDir() { + return LoadDir(path) + } + + return Load(path) +} + // LoadData loads a chart from data, where data is a []byte containing a gzipped tar file. func LoadData(data []byte) (*Chart, error) { return LoadDataFromReader(bytes.NewBuffer(data)) diff --git a/pkg/helm/convert.go b/pkg/helm/convert.go new file mode 100644 index 000000000..bd4ff4306 --- /dev/null +++ b/pkg/helm/convert.go @@ -0,0 +1,103 @@ +package helm + +import ( + chartutil "github.com/kubernetes/helm/pkg/chart" + chartpbs "github.com/kubernetes/helm/pkg/proto/hapi/chart" +) + +func ChartToProto(ch *chartutil.Chart) (chpb *chartpbs.Chart, err error) { + chpb = new(chartpbs.Chart) + + chpb.Metadata, err = MetadataToProto(ch) + if err != nil { + return + } + + chpb.Templates, err = TemplatesToProto(ch) + if err != nil { + return + } + + chs, err := WalkChartFile(ch) + if err != nil { + return + } + + for _, dep := range chs.deps { + chdep, err := ChartToProto(dep.File()) + if err != nil { + return nil, err + } + + chpb.Dependencies = append(chpb.Dependencies, chdep) + } + + return +} + +func MetadataToProto(ch *chartutil.Chart) (*chartpbs.Metadata, error) { + if ch == nil { + return nil, ErrMissingChart + } + + chfi := ch.Chartfile() + + md := &chartpbs.Metadata{ + Name: chfi.Name, + Home: chfi.Home, + Version: chfi.Version, + Description: chfi.Description, + } + + md.Sources = make([]string, len(chfi.Source)) + copy(md.Sources, chfi.Source) + + md.Keywords = make([]string, len(chfi.Keywords)) + copy(md.Keywords, chfi.Keywords) + + for _, maintainer := range chfi.Maintainers { + md.Maintainers = append(md.Maintainers, &chartpbs.Maintainer{ + Name: maintainer.Name, + Email: maintainer.Email, + }) + } + + return md, nil +} + +func TemplatesToProto(ch *chartutil.Chart) (tpls []*chartpbs.Template, err error) { + if ch == nil { + return nil, ErrMissingChart + } + + members, err := ch.LoadTemplates() + if err != nil { + return + } + + var tpl *chartpbs.Template + + for _, member := range members { + tpl = &chartpbs.Template{ + Name: member.Path, + Data: make([]byte, len(member.Content)), + } + + copy(tpl.Data, member.Content) + + tpls = append(tpls, tpl) + } + + return +} + +func ValuesToProto(ch *chartutil.Chart) (*chartpbs.Config, error) { + vals, err := ch.LoadValues() + if err != nil { + return nil, ErrMissingValues + } + + _ = vals + + return nil, nil +} diff --git a/pkg/helm/error.go b/pkg/helm/error.go index b8d72a2c4..781b670b8 100644 --- a/pkg/helm/error.go +++ b/pkg/helm/error.go @@ -1,11 +1,11 @@ package helm const ( - errNotImplemented = Error("helm api not implemented") - errMissingSrvAddr = Error("missing tiller address") - errMissingTpls = Error("missing chart templates") - errMissingChart = Error("missing chart metadata") - errMissingValues = Error("missing chart values") + ErrNotImplemented = Error("helm api not implemented") + ErrInvalidSrvAddr = Error("invalid tiller address") + ErrMissingTpls = Error("missing chart templates") + ErrMissingChart = Error("missing chart metadata") + ErrMissingValues = Error("missing chart values") ) // Error represents a Helm client error. diff --git a/pkg/helm/traverse.go b/pkg/helm/traverse.go new file mode 100644 index 000000000..4edcb409f --- /dev/null +++ b/pkg/helm/traverse.go @@ -0,0 +1,110 @@ +package helm + +import ( + chartutil "github.com/kubernetes/helm/pkg/chart" +) + +// +// TODO - we should probably consolidate +// most of the code in this package, that +// is specific to charts, into chartutil. +// + +// Walk a chart's dependency tree, returning +// a pointer to the root chart. +// +// The following is an example chart dependency +// hierarchy and the structure of a chartObj +// post traversal. (note some chart files are +// omitted for brevity), +// +// mychart/ +// charts/ +// chart_A/ +// charts/ +// chart_B/ +// chart_C/ +// charts/ +// chart_F/ +// chart_D/ +// charts/ +// chart_E/ +// chart_F/ +// +// +// chart: mychart (deps = 2) +// | +// |----> chart_A (deps = 2) +// | +// |--------> chart_B (deps = 0) +// | +// |--------> chart_C (deps = 1) +// | +// |------------> chart_F (deps = 0) +// | +// |----> chart_D (deps = 2) +// | +// |--------> chart_E (deps = 0) +// | +// |--------> chart_F (deps = 0) +// +// + +func WalkChartFile(chfi *chartutil.Chart) (*chartObj, error) { + root := &chartObj{file: chfi} + err := root.walkChartDeps(chfi) + + return root, err +} + +type chartObj struct { + file *chartutil.Chart + deps []*chartObj +} + +func (chd *chartObj) File() *chartutil.Chart { + return chd.file +} + +func (chs *chartObj) walkChartDeps(chfi *chartutil.Chart) error { + if hasDeps(chfi) { + names, err := chfi.ChartDepNames() + if err != nil { + return err + } + + if len(names) > 0 { + chs.deps = append(chs.deps, resolveChartDeps(names)...) + } + } + + return nil +} + +func resolveChartDeps(names []string) (deps []*chartObj) { + for _, name := range names { + chfi, err := chartutil.LoadDir(name) + if err != nil { + return + } + + chs := &chartObj{file: chfi} + err = chs.walkChartDeps(chfi) + if err != nil { + return + } + + deps = append(deps, chs) + } + + return +} + +func hasDeps(chfi *chartutil.Chart) bool { + names, err := chfi.ChartDepNames() + if err != nil { + return false + } + + return chfi.ChartsDir() != "" && len(names) > 0 +}