Commit 36bf7654 authored by Brian's avatar Brian Committed by GitHub

Merge pull request #837 from fibonacci1729/ref/pkg-helm-new

ref(pkg/helm) replace  'pkg/helm' with 'pkg/helmx'
parents 1b59967d 5a1c5429
package helm
import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"k8s.io/helm/pkg/chartutil"
rls "k8s.io/helm/pkg/proto/hapi/services"
"os"
)
const (
// $HELM_HOST envvar
HelmHostEnvVar = "HELM_HOST"
// $HELM_HOME envvar
HelmHomeEnvVar = "HELM_HOME"
"k8s.io/helm/pkg/proto/hapi/services"
// Default tiller server host address.
DefaultHelmHost = ":44134"
// Default $HELM_HOME envvar value
DefaultHelmHome = "$HOME/.helm"
)
type client struct {
cfg *config
conn *grpc.ClientConn
impl services.ReleaseServiceClient
// Helm client manages client side of the helm-tiller protocol
type Client struct {
opts options
}
func NewClient(opts ...Option) *Client {
return new(Client).Init().Option(opts...)
}
// Configure the helm client with the provided options
func (h *Client) Option(opts ...Option) *Client {
for _, opt := range opts {
opt(&h.opts)
}
return h
}
// Initializes the helm client with default options
func (h *Client) Init() *Client {
return h.Option(HelmHost(DefaultHelmHost)).
Option(HelmHome(os.ExpandEnv(DefaultHelmHome)))
}
// ListReleases lists the current releases.
func (h *Client) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcListReleases(rls.NewReleaseServiceClient(c), opts...)
}
func (c *client) dial() (err error) {
c.conn, err = grpc.Dial(c.cfg.ServAddr, c.cfg.DialOpts()...)
c.impl = services.NewReleaseServiceClient(c.conn)
return
// InstallRelease installs a new chart and returns the release response.
func (h *Client) InstallRelease(chStr string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
chart, err := chartutil.Load(chStr)
if err != nil {
return nil, err
}
return h.opts.rpcInstallRelease(chart, rls.NewReleaseServiceClient(c), opts...)
}
func (c *client) install(req *services.InstallReleaseRequest) (res *services.InstallReleaseResponse, err error) {
if err = c.dial(); err != nil {
return
// UninstallRelease uninstalls a named release and returns the response.
//
// Note: there aren't currently any supported DeleteOptions, but they are
// kept in the API signature as a placeholder for future additions.
func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcDeleteRelease(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
// UpdateRelease updates a release to a new/different chart.
//
// Note: there aren't currently any supported UpdateOptions, but they
// are kept in the API signature as a placeholder for future additions.
func (h *Client) UpdateRelease(rlsName string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return c.impl.InstallRelease(context.TODO(), req, c.cfg.CallOpts()...)
return h.opts.rpcUpdateRelease(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
func (c *client) uninstall(req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
if err := c.dial(); err != nil {
// ReleaseStatus returns the given release's status.
//
// Note: there aren't currently any supported StatusOptions,
// but they are kept in the API signature as a placeholder for future additions.
func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return c.impl.UninstallRelease(context.TODO(), req, c.cfg.CallOpts()...)
return h.opts.rpcGetReleaseStatus(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
func (c *client) Close() error {
return c.conn.Close()
// ReleaseContent returns the configuration for a given release.
//
// Note: there aren't currently any supported ContentOptions, but
// they are kept in the API signature as a placeholder for future additions.
func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcGetReleaseContent(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
package helmx
package helm
import (
"k8s.io/helm/pkg/helm"
rls "k8s.io/helm/pkg/proto/hapi/services"
)
// feature toggle helmx APIs while WIP
const EnableNewHelm = false
// These APIs are a temporary abstraction layer that captures the interaction between the current cmd/helm and old
// pkg/helm implementations. Post refactor the cmd/helm package will use the APIs exposed on helm.Client directly.
......@@ -15,12 +11,8 @@ var Config struct {
ServAddr string
}
// Soon to be deprecated helm ListReleases API. See pkg/helmx.
// Soon to be deprecated helm ListReleases API.
func ListReleases(limit int, offset string, sort rls.ListSort_SortBy, order rls.ListSort_SortOrder, filter string) (*rls.ListReleasesResponse, error) {
if !EnableNewHelm {
return helm.ListReleases(limit, offset, sort, order, filter)
}
opts := []ReleaseListOption{
ReleaseListLimit(limit),
ReleaseListOffset(offset),
......@@ -31,39 +23,23 @@ func ListReleases(limit int, offset string, sort rls.ListSort_SortBy, order rls.
return NewClient(HelmHost(Config.ServAddr)).ListReleases(opts...)
}
// Soon to be deprecated helm GetReleaseStatus API. See pkg/helmx.
// Soon to be deprecated helm GetReleaseStatus API.
func GetReleaseStatus(rlsName string) (*rls.GetReleaseStatusResponse, error) {
if !EnableNewHelm {
return helm.GetReleaseStatus(rlsName)
}
return NewClient(HelmHost(Config.ServAddr)).ReleaseStatus(rlsName)
}
// Soon to be deprecated helm GetReleaseContent API. See pkg/helmx.
// Soon to be deprecated helm GetReleaseContent API.
func GetReleaseContent(rlsName string) (*rls.GetReleaseContentResponse, error) {
if !EnableNewHelm {
return helm.GetReleaseContent(rlsName)
}
return NewClient(HelmHost(Config.ServAddr)).ReleaseContent(rlsName)
}
// Soon to be deprecated helm UpdateRelease API. See pkg/helmx.
// Soon to be deprecated helm UpdateRelease API.
func UpdateRelease(rlsName string) (*rls.UpdateReleaseResponse, error) {
if !EnableNewHelm {
return helm.UpdateRelease(rlsName)
}
return NewClient(HelmHost(Config.ServAddr)).UpdateRelease(rlsName)
}
// Soon to be deprecated helm InstallRelease API. See pkg/helmx.
// Soon to be deprecated helm InstallRelease API.
func InstallRelease(vals []byte, rlsName, chStr string, dryRun bool) (*rls.InstallReleaseResponse, error) {
if !EnableNewHelm {
return helm.InstallRelease(vals, rlsName, chStr, dryRun)
}
client := NewClient(HelmHost(Config.ServAddr))
if dryRun {
client.Option(DryRun())
......@@ -71,12 +47,8 @@ func InstallRelease(vals []byte, rlsName, chStr string, dryRun bool) (*rls.Insta
return client.InstallRelease(chStr, ValueOverrides(vals), ReleaseName(rlsName))
}
// Soon to be deprecated helm UninstallRelease API. See pkg/helmx.
// Soon to be deprecated helm UninstallRelease API.
func UninstallRelease(rlsName string, dryRun bool) (*rls.UninstallReleaseResponse, error) {
if !EnableNewHelm {
return helm.UninstallRelease(rlsName, dryRun)
}
client := NewClient(HelmHost(Config.ServAddr))
if dryRun {
client.Option(DryRun())
......
package helm
import (
"google.golang.org/grpc"
)
type config struct {
ServAddr string
Insecure bool
}
func (cfg *config) DialOpts() (opts []grpc.DialOption) {
if cfg.Insecure {
opts = append(opts, grpc.WithInsecure())
} else {
// TODO: handle transport credentials
}
return
}
func (cfg *config) CallOpts() (opts []grpc.CallOption) {
return
}
func (cfg *config) client() *client {
return &client{cfg: cfg}
}
package helm
import (
"bytes"
chartutil "k8s.io/helm/pkg/chart"
chartpbs "k8s.io/helm/pkg/proto/hapi/chart"
)
// ChartToProto converts a chart to its Protobuf struct representation.
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
}
// MetadataToProto converts Chart.yaml data into protocol buffere Metadata.
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
}
// TemplatesToProto converts chart templates to their protobuf representation.
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
}
// OverridesToProto converts arbitrary TOML override data to Config data.
func OverridesToProto(values []byte) *chartpbs.Config {
return &chartpbs.Config{
Raw: string(values),
}
}
// ValuesToProto converts a chart's values.toml data to protobuf.
func ValuesToProto(ch *chartutil.Chart) (*chartpbs.Config, error) {
if ch == nil {
return nil, ErrMissingChart
}
vals, err := ch.LoadValues()
if err != nil {
//return nil, ErrMissingValues
vals = map[string]interface{}{}
}
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
}
package helm
import (
"testing"
"gopkg.in/yaml.v2"
chartutil "k8s.io/helm/pkg/chart"
)
func TestInstallReleaseOverrides(t *testing.T) {
// FIXME: This can't currently run unless a Tiller server is running, simply
// because --dry-run still uses the server. There's already a WIP for a
// testing harness, so this can be ported when that is done.
t.Skip()
vals := `name = "mariner"`
ch := "./testdata/albatross"
ir, err := InstallRelease([]byte(vals), "foo", ch, true)
if err != nil {
t.Fatalf("Failed to release: %s", err)
}
if len(ir.Release.Manifest) == 0 {
t.Fatalf("Expected a manifest.")
}
// Parse the result and see if the override worked
d := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(ir.Release.Manifest), d); err != nil {
t.Fatalf("Failed to unmarshal manifest: %s", err)
}
if d["name"] != "mariner" {
t.Errorf("Unexpected name %q", d["name"])
}
if d["home"] != "nest" {
t.Errorf("Unexpected home %q", d["home"])
}
}
func TestOverridesToProto(t *testing.T) {
override := []byte(`test = "foo"`)
c := OverridesToProto(override)
if c.Raw != string(override) {
t.Errorf("Expected %q to match %q", c.Raw, override)
}
}
func TestChartToProto(t *testing.T) {
c, err := chartutil.LoadDir("./testdata/albatross")
if err != nil {
t.Fatalf("failed to load testdata chart: %s", err)
}
p, err := ChartToProto(c)
if err != nil {
t.Fatalf("failed to conver chart to proto: %s", err)
}
if p.Metadata.Name != c.Chartfile().Name {
t.Errorf("Expected names to match.")
}
}
package helm
const (
// ErrNotImplemented indicates that this API is not implemented.
ErrNotImplemented = Error("helm api not implemented")
// ErrInvalidSrvAddr indicates an invalid address to the Tiller server.
ErrInvalidSrvAddr = Error("invalid tiller address")
// ErrMissingTpls indicates that the templates are missing from a chart.
ErrMissingTpls = Error("missing chart templates")
// ErrMissingChart indicates that the Chart.yaml data is missing.
ErrMissingChart = Error("missing chart metadata")
// ErrMissingValues indicates that the config values.yaml data is missing.
ErrMissingValues = Error("missing chart values")
)
// Error represents a Helm client error.
type Error string
// Error returns a string representation of this error.
func (e Error) Error() string {
return string(e)
}
package helm
import (
"golang.org/x/net/context"
chartutil "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/proto/hapi/services"
)
// Config defines a gRPC client's configuration.
var Config = &config{
ServAddr: ":44134",
Insecure: true,
}
// ListReleases lists the current releases.
func ListReleases(limit int, offset string, sort services.ListSort_SortBy, order services.ListSort_SortOrder, filter string) (*services.ListReleasesResponse, error) {
c := Config.client()
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
req := &services.ListReleasesRequest{
Limit: int64(limit),
Offset: offset,
SortBy: sort,
SortOrder: order,
Filter: filter,
}
cli, err := c.impl.ListReleases(context.TODO(), req, c.cfg.CallOpts()...)
if err != nil {
return nil, err
}
return cli.Recv()
}
// GetReleaseStatus returns the given release's status.
func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) {
c := Config.client()
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
req := &services.GetReleaseStatusRequest{Name: name}
return c.impl.GetReleaseStatus(context.TODO(), req, c.cfg.CallOpts()...)
}
// GetReleaseContent returns the configuration for a given release.
func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) {
c := Config.client()
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
req := &services.GetReleaseContentRequest{Name: name}
return c.impl.GetReleaseContent(context.TODO(), req, c.cfg.CallOpts()...)
}
// 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
}
// UninstallRelease uninstalls a named release and returns the response.
func UninstallRelease(name string, dryRun bool) (*services.UninstallReleaseResponse, error) {
if dryRun {
// In the dry run case, just see if the release exists.
res, err := GetReleaseContent(name)
if err != nil {
return &services.UninstallReleaseResponse{}, err
}
return &services.UninstallReleaseResponse{Release: res.Release}, nil
}
u := &services.UninstallReleaseRequest{
Name: name,
}
return Config.client().uninstall(u)
}
// InstallRelease installs a new chart and returns the release response.
func InstallRelease(rawVals []byte, name string, chStr string, dryRun bool) (*services.InstallReleaseResponse, error) {
chfi, err := chartutil.LoadChart(chStr)
if err != nil {
return nil, err
}
chpb, err := ChartToProto(chfi)
if err != nil {
return nil, err
}
vals := OverridesToProto(rawVals)
return Config.client().install(&services.InstallReleaseRequest{
Chart: chpb,
Values: vals,
DryRun: dryRun,
Name: name,
})
}
package helmx
package helm
import (
"fmt"
......
name: albatross
description: testing chart
version: 0.1.0
home: "https://k8s.io/helm"
# Test data. Not a valid Kubernetes manifest
name: {{.name}}
home: {{.home}}
package helm
import (
chartutil "k8s.io/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)
//
//
// WalkChartFile walks a chart and returns a *chartObj.
//
// FIXME: Why does an exported function return an unexported struct whose only
// exported method is to return the object passed into this method?
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
}
// File returns the *chartutil.Chart associated with this *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
}
package helmx
import (
"google.golang.org/grpc"
"k8s.io/helm/pkg/chartutil"
rls "k8s.io/helm/pkg/proto/hapi/services"
"os"
)
const (
// $HELM_HOST envvar
HelmHostEnvVar = "HELM_HOST"
// $HELM_HOME envvar
HelmHomeEnvVar = "HELM_HOME"
// Default tiller server host address.
DefaultHelmHost = ":44134"
// Default $HELM_HOME envvar value
DefaultHelmHome = "$HOME/.helm"
)
// Helm client manages client side of the helm-tiller protocol
type Client struct {
opts options
}
func NewClient(opts ...Option) *Client {
return new(Client).Init().Option(opts...)
}
// Configure the helm client with the provided options
func (h *Client) Option(opts ...Option) *Client {
for _, opt := range opts {
opt(&h.opts)
}
return h
}
// Initializes the helm client with default options
func (h *Client) Init() *Client {
return h.Option(HelmHost(DefaultHelmHost)).
Option(HelmHome(os.ExpandEnv(DefaultHelmHome)))
}
// ListReleases lists the current releases.
func (h *Client) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcListReleases(rls.NewReleaseServiceClient(c), opts...)
}
// InstallRelease installs a new chart and returns the release response.
func (h *Client) InstallRelease(chStr string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
chart, err := chartutil.Load(chStr)
if err != nil {
return nil, err
}
return h.opts.rpcInstallRelease(chart, rls.NewReleaseServiceClient(c), opts...)
}
// UninstallRelease uninstalls a named release and returns the response.
//
// Note: there aren't currently any supported DeleteOptions, but they are
// kept in the API signature as a placeholder for future additions.
func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcDeleteRelease(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
// UpdateRelease updates a release to a new/different chart.
//
// Note: there aren't currently any supported UpdateOptions, but they
// are kept in the API signature as a placeholder for future additions.
func (h *Client) UpdateRelease(rlsName string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcUpdateRelease(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
// ReleaseStatus returns the given release's status.
//
// Note: there aren't currently any supported StatusOptions,
// but they are kept in the API signature as a placeholder for future additions.
func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcGetReleaseStatus(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
// ReleaseContent returns the configuration for a given release.
//
// Note: there aren't currently any supported ContentOptions, but
// they are kept in the API signature as a placeholder for future additions.
func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
return h.opts.rpcGetReleaseContent(rlsName, rls.NewReleaseServiceClient(c), opts...)
}
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