feat(chart2proto): chart to proto transformations for helm grpc client

pull/627/head
Brian 9 years ago committed by fibonacci1729
parent 65a7be5618
commit c349bfbffd

@ -3,22 +3,32 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/kubernetes/helm/pkg/chart"
"github.com/kubernetes/helm/pkg/helm" "github.com/kubernetes/helm/pkg/helm"
"github.com/kubernetes/helm/pkg/proto/hapi/release"
) )
const installDesc = ` const installDesc = `
This command installs a chart archive. This command installs a chart archive.
The install argument must be either a relative
path to a chart directory or the name of a
chart in the current working directory.
` `
func init() { const (
RootCommand.Flags() hostEnvVar = "TILLER_HOST"
RootCommand.AddCommand(installCmd) defaultHost = ":44134"
} )
// install flags & args
var (
installArg string // name or relative path of the chart to install
tillerHost string // override TILLER_HOST envVar
verbose bool // enable verbose install
)
var installCmd = &cobra.Command{ var installCmd = &cobra.Command{
Use: "install [CHART]", Use: "install [CHART]",
@ -28,38 +38,59 @@ var installCmd = &cobra.Command{
} }
func runInstall(cmd *cobra.Command, args []string) error { func runInstall(cmd *cobra.Command, args []string) error {
if len(args) == 0 { setupInstallEnv(args)
return fmt.Errorf("This command needs at least one argument, the name of the chart.")
}
ch, err := loadChart(args[0])
if err != nil {
return err
}
res, err := helm.InstallRelease(ch) res, err := helm.InstallRelease(installArg)
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("release.name: %s\n", res.Release.Name) printRelease(res.GetRelease())
fmt.Printf("release.chart: %s\n", res.Release.Chart.Metadata.Name)
fmt.Printf("release.status: %s\n", res.Release.Info.Status.Code)
return nil return nil
} }
func loadChart(path string) (*chart.Chart, error) { // TODO -- Display formatted description of install release status / info.
path, err := filepath.Abs(path) // Might be friendly to wrap our proto model with pretty-printers.
if err != nil { //
return nil, err func printRelease(rel *release.Release) {
if verbose {
if rel != nil {
fmt.Printf("release.name: %s\n", rel.Name)
fmt.Printf("release.info: %s\n", rel.GetInfo())
fmt.Printf("release.chart: %s\n", rel.GetChart())
}
}
}
func setupInstallEnv(args []string) {
if len(args) > 0 {
installArg = args[0]
} else {
fatalf("This command needs at least one argument, the name of the chart.")
} }
if fi, err := os.Stat(path); err != nil { // note: TILLER_HOST envvar is only
return nil, err // acknowledged iff the host flag
} else if fi.IsDir() { // does not override the default.
return chart.LoadDir(path) if tillerHost == defaultHost {
host := os.Getenv(hostEnvVar)
if host != "" {
tillerHost = host
}
} }
return chart.Load(path) helm.Config.ServAddr = tillerHost
}
func fatalf(format string, args ...interface{}) {
fmt.Printf("fatal: %s\n", fmt.Sprintf(format, args...))
os.Exit(0)
}
func init() {
installCmd.Flags().StringVar(&tillerHost, "host", defaultHost, "address of tiller server")
installCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose install")
RootCommand.AddCommand(installCmd)
} }

@ -98,6 +98,23 @@ func (c *Chart) LoadValues() (Values, error) {
return ReadValuesFile(filepath.Join(c.loader.dir(), preValues)) 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. // chartLoader provides load, close, and save implementations for a chart.
type chartLoader interface { type chartLoader interface {
// Chartfile resturns a *Chartfile for this chart. // Chartfile resturns a *Chartfile for this chart.
@ -238,6 +255,32 @@ func LoadDir(chart string) (*Chart, error) {
}, nil }, 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. // 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) {
return LoadDataFromReader(bytes.NewBuffer(data)) return LoadDataFromReader(bytes.NewBuffer(data))

@ -2,6 +2,7 @@ package chart
import ( import (
"errors" "errors"
"io"
"io/ioutil" "io/ioutil"
"strings" "strings"
@ -40,6 +41,10 @@ func (v Values) Table(name string) (Values, error) {
return table, err return table, err
} }
func (v Values) Encode(w io.Writer) error {
return toml.NewEncoder(w).Encode(v)
}
func tableLookup(v Values, simple string) (Values, error) { func tableLookup(v Values, simple string) (Values, error) {
v2, ok := v[simple] v2, ok := v[simple]
if !ok { if !ok {

@ -0,0 +1,120 @@
package helm
import (
"bytes"
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
}
chpb.Values, err = ValuesToProto(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) {
if ch == nil {
return nil, ErrMissingChart
}
vals, err := ch.LoadValues()
if err != nil {
return nil, ErrMissingValues
}
var buf bytes.Buffer
if err = vals.Encode(&buf); err != nil {
return nil, err
}
cfgVals := new(chartpbs.Config)
cfgVals.Raw = buf.String()
return cfgVals, nil
}

@ -1,11 +1,11 @@
package helm package helm
const ( const (
errNotImplemented = Error("helm api not implemented") ErrNotImplemented = Error("helm api not implemented")
errMissingSrvAddr = Error("missing tiller address") ErrInvalidSrvAddr = Error("invalid tiller address")
errMissingTpls = Error("missing chart templates") ErrMissingTpls = Error("missing chart templates")
errMissingChart = Error("missing chart metadata") ErrMissingChart = Error("missing chart metadata")
errMissingValues = Error("missing chart values") ErrMissingValues = Error("missing chart values")
) )
// Error represents a Helm client error. // Error represents a Helm client error.

@ -1,8 +1,7 @@
package helm package helm
import ( import (
"github.com/kubernetes/helm/pkg/chart" chartutil "github.com/kubernetes/helm/pkg/chart"
chartpb "github.com/kubernetes/helm/pkg/proto/hapi/chart"
"github.com/kubernetes/helm/pkg/proto/hapi/services" "github.com/kubernetes/helm/pkg/proto/hapi/services"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -15,7 +14,7 @@ var Config = &config{
// ListReleases lists the current releases. // ListReleases lists the current releases.
func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) { func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) {
return nil, errNotImplemented return nil, ErrNotImplemented
} }
// GetReleaseStatus returns the given release's status. // GetReleaseStatus returns the given release's status.
@ -45,7 +44,7 @@ func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error)
// UpdateRelease updates a release to a new/different chart. // UpdateRelease updates a release to a new/different chart.
// TODO: This must take more than just name for an arg. // TODO: This must take more than just name for an arg.
func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) { func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) {
return nil, errNotImplemented return nil, ErrNotImplemented
} }
// UninstallRelease uninstalls a named release and returns the response. // UninstallRelease uninstalls a named release and returns the response.
@ -57,90 +56,24 @@ func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) {
} }
// InstallRelease installs a new chart and returns the release response. // InstallRelease installs a new chart and returns the release response.
func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) { func InstallRelease(chStr string) (*services.InstallReleaseResponse, error) {
chpb := new(chartpb.Chart) chfi, err := chartutil.LoadChart(chStr)
chpb.Metadata, err = mkProtoMetadata(ch.Chartfile())
if err != nil { if err != nil {
return return nil, err
}
chpb.Templates, err = mkProtoTemplates(ch)
if err != nil {
return
} }
chpb.Dependencies, err = mkProtoChartDeps(ch) chpb, err := ChartToProto(chfi)
if err != nil { if err != nil {
return return nil, err
} }
var vals *chartpb.Config vals, err := ValuesToProto(chfi)
vals, err = mkProtoConfigValues(ch)
if err != nil { if err != nil {
return return nil, err
} }
res, err = Config.client().install(&services.InstallReleaseRequest{ return Config.client().install(&services.InstallReleaseRequest{
Chart: chpb, Chart: chpb,
Values: vals, Values: vals,
}) })
return
}
// pkg/chart to proto/hapi/chart helpers. temporary.
func mkProtoMetadata(ch *chart.Chartfile) (*chartpb.Metadata, error) {
if ch == nil {
return nil, errMissingChart
}
md := &chartpb.Metadata{
Name: ch.Name,
Home: ch.Home,
Version: ch.Version,
Description: ch.Description,
}
md.Sources = make([]string, len(ch.Source))
copy(md.Sources, ch.Source)
md.Keywords = make([]string, len(ch.Keywords))
copy(md.Keywords, ch.Keywords)
for _, maintainer := range ch.Maintainers {
md.Maintainers = append(md.Maintainers, &chartpb.Maintainer{
Name: maintainer.Name,
Email: maintainer.Email,
})
}
return md, nil
}
func mkProtoTemplates(ch *chart.Chart) ([]*chartpb.Template, error) {
tpls, err := ch.LoadTemplates()
if err != nil {
return nil, err
}
_ = tpls
return nil, nil
}
func mkProtoChartDeps(ch *chart.Chart) ([]*chartpb.Chart, error) {
return nil, nil
}
func mkProtoConfigValues(ch *chart.Chart) (*chartpb.Config, error) {
vals, err := ch.LoadValues()
if err != nil {
return nil, errMissingValues
}
_ = vals
return nil, nil
} }

@ -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
}
Loading…
Cancel
Save