mirror of https://github.com/helm/helm.git
feat(chart2proto): chart to proto transformations for helm grpc client
This commit is contained in:
parent
65a7be5618
commit
c349bfbffd
|
@ -3,22 +3,32 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/chart"
|
||||
"github.com/kubernetes/helm/pkg/helm"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
const installDesc = `
|
||||
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() {
|
||||
RootCommand.Flags()
|
||||
RootCommand.AddCommand(installCmd)
|
||||
}
|
||||
const (
|
||||
hostEnvVar = "TILLER_HOST"
|
||||
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{
|
||||
Use: "install [CHART]",
|
||||
|
@ -28,38 +38,59 @@ var installCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func runInstall(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("This command needs at least one argument, the name of the chart.")
|
||||
}
|
||||
setupInstallEnv(args)
|
||||
|
||||
ch, err := loadChart(args[0])
|
||||
res, err := helm.InstallRelease(installArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := helm.InstallRelease(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("release.name: %s\n", res.Release.Name)
|
||||
fmt.Printf("release.chart: %s\n", res.Release.Chart.Metadata.Name)
|
||||
fmt.Printf("release.status: %s\n", res.Release.Info.Status.Code)
|
||||
printRelease(res.GetRelease())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadChart(path string) (*chart.Chart, error) {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// TODO -- Display formatted description of install release status / info.
|
||||
// Might be friendly to wrap our proto model with pretty-printers.
|
||||
//
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
if fi, err := os.Stat(path); err != nil {
|
||||
return nil, err
|
||||
} else if fi.IsDir() {
|
||||
return chart.LoadDir(path)
|
||||
}
|
||||
|
||||
return chart.Load(path)
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
// note: TILLER_HOST envvar is only
|
||||
// acknowledged iff the host flag
|
||||
// does not override the default.
|
||||
if tillerHost == defaultHost {
|
||||
host := os.Getenv(hostEnvVar)
|
||||
if host != "" {
|
||||
tillerHost = host
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
|
|
@ -2,6 +2,7 @@ package chart
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
|
@ -40,6 +41,10 @@ func (v Values) Table(name string) (Values, error) {
|
|||
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) {
|
||||
v2, ok := v[simple]
|
||||
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
|
||||
|
||||
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.
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/helm/pkg/chart"
|
||||
chartpb "github.com/kubernetes/helm/pkg/proto/hapi/chart"
|
||||
chartutil "github.com/kubernetes/helm/pkg/chart"
|
||||
"github.com/kubernetes/helm/pkg/proto/hapi/services"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
@ -15,7 +14,7 @@ var Config = &config{
|
|||
|
||||
// ListReleases lists the current releases.
|
||||
func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) {
|
||||
return nil, errNotImplemented
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// 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.
|
||||
// TODO: This must take more than just name for an arg.
|
||||
func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) {
|
||||
return nil, errNotImplemented
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// 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.
|
||||
func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) {
|
||||
chpb := new(chartpb.Chart)
|
||||
|
||||
chpb.Metadata, err = mkProtoMetadata(ch.Chartfile())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
chpb.Templates, err = mkProtoTemplates(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
chpb.Dependencies, err = mkProtoChartDeps(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var vals *chartpb.Config
|
||||
|
||||
vals, err = mkProtoConfigValues(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err = Config.client().install(&services.InstallReleaseRequest{
|
||||
Chart: chpb,
|
||||
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()
|
||||
func InstallRelease(chStr string) (*services.InstallReleaseResponse, error) {
|
||||
chfi, err := chartutil.LoadChart(chStr)
|
||||
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()
|
||||
chpb, err := ChartToProto(chfi)
|
||||
if err != nil {
|
||||
return nil, errMissingValues
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = vals
|
||||
vals, err := ValuesToProto(chfi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return Config.client().install(&services.InstallReleaseRequest{
|
||||
Chart: chpb,
|
||||
Values: vals,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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…
Reference in New Issue