ref(*): bypass grpc for helm client

pull/3945/head
Adam Reese 8 years ago
parent a6f0d1360d
commit 496ca54183
No known key found for this signature in database
GPG Key ID: 06F35E60A7A18DD6

@ -57,7 +57,6 @@ func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command {
SuggestFor: []string{"remove", "rm"}, SuggestFor: []string{"remove", "rm"},
Short: "given a release name, delete the release from Kubernetes", Short: "given a release name, delete the release from Kubernetes",
Long: deleteDesc, Long: deleteDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("command 'delete' requires a release name") return errors.New("command 'delete' requires a release name")
@ -97,5 +96,5 @@ func (d *deleteCmd) run() error {
fmt.Fprintln(d.out, res.Info) fmt.Fprintln(d.out, res.Info)
} }
return prettyError(err) return err
} }

@ -54,10 +54,9 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "get [flags] RELEASE_NAME", Use: "get [flags] RELEASE_NAME",
Short: "download a named release", Short: "download a named release",
Long: getHelp, Long: getHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errReleaseRequired return errReleaseRequired
@ -83,7 +82,7 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
func (g *getCmd) run() error { func (g *getCmd) run() error {
res, err := g.client.ReleaseContent(g.release, g.version) res, err := g.client.ReleaseContent(g.release, g.version)
if err != nil { if err != nil {
return prettyError(err) return err
} }
return printRelease(g.out, res) return printRelease(g.out, res)
} }

@ -44,10 +44,9 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
client: client, client: client,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "hooks [flags] RELEASE_NAME", Use: "hooks [flags] RELEASE_NAME",
Short: "download all hooks for a named release", Short: "download all hooks for a named release",
Long: getHooksHelp, Long: getHooksHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errReleaseRequired return errReleaseRequired
@ -65,7 +64,7 @@ func (g *getHooksCmd) run() error {
res, err := g.client.ReleaseContent(g.release, g.version) res, err := g.client.ReleaseContent(g.release, g.version)
if err != nil { if err != nil {
fmt.Fprintln(g.out, g.release) fmt.Fprintln(g.out, g.release)
return prettyError(err) return err
} }
for _, hook := range res.Hooks { for _, hook := range res.Hooks {

@ -46,10 +46,9 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
client: client, client: client,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "manifest [flags] RELEASE_NAME", Use: "manifest [flags] RELEASE_NAME",
Short: "download the manifest for a named release", Short: "download the manifest for a named release",
Long: getManifestHelp, Long: getManifestHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errReleaseRequired return errReleaseRequired
@ -68,7 +67,7 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
func (g *getManifestCmd) run() error { func (g *getManifestCmd) run() error {
res, err := g.client.ReleaseContent(g.release, g.version) res, err := g.client.ReleaseContent(g.release, g.version)
if err != nil { if err != nil {
return prettyError(err) return err
} }
fmt.Fprintln(g.out, res.Manifest) fmt.Fprintln(g.out, res.Manifest)
return nil return nil

@ -44,10 +44,9 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
client: client, client: client,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "values [flags] RELEASE_NAME", Use: "values [flags] RELEASE_NAME",
Short: "download the values file for a named release", Short: "download the values file for a named release",
Long: getValuesHelp, Long: getValuesHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errReleaseRequired return errReleaseRequired
@ -67,7 +66,7 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
func (g *getValuesCmd) run() error { func (g *getValuesCmd) run() error {
res, err := g.client.ReleaseContent(g.release, g.version) res, err := g.client.ReleaseContent(g.release, g.version)
if err != nil { if err != nil {
return prettyError(err) return err
} }
// If the user wants all values, compute the values and return. // If the user wants all values, compute the values and return.

@ -18,14 +18,10 @@ package main // import "k8s.io/helm/cmd/helm"
import ( import (
"fmt" "fmt"
"io/ioutil"
"log"
"os" "os"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -34,14 +30,11 @@ import (
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
helm_env "k8s.io/helm/pkg/helm/environment" helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/storage/driver"
) )
var ( var settings helm_env.EnvSettings
tillerTunnel *kube.Tunnel
settings helm_env.EnvSettings
)
var globalUsage = `The Kubernetes package manager var globalUsage = `The Kubernetes package manager
@ -73,9 +66,6 @@ func newRootCmd(args []string) *cobra.Command {
Short: "The Helm package manager for Kubernetes.", Short: "The Helm package manager for Kubernetes.",
Long: globalUsage, Long: globalUsage,
SilenceUsage: true, SilenceUsage: true,
PersistentPostRun: func(*cobra.Command, []string) {
teardown()
},
} }
flags := cmd.PersistentFlags() flags := cmd.PersistentFlags()
@ -102,18 +92,17 @@ func newRootCmd(args []string) *cobra.Command {
newHistoryCmd(nil, out), newHistoryCmd(nil, out),
newInstallCmd(nil, out), newInstallCmd(nil, out),
newListCmd(nil, out), newListCmd(nil, out),
newReleaseTestCmd(nil, out),
newRollbackCmd(nil, out), newRollbackCmd(nil, out),
newStatusCmd(nil, out), newStatusCmd(nil, out),
newUpgradeCmd(nil, out), newUpgradeCmd(nil, out),
newReleaseTestCmd(nil, out),
newVersionCmd(out),
newCompletionCmd(out), newCompletionCmd(out),
newHomeCmd(out), newHomeCmd(out),
newInitCmd(out), newInitCmd(out),
newPluginCmd(out), newPluginCmd(out),
newTemplateCmd(out), newTemplateCmd(out),
newVersionCmd(out),
// Hidden documentation generator command: 'helm docs' // Hidden documentation generator command: 'helm docs'
newDocsCmd(out), newDocsCmd(out),
@ -130,11 +119,6 @@ func newRootCmd(args []string) *cobra.Command {
return cmd return cmd
} }
func init() {
// Tell gRPC not to log to console.
grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
}
func main() { func main() {
cmd := newRootCmd(os.Args[1:]) cmd := newRootCmd(os.Args[1:])
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
@ -142,35 +126,6 @@ func main() {
} }
} }
func setupConnection() error {
if settings.TillerHost == "" {
config, client, err := getKubeClient(settings.KubeContext)
if err != nil {
return err
}
tunnel, err := portforwarder.New(settings.TillerNamespace, client, config)
if err != nil {
return err
}
settings.TillerHost = fmt.Sprintf("127.0.0.1:%d", tunnel.Local)
debug("Created tunnel using local port: '%d'\n", tunnel.Local)
}
// Set up the gRPC config.
debug("SERVER: %q\n", settings.TillerHost)
// Plugin support.
return nil
}
func teardown() {
if tillerTunnel != nil {
tillerTunnel.Close()
}
}
func checkArgsLength(argsReceived int, requiredArgs ...string) error { func checkArgsLength(argsReceived int, requiredArgs ...string) error {
expectedNum := len(requiredArgs) expectedNum := len(requiredArgs)
if argsReceived != expectedNum { if argsReceived != expectedNum {
@ -183,20 +138,6 @@ func checkArgsLength(argsReceived int, requiredArgs ...string) error {
return nil return nil
} }
// prettyError unwraps or rewrites certain errors to make them more user-friendly.
func prettyError(err error) error {
// Add this check can prevent the object creation if err is nil.
if err == nil {
return nil
}
// If it's grpc's error, make it more user-friendly.
if s, ok := status.FromError(err); ok {
return fmt.Errorf(s.Message())
}
// Else return the original error.
return err
}
// configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. // configForContext creates a Kubernetes REST client configuration for a given kubeconfig context.
func configForContext(context string) (*rest.Config, error) { func configForContext(context string) (*rest.Config, error) {
config, err := kube.GetConfig(context).ClientConfig() config, err := kube.GetConfig(context).ClientConfig()
@ -228,6 +169,15 @@ func ensureHelmClient(h helm.Interface) helm.Interface {
} }
func newClient() helm.Interface { func newClient() helm.Interface {
options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)} clientset, err := kube.New(nil).ClientSet()
return helm.NewClient(options...) if err != nil {
// TODO return error
panic(err)
}
// TODO add other backends
cfgmaps := driver.NewConfigMaps(clientset.Core().ConfigMaps(settings.TillerNamespace))
return helm.NewClient(
helm.Driver(cfgmaps),
helm.ClientSet(clientset),
)
} }

@ -74,7 +74,6 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
Long: historyHelp, Long: historyHelp,
Short: "fetch release history", Short: "fetch release history",
Aliases: []string{"hist"}, Aliases: []string{"hist"},
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
switch { switch {
case len(args) == 0: case len(args) == 0:
@ -96,15 +95,15 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command {
} }
func (cmd *historyCmd) run() error { func (cmd *historyCmd) run() error {
r, err := cmd.helmc.ReleaseHistory(cmd.rls, helm.WithMaxHistory(cmd.max)) rels, err := cmd.helmc.ReleaseHistory(cmd.rls, cmd.max)
if err != nil { if err != nil {
return prettyError(err) return err
} }
if len(r.Releases) == 0 { if len(rels) == 0 {
return nil return nil
} }
releaseHistory := getReleaseHistory(r.Releases) releaseHistory := getReleaseHistory(rels)
var history []byte var history []byte
var formattingError error var formattingError error
@ -121,7 +120,7 @@ func (cmd *historyCmd) run() error {
} }
if formattingError != nil { if formattingError != nil {
return prettyError(formattingError) return formattingError
} }
fmt.Fprintln(cmd.out, string(history)) fmt.Fprintln(cmd.out, string(history))

@ -158,10 +158,9 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "install [CHART]", Use: "install [CHART]",
Short: "install a chart archive", Short: "install a chart archive",
Long: installDesc, Long: installDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "chart name"); err != nil { if err := checkArgsLength(len(args), "chart name"); err != nil {
return err return err
@ -236,7 +235,7 @@ func (i *installCmd) run() error {
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts
chartRequested, err := chartutil.Load(i.chartPath) chartRequested, err := chartutil.Load(i.chartPath)
if err != nil { if err != nil {
return prettyError(err) return err
} }
if req, err := chartutil.LoadRequirements(chartRequested); err == nil { if req, err := chartutil.LoadRequirements(chartRequested); err == nil {
@ -254,10 +253,10 @@ func (i *installCmd) run() error {
Getters: getter.All(settings), Getters: getter.All(settings),
} }
if err := man.Update(); err != nil { if err := man.Update(); err != nil {
return prettyError(err) return err
} }
} else { } else {
return prettyError(err) return err
} }
} }
@ -265,7 +264,7 @@ func (i *installCmd) run() error {
return fmt.Errorf("cannot load requirements: %v", err) return fmt.Errorf("cannot load requirements: %v", err)
} }
res, err := i.client.InstallReleaseFromChart( rel, err := i.client.InstallReleaseFromChart(
chartRequested, chartRequested,
i.namespace, i.namespace,
helm.ValueOverrides(rawVals), helm.ValueOverrides(rawVals),
@ -276,10 +275,9 @@ func (i *installCmd) run() error {
helm.InstallTimeout(i.timeout), helm.InstallTimeout(i.timeout),
helm.InstallWait(i.wait)) helm.InstallWait(i.wait))
if err != nil { if err != nil {
return prettyError(err) return err
} }
rel := res.GetRelease()
if rel == nil { if rel == nil {
return nil return nil
} }
@ -291,9 +289,9 @@ func (i *installCmd) run() error {
} }
// Print the status like status command does // Print the status like status command does
status, err := i.client.ReleaseStatus(rel.Name) status, err := i.client.ReleaseStatus(rel.Name, 0)
if err != nil { if err != nil {
return prettyError(err) return err
} }
PrintStatus(i.out, status) PrintStatus(i.out, status)
return nil return nil

@ -88,7 +88,6 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command {
Short: "list releases", Short: "list releases",
Long: listHelp, Long: listHelp,
Aliases: []string{"ls"}, Aliases: []string{"ls"},
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 { if len(args) > 0 {
list.filter = strings.Join(args, " ") list.filter = strings.Join(args, " ")
@ -145,7 +144,7 @@ func (l *listCmd) run() error {
) )
if err != nil { if err != nil {
return prettyError(err) return err
} }
if len(res) == 0 { if len(res) == 0 {

@ -100,10 +100,8 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
if md.UseTunnel { if md.UseTunnel {
c.PreRunE = func(cmd *cobra.Command, args []string) error { c.PreRunE = func(cmd *cobra.Command, args []string) error {
// Parse the parent flag, but not the local flags. // Parse the parent flag, but not the local flags.
if _, err := processParent(cmd, args); err != nil { _, err := processParent(cmd, args)
return err return err
}
return setupConnection()
} }
} }

@ -48,10 +48,9 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "test [RELEASE]", Use: "test [RELEASE]",
Short: "test a release", Short: "test a release",
Long: releaseTestDesc, Long: releaseTestDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name"); err != nil { if err := checkArgsLength(len(args), "release name"); err != nil {
return err return err
@ -81,10 +80,10 @@ func (t *releaseTestCmd) run() (err error) {
for { for {
select { select {
case err := <-errc: case err := <-errc:
if prettyError(err) == nil && testErr.failed > 0 { if err == nil && testErr.failed > 0 {
return testErr.Error() return testErr.Error()
} }
return prettyError(err) return err
case res, ok := <-c: case res, ok := <-c:
if !ok { if !ok {
break break

@ -54,10 +54,9 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rollback [flags] [RELEASE] [REVISION]", Use: "rollback [flags] [RELEASE] [REVISION]",
Short: "roll back a release to a previous revision", Short: "roll back a release to a previous revision",
Long: rollbackDesc, Long: rollbackDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "revision number"); err != nil { if err := checkArgsLength(len(args), "release name", "revision number"); err != nil {
return err return err
@ -98,7 +97,7 @@ func (r *rollbackCmd) run() error {
helm.RollbackTimeout(r.timeout), helm.RollbackTimeout(r.timeout),
helm.RollbackWait(r.wait)) helm.RollbackWait(r.wait))
if err != nil { if err != nil {
return prettyError(err) return err
} }
fmt.Fprintf(r.out, "Rollback was a success! Happy Helming!\n") fmt.Fprintf(r.out, "Rollback was a success! Happy Helming!\n")

@ -60,10 +60,9 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "status [flags] RELEASE_NAME", Use: "status [flags] RELEASE_NAME",
Short: "displays the status of the named release", Short: "displays the status of the named release",
Long: statusHelp, Long: statusHelp,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errReleaseRequired return errReleaseRequired
@ -83,9 +82,9 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
func (s *statusCmd) run() error { func (s *statusCmd) run() error {
res, err := s.client.ReleaseStatus(s.release, helm.StatusReleaseVersion(s.version)) res, err := s.client.ReleaseStatus(s.release, s.version)
if err != nil { if err != nil {
return prettyError(err) return err
} }
switch s.outfmt { switch s.outfmt {

@ -168,12 +168,12 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
// Check chart requirements to make sure all dependencies are present in /charts // Check chart requirements to make sure all dependencies are present in /charts
c, err := chartutil.Load(t.chartPath) c, err := chartutil.Load(t.chartPath)
if err != nil { if err != nil {
return prettyError(err) return err
} }
if req, err := chartutil.LoadRequirements(c); err == nil { if req, err := chartutil.LoadRequirements(c); err == nil {
if err := checkDependencies(c, req); err != nil { if err := checkDependencies(c, req); err != nil {
return prettyError(err) return err
} }
} else if err != chartutil.ErrRequirementsNotFound { } else if err != chartutil.ErrRequirementsNotFound {
return fmt.Errorf("cannot load requirements: %v", err) return fmt.Errorf("cannot load requirements: %v", err)

@ -92,10 +92,9 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]", Use: "upgrade [RELEASE] [CHART]",
Short: "upgrade a release", Short: "upgrade a release",
Long: upgradeDesc, Long: upgradeDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "chart path"); err != nil { if err := checkArgsLength(len(args), "release name", "chart path"); err != nil {
return err return err
@ -158,13 +157,13 @@ func (u *upgradeCmd) run() error {
// The returned error is a grpc.rpcError that wraps the message from the original error. // The returned error is a grpc.rpcError that wraps the message from the original error.
// So we're stuck doing string matching against the wrapped error, which is nested somewhere // So we're stuck doing string matching against the wrapped error, which is nested somewhere
// inside of the grpc.rpcError message. // inside of the grpc.rpcError message.
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1)) releaseHistory, err := u.client.ReleaseHistory(u.release, 1)
if err == nil { if err == nil {
if u.namespace == "" { if u.namespace == "" {
u.namespace = defaultNamespace() u.namespace = defaultNamespace()
} }
previousReleaseNamespace := releaseHistory.Releases[0].Namespace previousReleaseNamespace := releaseHistory[0].Namespace
if previousReleaseNamespace != u.namespace { if previousReleaseNamespace != u.namespace {
fmt.Fprintf(u.out, fmt.Fprintf(u.out,
"WARNING: Namespace %q doesn't match with previous. Release will be deployed to %s\n", "WARNING: Namespace %q doesn't match with previous. Release will be deployed to %s\n",
@ -210,7 +209,7 @@ func (u *upgradeCmd) run() error {
return fmt.Errorf("cannot load requirements: %v", err) return fmt.Errorf("cannot load requirements: %v", err)
} }
} else { } else {
return prettyError(err) return err
} }
resp, err := u.client.UpdateRelease( resp, err := u.client.UpdateRelease(
@ -226,19 +225,19 @@ func (u *upgradeCmd) run() error {
helm.ReuseValues(u.reuseValues), helm.ReuseValues(u.reuseValues),
helm.UpgradeWait(u.wait)) helm.UpgradeWait(u.wait))
if err != nil { if err != nil {
return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) return fmt.Errorf("UPGRADE FAILED: %v", err)
} }
if settings.Debug { if settings.Debug {
printRelease(u.out, resp.Release) printRelease(u.out, resp)
} }
fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release) fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release)
// Print the status like status command does // Print the status like status command does
status, err := u.client.ReleaseStatus(u.release) status, err := u.client.ReleaseStatus(u.release, 0)
if err != nil { if err != nil {
return prettyError(err) return err
} }
PrintStatus(u.out, status) PrintStatus(u.out, status)

@ -25,76 +25,78 @@ import (
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
rls "k8s.io/helm/pkg/proto/hapi/services" rls "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/tiller" "k8s.io/helm/pkg/tiller"
"k8s.io/helm/pkg/tiller/environment"
) )
// maxMsgSize use 20MB as the default message size limit. // maxMsgSize use 20MB as the default message size limit.
// grpc library default is 4MB // grpc library default is 4MB
const maxMsgSize = 1024 * 1024 * 20 const maxMsgSize = 1024 * 1024 * 20
type Tiller = tiller.ReleaseServer
// Client manages client side of the Helm-Tiller protocol. // Client manages client side of the Helm-Tiller protocol.
type Client struct { type Client struct {
opts options opts options
store *storage.Storage tiller *tiller.ReleaseServer
tiller *Tiller
} }
// NewClient creates a new client. // NewClient creates a new client.
func NewClient(opts ...Option) *Client { func NewClient(opts ...Option) *Client {
var c Client var c Client
c.store = storage.Init(driver.NewMemory()) return c.Option(opts...).init()
// set some sane defaults }
c.Option(ConnectTimeout(5))
return c.Option(opts...) func (c *Client) init() *Client {
env := environment.New()
env.Releases = storage.Init(c.opts.driver)
// TODO
env.KubeClient = kube.New(nil)
c.tiller = tiller.NewReleaseServer(env, c.opts.clientset)
return c
} }
// Option configures the Helm client with the provided options. // Option configures the Helm client with the provided options.
func (h *Client) Option(opts ...Option) *Client { func (c *Client) Option(opts ...Option) *Client {
for _, opt := range opts { for _, opt := range opts {
opt(&h.opts) opt(&c.opts)
} }
return h return c
} }
// ListReleases lists the current releases. // ListReleases lists the current releases.
func (h *Client) ListReleases(opts ...ReleaseListOption) ([]*release.Release, error) { func (c *Client) ListReleases(opts ...ReleaseListOption) ([]*release.Release, error) {
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts { for _, opt := range opts {
opt(&reqOpts) opt(&reqOpts)
} }
req := &reqOpts.listReq req := &reqOpts.listReq
ctx := NewContext() if err := reqOpts.runBefore(req); err != nil {
return nil, err
if reqOpts.before != nil {
if err := reqOpts.before(ctx, req); err != nil {
return nil, err
}
} }
return h.tiller.ListReleases(req) return c.tiller.ListReleases(req)
} }
// InstallRelease loads a chart from chstr, installs it, and returns the release response. // InstallRelease loads a chart from chstr, installs it, and returns the release response.
func (h *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { func (c *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*release.Release, error) {
// load the chart to install // load the chart to install
chart, err := chartutil.Load(chstr) chart, err := chartutil.Load(chstr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return h.InstallReleaseFromChart(chart, ns, opts...) return c.InstallReleaseFromChart(chart, ns, opts...)
} }
// InstallReleaseFromChart installs a new chart and returns the release response. // InstallReleaseFromChart installs a new chart and returns the release response.
func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { func (c *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*release.Release, error) {
// apply the install options // apply the install options
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts { for _, opt := range opts {
opt(&reqOpts) opt(&reqOpts)
} }
@ -104,12 +106,9 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...
req.DryRun = reqOpts.dryRun req.DryRun = reqOpts.dryRun
req.DisableHooks = reqOpts.disableHooks req.DisableHooks = reqOpts.disableHooks
req.ReuseName = reqOpts.reuseName req.ReuseName = reqOpts.reuseName
ctx := NewContext()
if reqOpts.before != nil { if err := reqOpts.runBefore(req); err != nil {
if err := reqOpts.before(ctx, req); err != nil { return nil, err
return nil, err
}
} }
err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values) err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values)
if err != nil { if err != nil {
@ -120,20 +119,20 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...
return nil, err return nil, err
} }
return h.install(ctx, req) return c.tiller.InstallRelease(req)
} }
// DeleteRelease uninstalls a named release and returns the response. // DeleteRelease uninstalls a named release and returns the response.
func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { func (c *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
// apply the uninstall options // apply the uninstall options
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts { for _, opt := range opts {
opt(&reqOpts) opt(&reqOpts)
} }
if reqOpts.dryRun { if reqOpts.dryRun {
// In the dry run case, just see if the release exists // In the dry run case, just see if the release exists
r, err := h.ReleaseContent(rlsName, 0) r, err := c.ReleaseContent(rlsName, 0)
if err != nil { if err != nil {
return &rls.UninstallReleaseResponse{}, err return &rls.UninstallReleaseResponse{}, err
} }
@ -143,31 +142,28 @@ func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.Unins
req := &reqOpts.uninstallReq req := &reqOpts.uninstallReq
req.Name = rlsName req.Name = rlsName
req.DisableHooks = reqOpts.disableHooks req.DisableHooks = reqOpts.disableHooks
ctx := NewContext()
if reqOpts.before != nil { if err := reqOpts.runBefore(req); err != nil {
if err := reqOpts.before(ctx, req); err != nil { return nil, err
return nil, err
}
} }
return h.delete(ctx, req) return c.tiller.UninstallRelease(req)
} }
// UpdateRelease loads a chart from chstr and updates a release to a new/different chart. // UpdateRelease loads a chart from chstr and updates a release to a new/different chart.
func (h *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (c *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*release.Release, error) {
// load the chart to update // load the chart to update
chart, err := chartutil.Load(chstr) chart, err := chartutil.Load(chstr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return h.UpdateReleaseFromChart(rlsName, chart, opts...) return c.UpdateReleaseFromChart(rlsName, chart, opts...)
} }
// UpdateReleaseFromChart updates a release to a new/different chart. // UpdateReleaseFromChart updates a release to a new/different chart.
func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (c *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*release.Release, error) {
// apply the update options // apply the update options
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts { for _, opt := range opts {
opt(&reqOpts) opt(&reqOpts)
} }
@ -180,12 +176,9 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts
req.Force = reqOpts.force req.Force = reqOpts.force
req.ResetValues = reqOpts.resetValues req.ResetValues = reqOpts.resetValues
req.ReuseValues = reqOpts.reuseValues req.ReuseValues = reqOpts.reuseValues
ctx := NewContext()
if reqOpts.before != nil { if err := reqOpts.runBefore(req); err != nil {
if err := reqOpts.before(ctx, req); err != nil { return nil, err
return nil, err
}
} }
err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values) err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values)
if err != nil { if err != nil {
@ -196,12 +189,12 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts
return nil, err return nil, err
} }
return h.update(ctx, req) return c.tiller.UpdateRelease(req)
} }
// RollbackRelease rolls back a release to the previous version. // RollbackRelease rolls back a release to the previous version.
func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) { func (c *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*release.Release, error) {
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts { for _, opt := range opts {
opt(&reqOpts) opt(&reqOpts)
} }
@ -211,78 +204,68 @@ func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.R
req.DisableHooks = reqOpts.disableHooks req.DisableHooks = reqOpts.disableHooks
req.DryRun = reqOpts.dryRun req.DryRun = reqOpts.dryRun
req.Name = rlsName req.Name = rlsName
ctx := NewContext()
if reqOpts.before != nil { if err := reqOpts.runBefore(req); err != nil {
if err := reqOpts.before(ctx, req); err != nil { return nil, err
return nil, err
}
} }
return h.rollback(ctx, req) return c.tiller.RollbackRelease(req)
} }
// ReleaseStatus returns the given release's status. // ReleaseStatus returns the given release's status.
func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { func (c *Client) ReleaseStatus(rlsName string, version int32) (*rls.GetReleaseStatusResponse, error) {
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts {
opt(&reqOpts)
}
req := &reqOpts.statusReq req := &reqOpts.statusReq
req.Name = rlsName req.Name = rlsName
ctx := NewContext() req.Version = version
if reqOpts.before != nil { if err := reqOpts.runBefore(req); err != nil {
if err := reqOpts.before(ctx, req); err != nil { return nil, err
return nil, err
}
} }
return h.status(ctx, req) return c.tiller.GetReleaseStatus(req)
} }
// ReleaseContent returns the configuration for a given release. // ReleaseContent returns the configuration for a given release.
func (c *Client) ReleaseContent(name string, version int32) (*release.Release, error) { func (c *Client) ReleaseContent(name string, version int32) (*release.Release, error) {
if version <= 0 { reqOpts := c.opts
return c.store.Last(name) req := &reqOpts.contentReq
req.Name = name
req.Version = version
if err := reqOpts.runBefore(req); err != nil {
return nil, err
} }
return c.store.Get(name, version) return c.tiller.GetReleaseContent(req)
} }
// ReleaseHistory returns a release's revision history. // ReleaseHistory returns a release's revision history.
func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) { func (c *Client) ReleaseHistory(rlsName string, max int32) ([]*release.Release, error) {
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts {
opt(&reqOpts)
}
req := &reqOpts.histReq req := &reqOpts.histReq
req.Name = rlsName req.Name = rlsName
ctx := NewContext() req.Max = max
if reqOpts.before != nil { if err := reqOpts.runBefore(req); err != nil {
if err := reqOpts.before(ctx, req); err != nil { return nil, err
return nil, err
}
} }
return h.history(ctx, req) return c.tiller.GetHistory(req)
} }
// RunReleaseTest executes a pre-defined test on a release. // RunReleaseTest executes a pre-defined test on a release.
func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) { func (c *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
reqOpts := h.opts reqOpts := c.opts
for _, opt := range opts { for _, opt := range opts {
opt(&reqOpts) opt(&reqOpts)
} }
req := &reqOpts.testReq req := &reqOpts.testReq
req.Name = rlsName req.Name = rlsName
ctx := NewContext()
return h.test(ctx, req) return c.test(req)
} }
// connect returns a gRPC connection to Tiller or error. The gRPC dial options // connect returns a gRPC connection to Tiller or error. The gRPC dial options
// are constructed here. // are constructed here.
func (h *Client) connect(ctx context.Context) (conn *grpc.ClientConn, err error) { func (c *Client) connect() (conn *grpc.ClientConn, err error) {
opts := []grpc.DialOption{ opts := []grpc.DialOption{
grpc.WithBlock(), grpc.WithBlock(),
grpc.WithKeepaliveParams(keepalive.ClientParameters{ grpc.WithKeepaliveParams(keepalive.ClientParameters{
@ -293,121 +276,18 @@ func (h *Client) connect(ctx context.Context) (conn *grpc.ClientConn, err error)
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)),
} }
opts = append(opts, grpc.WithInsecure()) opts = append(opts, grpc.WithInsecure())
ctx, cancel := context.WithTimeout(ctx, h.opts.connectTimeout) ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel() defer cancel()
if conn, err = grpc.DialContext(ctx, h.opts.host, opts...); err != nil { if conn, err = grpc.DialContext(ctx, c.opts.host, opts...); err != nil {
return nil, err return nil, err
} }
return conn, nil return conn, nil
} }
// Executes tiller.ListReleases RPC.
func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
s, err := rlc.ListReleases(ctx, req)
if err != nil {
return nil, err
}
var resp *rls.ListReleasesResponse
for {
r, err := s.Recv()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if resp == nil {
resp = r
continue
}
resp.Releases = append(resp.Releases, r.GetReleases()[0])
}
return resp, nil
}
// Executes tiller.InstallRelease RPC.
func (h *Client) install(ctx context.Context, req *rls.InstallReleaseRequest) (*rls.InstallReleaseResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.InstallRelease(ctx, req)
}
// Executes tiller.UninstallRelease RPC.
func (h *Client) delete(ctx context.Context, req *rls.UninstallReleaseRequest) (*rls.UninstallReleaseResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.UninstallRelease(ctx, req)
}
// Executes tiller.UpdateRelease RPC.
func (h *Client) update(ctx context.Context, req *rls.UpdateReleaseRequest) (*rls.UpdateReleaseResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.UpdateRelease(ctx, req)
}
// Executes tiller.RollbackRelease RPC.
func (h *Client) rollback(ctx context.Context, req *rls.RollbackReleaseRequest) (*rls.RollbackReleaseResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.RollbackRelease(ctx, req)
}
// Executes tiller.GetReleaseStatus RPC.
func (h *Client) status(ctx context.Context, req *rls.GetReleaseStatusRequest) (*rls.GetReleaseStatusResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.GetReleaseStatus(ctx, req)
}
// Executes tiller.GetHistory RPC.
func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.GetHistoryResponse, error) {
c, err := h.connect(ctx)
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.GetHistory(ctx, req)
}
// Executes tiller.TestRelease RPC. // Executes tiller.TestRelease RPC.
func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) { func (c *Client) test(req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) {
errc := make(chan error, 1) errc := make(chan error, 1)
c, err := h.connect(ctx) conn, err := c.connect()
if err != nil { if err != nil {
errc <- err errc <- err
return nil, errc return nil, errc
@ -417,10 +297,10 @@ func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan
go func() { go func() {
defer close(errc) defer close(errc)
defer close(ch) defer close(ch)
defer c.Close() defer conn.Close()
rlc := rls.NewReleaseServiceClient(c) rlc := rls.NewReleaseServiceClient(conn)
s, err := rlc.RunReleaseTest(ctx, req) s, err := rlc.RunReleaseTest(context.TODO(), req)
if err != nil { if err != nil {
errc <- err errc <- err
return return

@ -1,34 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helm
import (
"testing"
"time"
)
func TestNewClient(t *testing.T) {
helmClient := NewClient()
if helmClient.opts.connectTimeout != 5*time.Second {
t.Errorf("expected default timeout duration to be 5 seconds, got %v", helmClient.opts.connectTimeout)
}
helmClient = NewClient(ConnectTimeout(60))
if helmClient.opts.connectTimeout != time.Minute {
t.Errorf("expected timeout duration to be 1 minute, got %v", helmClient.opts.connectTimeout)
}
}

@ -27,7 +27,6 @@ import (
"path/filepath" "path/filepath"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/client-go/util/homedir" "k8s.io/client-go/util/homedir"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )

@ -53,13 +53,13 @@ func (c *FakeClient) ListReleases(opts ...ReleaseListOption) ([]*release.Release
} }
// InstallRelease creates a new release and returns a InstallReleaseResponse containing that release // InstallRelease creates a new release and returns a InstallReleaseResponse containing that release
func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*release.Release, error) {
chart := &chart.Chart{} chart := &chart.Chart{}
return c.InstallReleaseFromChart(chart, ns, opts...) return c.InstallReleaseFromChart(chart, ns, opts...)
} }
// InstallReleaseFromChart adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release // InstallReleaseFromChart adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release
func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*release.Release, error) {
for _, opt := range opts { for _, opt := range opts {
opt(&c.Opts) opt(&c.Opts)
} }
@ -67,17 +67,14 @@ func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts
releaseName := c.Opts.instReq.Name releaseName := c.Opts.instReq.Name
// Check to see if the release already exists. // Check to see if the release already exists.
rel, err := c.ReleaseStatus(releaseName, nil) rel, err := c.ReleaseStatus(releaseName, 0)
if err == nil && rel != nil { if err == nil && rel != nil {
return nil, errors.New("cannot re-use a name that is still in use") return nil, errors.New("cannot re-use a name that is still in use")
} }
release := ReleaseMock(&MockReleaseOptions{Name: releaseName, Namespace: ns}) release := ReleaseMock(&MockReleaseOptions{Name: releaseName, Namespace: ns})
c.Rels = append(c.Rels, release) c.Rels = append(c.Rels, release)
return release, nil
return &rls.InstallReleaseResponse{
Release: release,
}, nil
} }
// DeleteRelease deletes a release from the FakeClient // DeleteRelease deletes a release from the FakeClient
@ -95,28 +92,23 @@ func (c *FakeClient) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.U
} }
// UpdateRelease returns an UpdateReleaseResponse containing the updated release, if it exists // UpdateRelease returns an UpdateReleaseResponse containing the updated release, if it exists
func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*release.Release, error) {
return c.UpdateReleaseFromChart(rlsName, &chart.Chart{}, opts...) return c.UpdateReleaseFromChart(rlsName, &chart.Chart{}, opts...)
} }
// UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists // UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists
func (c *FakeClient) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (c *FakeClient) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*release.Release, error) {
// Check to see if the release already exists. // Check to see if the release already exists.
rel, err := c.ReleaseContent(rlsName, 0) return c.ReleaseContent(rlsName, 0)
if err != nil {
return nil, err
}
return &rls.UpdateReleaseResponse{Release: rel}, nil
} }
// RollbackRelease returns nil, nil // RollbackRelease returns nil, nil
func (c *FakeClient) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) { func (c *FakeClient) RollbackRelease(rlsName string, opts ...RollbackOption) (*release.Release, error) {
return nil, nil return nil, nil
} }
// ReleaseStatus returns a release status response with info from the matching release name. // ReleaseStatus returns a release status response with info from the matching release name.
func (c *FakeClient) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { func (c *FakeClient) ReleaseStatus(rlsName string, version int32) (*rls.GetReleaseStatusResponse, error) {
for _, rel := range c.Rels { for _, rel := range c.Rels {
if rel.Name == rlsName { if rel.Name == rlsName {
return &rls.GetReleaseStatusResponse{ return &rls.GetReleaseStatusResponse{
@ -140,8 +132,8 @@ func (c *FakeClient) ReleaseContent(rlsName string, version int32) (*release.Rel
} }
// ReleaseHistory returns a release's revision history. // ReleaseHistory returns a release's revision history.
func (c *FakeClient) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) { func (c *FakeClient) ReleaseHistory(rlsName string, max int32) ([]*release.Release, error) {
return &rls.GetHistoryResponse{Releases: c.Rels}, nil return c.Rels, nil
} }
// RunReleaseTest executes a pre-defined tests on a release // RunReleaseTest executes a pre-defined tests on a release

@ -34,7 +34,6 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
} }
type args struct { type args struct {
rlsName string rlsName string
opts []StatusOption
} }
tests := []struct { tests := []struct {
name string name string
@ -52,7 +51,6 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
}, },
args: args{ args: args{
rlsName: releasePresent.Name, rlsName: releasePresent.Name,
opts: nil,
}, },
want: &rls.GetReleaseStatusResponse{ want: &rls.GetReleaseStatusResponse{
Name: releasePresent.Name, Name: releasePresent.Name,
@ -71,7 +69,6 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
}, },
args: args{ args: args{
rlsName: releaseNotPresent.Name, rlsName: releaseNotPresent.Name,
opts: nil,
}, },
want: nil, want: nil,
wantErr: true, wantErr: true,
@ -87,7 +84,6 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
}, },
args: args{ args: args{
rlsName: releasePresent.Name, rlsName: releasePresent.Name,
opts: nil,
}, },
want: &rls.GetReleaseStatusResponse{ want: &rls.GetReleaseStatusResponse{
Name: releasePresent.Name, Name: releasePresent.Name,
@ -104,7 +100,7 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
c := &FakeClient{ c := &FakeClient{
Rels: tt.fields.Rels, Rels: tt.fields.Rels,
} }
got, err := c.ReleaseStatus(tt.args.rlsName, tt.args.opts...) got, err := c.ReleaseStatus(tt.args.rlsName, 0)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("FakeClient.ReleaseStatus() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("FakeClient.ReleaseStatus() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -129,7 +125,7 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
name string name string
fields fields fields fields
args args args args
want *rls.InstallReleaseResponse want *release.Release
relsAfter []*release.Release relsAfter []*release.Release
wantErr bool wantErr bool
}{ }{
@ -142,9 +138,7 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
ns: "default", ns: "default",
opts: []InstallOption{ReleaseName("new-release")}, opts: []InstallOption{ReleaseName("new-release")},
}, },
want: &rls.InstallReleaseResponse{ want: ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
Release: ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
},
relsAfter: []*release.Release{ relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}), ReleaseMock(&MockReleaseOptions{Name: "new-release"}),
}, },

@ -23,7 +23,6 @@ import (
"testing" "testing"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
cpb "k8s.io/helm/pkg/proto/hapi/chart" cpb "k8s.io/helm/pkg/proto/hapi/chart"
@ -76,7 +75,7 @@ func TestListReleases_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client ListReleasesRequest // BeforeCall option to intercept Helm client ListReleasesRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.ListReleasesRequest: case *tpb.ListReleasesRequest:
t.Logf("ListReleasesRequest: %#+v\n", act) t.Logf("ListReleasesRequest: %#+v\n", act)
@ -130,7 +129,7 @@ func TestInstallRelease_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client InstallReleaseRequest // BeforeCall option to intercept Helm client InstallReleaseRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.InstallReleaseRequest: case *tpb.InstallReleaseRequest:
t.Logf("InstallReleaseRequest: %#+v\n", act) t.Logf("InstallReleaseRequest: %#+v\n", act)
@ -171,7 +170,7 @@ func TestDeleteRelease_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client DeleteReleaseRequest // BeforeCall option to intercept Helm client DeleteReleaseRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.UninstallReleaseRequest: case *tpb.UninstallReleaseRequest:
t.Logf("UninstallReleaseRequest: %#+v\n", act) t.Logf("UninstallReleaseRequest: %#+v\n", act)
@ -218,7 +217,7 @@ func TestUpdateRelease_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client UpdateReleaseRequest // BeforeCall option to intercept Helm client UpdateReleaseRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.UpdateReleaseRequest: case *tpb.UpdateReleaseRequest:
t.Logf("UpdateReleaseRequest: %#+v\n", act) t.Logf("UpdateReleaseRequest: %#+v\n", act)
@ -262,7 +261,7 @@ func TestRollbackRelease_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client RollbackReleaseRequest // BeforeCall option to intercept Helm client RollbackReleaseRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.RollbackReleaseRequest: case *tpb.RollbackReleaseRequest:
t.Logf("RollbackReleaseRequest: %#+v\n", act) t.Logf("RollbackReleaseRequest: %#+v\n", act)
@ -295,7 +294,7 @@ func TestReleaseStatus_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client GetReleaseStatusRequest // BeforeCall option to intercept Helm client GetReleaseStatusRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.GetReleaseStatusRequest: case *tpb.GetReleaseStatusRequest:
t.Logf("GetReleaseStatusRequest: %#+v\n", act) t.Logf("GetReleaseStatusRequest: %#+v\n", act)
@ -307,7 +306,7 @@ func TestReleaseStatus_VerifyOptions(t *testing.T) {
}) })
client := NewClient(b4c) client := NewClient(b4c)
if _, err := client.ReleaseStatus(releaseName, StatusReleaseVersion(revision)); err != errSkip { if _, err := client.ReleaseStatus(releaseName, revision); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err) t.Fatalf("did not expect error but got (%v)\n``", err)
} }
@ -329,7 +328,7 @@ func TestReleaseContent_VerifyOptions(t *testing.T) {
} }
// BeforeCall option to intercept Helm client GetReleaseContentRequest // BeforeCall option to intercept Helm client GetReleaseContentRequest
b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { b4c := BeforeCall(func(msg proto.Message) error {
switch act := msg.(type) { switch act := msg.(type) {
case *tpb.GetReleaseContentRequest: case *tpb.GetReleaseContentRequest:
t.Logf("GetReleaseContentRequest: %#+v\n", act) t.Logf("GetReleaseContentRequest: %#+v\n", act)

@ -25,14 +25,14 @@ import (
// Interface for helm client for mocking in tests // Interface for helm client for mocking in tests
type Interface interface { type Interface interface {
ListReleases(opts ...ReleaseListOption) ([]*release.Release, error) ListReleases(opts ...ReleaseListOption) ([]*release.Release, error)
InstallRelease(chStr, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) InstallRelease(chStr, namespace string, opts ...InstallOption) (*release.Release, error)
InstallReleaseFromChart(chart *chart.Chart, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) InstallReleaseFromChart(chart *chart.Chart, namespace string, opts ...InstallOption) (*release.Release, error)
DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error)
ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) ReleaseStatus(rlsName string, version int32) (*rls.GetReleaseStatusResponse, error)
UpdateRelease(rlsName, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) UpdateRelease(rlsName, chStr string, opts ...UpdateOption) (*release.Release, error)
UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*release.Release, error)
RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) RollbackRelease(rlsName string, opts ...RollbackOption) (*release.Release, error)
ReleaseContent(rlsName string, version int32) (*release.Release, error) ReleaseContent(rlsName string, version int32) (*release.Release, error)
ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) ReleaseHistory(rlsName string, max int32) ([]*release.Release, error)
RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error)
} }

@ -17,16 +17,13 @@ limitations under the License.
package helm package helm
import ( import (
"time"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"golang.org/x/net/context" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"google.golang.org/grpc/metadata"
cpb "k8s.io/helm/pkg/proto/hapi/chart" cpb "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
rls "k8s.io/helm/pkg/proto/hapi/services" rls "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/storage/driver"
) )
// Option allows specifying various settings configurable by // Option allows specifying various settings configurable by
@ -63,7 +60,7 @@ type options struct {
// release rollback options are applied directly to the rollback release request // release rollback options are applied directly to the rollback release request
rollbackReq rls.RollbackReleaseRequest rollbackReq rls.RollbackReleaseRequest
// before intercepts client calls before sending // before intercepts client calls before sending
before func(context.Context, proto.Message) error before func(proto.Message) error
// release history options are applied directly to the get release history request // release history options are applied directly to the get release history request
histReq rls.GetHistoryRequest histReq rls.GetHistoryRequest
// resetValues instructs Tiller to reset values to their defaults. // resetValues instructs Tiller to reset values to their defaults.
@ -72,21 +69,22 @@ type options struct {
reuseValues bool reuseValues bool
// release test options are applied directly to the test release history request // release test options are applied directly to the test release history request
testReq rls.TestReleaseRequest testReq rls.TestReleaseRequest
// connectTimeout specifies the time duration Helm will wait to establish a connection to tiller
connectTimeout time.Duration driver driver.Driver
clientset internalclientset.Interface
} }
// Host specifies the host address of the Tiller release server, (default = ":44134"). func (opts *options) runBefore(msg proto.Message) error {
func Host(host string) Option { if opts.before != nil {
return func(opts *options) { return opts.before(msg)
opts.host = host
} }
return nil
} }
// BeforeCall returns an option that allows intercepting a helm client rpc // BeforeCall returns an option that allows intercepting a helm client rpc
// before being sent OTA to tiller. The intercepting function should return // before being sent OTA to tiller. The intercepting function should return
// an error to indicate that the call should not proceed or nil otherwise. // an error to indicate that the call should not proceed or nil otherwise.
func BeforeCall(fn func(context.Context, proto.Message) error) Option { func BeforeCall(fn func(proto.Message) error) Option {
return func(opts *options) { return func(opts *options) {
opts.before = fn opts.before = fn
} }
@ -168,13 +166,6 @@ func ReleaseName(name string) InstallOption {
} }
} }
// ConnectTimeout specifies the duration (in seconds) Helm will wait to establish a connection to tiller
func ConnectTimeout(timeout int64) Option {
return func(opts *options) {
opts.connectTimeout = time.Duration(timeout) * time.Second
}
}
// InstallTimeout specifies the number of seconds before kubernetes calls timeout // InstallTimeout specifies the number of seconds before kubernetes calls timeout
func InstallTimeout(timeout int64) InstallOption { func InstallTimeout(timeout int64) InstallOption {
return func(opts *options) { return func(opts *options) {
@ -365,37 +356,10 @@ func UpgradeForce(force bool) UpdateOption {
} }
} }
// ContentOption allows setting optional attributes when
// performing a GetReleaseContent tiller rpc.
type ContentOption func(*options)
// ContentReleaseVersion will instruct Tiller to retrieve the content
// of a particular version of a release.
func ContentReleaseVersion(version int32) ContentOption {
return func(opts *options) {
opts.contentReq.Version = version
}
}
// StatusOption allows setting optional attributes when
// performing a GetReleaseStatus tiller rpc.
type StatusOption func(*options)
// StatusReleaseVersion will instruct Tiller to retrieve the status
// of a particular version of a release.
func StatusReleaseVersion(version int32) StatusOption {
return func(opts *options) {
opts.statusReq.Version = version
}
}
// DeleteOption allows setting optional attributes when // DeleteOption allows setting optional attributes when
// performing a UninstallRelease tiller rpc. // performing a UninstallRelease tiller rpc.
type DeleteOption func(*options) type DeleteOption func(*options)
// VersionOption -- TODO
type VersionOption func(*options)
// UpdateOption allows specifying various settings // UpdateOption allows specifying various settings
// configurable by the helm client user for overriding // configurable by the helm client user for overriding
// the defaults used when running the `helm upgrade` command. // the defaults used when running the `helm upgrade` command.
@ -406,24 +370,18 @@ type UpdateOption func(*options)
// running the `helm rollback` command. // running the `helm rollback` command.
type RollbackOption func(*options) type RollbackOption func(*options)
// HistoryOption allows configuring optional request data for // ReleaseTestOption allows configuring optional request data for
// issuing a GetHistory rpc. // issuing a TestRelease rpc.
type HistoryOption func(*options) type ReleaseTestOption func(*options)
// WithMaxHistory sets the max number of releases to return func Driver(d driver.Driver) Option {
// in a release history query.
func WithMaxHistory(max int32) HistoryOption {
return func(opts *options) { return func(opts *options) {
opts.histReq.Max = max opts.driver = d
} }
} }
// NewContext creates a versioned context. func ClientSet(cs internalclientset.Interface) Option {
func NewContext() context.Context { return func(opts *options) {
md := metadata.Pairs("x-helm-api-client", version.GetVersion()) opts.clientset = cs
return metadata.NewOutgoingContext(context.TODO(), md) }
} }
// ReleaseTestOption allows configuring optional request data for
// issuing a TestRelease rpc.
type ReleaseTestOption func(*options)

@ -1,60 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package portforwarder
import (
"k8s.io/api/core/v1"
)
// These functions are adapted from the "kubernetes" repository's file
//
// kubernetes/pkg/api/v1/pod/util.go
//
// where they rely upon the API types specific to that repository. Here we recast them to operate
// upon the type from the "client-go" repository instead.
// isPodReady returns true if a pod is ready; false otherwise.
func isPodReady(pod *v1.Pod) bool {
return isPodReadyConditionTrue(pod.Status)
}
// isPodReady retruns true if a pod is ready; false otherwise.
func isPodReadyConditionTrue(status v1.PodStatus) bool {
condition := getPodReadyCondition(status)
return condition != nil && condition.Status == v1.ConditionTrue
}
// getPodReadyCondition extracts the pod ready condition from the given status and returns that.
// Returns nil if the condition is not present.
func getPodReadyCondition(status v1.PodStatus) *v1.PodCondition {
_, condition := getPodCondition(&status, v1.PodReady)
return condition
}
// getPodCondition extracts the provided condition from the given status and returns that.
// Returns nil and -1 if the condition is not present, and the index of the located condition.
func getPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
if status == nil {
return -1, nil
}
for i := range status.Conditions {
if status.Conditions[i].Type == conditionType {
return i, &status.Conditions[i]
}
}
return -1, nil
}

@ -1,72 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package portforwarder
import (
"fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/helm/pkg/kube"
)
var (
tillerPodLabels = labels.Set{"app": "helm", "name": "tiller"}
)
// New creates a new and initialized tunnel.
func New(namespace string, client kubernetes.Interface, config *rest.Config) (*kube.Tunnel, error) {
podName, err := GetTillerPodName(client.CoreV1(), namespace)
if err != nil {
return nil, err
}
const tillerPort = 44134
t := kube.NewTunnel(client.CoreV1().RESTClient(), config, namespace, podName, tillerPort)
return t, t.ForwardPort()
}
// GetTillerPodName fetches the name of tiller pod running in the given namespace.
func GetTillerPodName(client corev1.PodsGetter, namespace string) (string, error) {
selector := tillerPodLabels.AsSelector()
pod, err := getFirstRunningPod(client, namespace, selector)
if err != nil {
return "", err
}
return pod.ObjectMeta.GetName(), nil
}
func getFirstRunningPod(client corev1.PodsGetter, namespace string, selector labels.Selector) (*v1.Pod, error) {
options := metav1.ListOptions{LabelSelector: selector.String()}
pods, err := client.Pods(namespace).List(options)
if err != nil {
return nil, err
}
if len(pods.Items) < 1 {
return nil, fmt.Errorf("could not find tiller")
}
for _, p := range pods.Items {
if isPodReady(&p) {
return &p, nil
}
}
return nil, fmt.Errorf("could not find a ready tiller pod")
}

@ -1,87 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package portforwarder
import (
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func mockTillerPod() v1.Pod {
return v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "orca",
Namespace: v1.NamespaceDefault,
Labels: tillerPodLabels,
},
Status: v1.PodStatus{
Phase: v1.PodRunning,
Conditions: []v1.PodCondition{
{
Status: v1.ConditionTrue,
Type: v1.PodReady,
},
},
},
}
}
func mockTillerPodPending() v1.Pod {
p := mockTillerPod()
p.Name = "blue"
p.Status.Conditions[0].Status = v1.ConditionFalse
return p
}
func TestGetFirstPod(t *testing.T) {
tests := []struct {
name string
pods []v1.Pod
expected string
err bool
}{
{
name: "with a ready pod",
pods: []v1.Pod{mockTillerPod()},
expected: "orca",
},
{
name: "without a ready pod",
pods: []v1.Pod{mockTillerPodPending()},
err: true,
},
{
name: "without a pod",
pods: []v1.Pod{},
err: true,
},
}
for _, tt := range tests {
client := fake.NewSimpleClientset(&v1.PodList{Items: tt.pods})
name, err := GetTillerPodName(client.Core(), v1.NamespaceDefault)
if (err != nil) != tt.err {
t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err)
}
if name != tt.expected {
t.Errorf("%q. expected %q, got %q", tt.name, tt.expected, name)
}
}
}

@ -1,122 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kube
import (
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strconv"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
// Tunnel describes a ssh-like tunnel to a kubernetes pod
type Tunnel struct {
Local int
Remote int
Namespace string
PodName string
Out io.Writer
stopChan chan struct{}
readyChan chan struct{}
config *rest.Config
client rest.Interface
}
// NewTunnel creates a new tunnel
func NewTunnel(client rest.Interface, config *rest.Config, namespace, podName string, remote int) *Tunnel {
return &Tunnel{
config: config,
client: client,
Namespace: namespace,
PodName: podName,
Remote: remote,
stopChan: make(chan struct{}, 1),
readyChan: make(chan struct{}, 1),
Out: ioutil.Discard,
}
}
// Close disconnects a tunnel connection
func (t *Tunnel) Close() {
close(t.stopChan)
}
// ForwardPort opens a tunnel to a kubernetes pod
func (t *Tunnel) ForwardPort() error {
// Build a url to the portforward endpoint
// example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-deploy-9itlq/portforward
u := t.client.Post().
Resource("pods").
Namespace(t.Namespace).
Name(t.PodName).
SubResource("portforward").URL()
transport, upgrader, err := spdy.RoundTripperFor(t.config)
if err != nil {
return err
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", u)
local, err := getAvailablePort()
if err != nil {
return fmt.Errorf("could not find an available port: %s", err)
}
t.Local = local
ports := []string{fmt.Sprintf("%d:%d", t.Local, t.Remote)}
pf, err := portforward.New(dialer, ports, t.stopChan, t.readyChan, t.Out, t.Out)
if err != nil {
return err
}
errChan := make(chan error)
go func() {
errChan <- pf.ForwardPorts()
}()
select {
case err = <-errChan:
return fmt.Errorf("forwarding ports: %v", err)
case <-pf.Ready:
return nil
}
}
func getAvailablePort() (int, error) {
l, err := net.Listen("tcp", ":0")
if err != nil {
return 0, err
}
defer l.Close()
_, p, err := net.SplitHostPort(l.Addr().String())
if err != nil {
return 0, err
}
port, err := strconv.Atoi(p)
if err != nil {
return 0, err
}
return port, err
}

@ -1,31 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kube
import (
"testing"
)
func TestAvailablePort(t *testing.T) {
port, err := getAvailablePort()
if err != nil {
t.Fatal(err)
}
if port < 1 {
t.Fatalf("generated port should be > 1, got %d", port)
}
}

@ -21,14 +21,15 @@ import (
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"io" "io"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin/cache"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/plugin/cache"
) )
// HTTPInstaller installs plugins from an archive served by a web server. // HTTPInstaller installs plugins from an archive served by a web server.

@ -20,9 +20,10 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"k8s.io/helm/pkg/helm/helmpath"
"os" "os"
"testing" "testing"
"k8s.io/helm/pkg/helm/helmpath"
) )
var _ Installer = new(HTTPInstaller) var _ Installer = new(HTTPInstaller)

@ -129,11 +129,6 @@ func newMockTestingEnvironment() *MockTestingEnvironment {
} }
} }
func (mte MockTestingEnvironment) streamRunning(name string) error { return nil }
func (mte MockTestingEnvironment) streamError(info string) error { return nil }
func (mte MockTestingEnvironment) streamFailed(name string) error { return nil }
func (mte MockTestingEnvironment) streamSuccess(name string) error { return nil }
func (mte MockTestingEnvironment) streamUnknown(name, info string) error { return nil }
func (mte MockTestingEnvironment) streamMessage(msg string, status release.TestRun_Status) error { func (mte MockTestingEnvironment) streamMessage(msg string, status release.TestRun_Status) error {
mte.Stream.Send(&services.TestReleaseResponse{Msg: msg, Status: status}) mte.Stream.Send(&services.TestReleaseResponse{Msg: msg, Status: status})
return nil return nil

@ -46,18 +46,15 @@ type test struct {
// NewTestSuite takes a release object and returns a TestSuite object with test definitions // NewTestSuite takes a release object and returns a TestSuite object with test definitions
// extracted from the release // extracted from the release
func NewTestSuite(rel *release.Release) (*TestSuite, error) { func NewTestSuite(rel *release.Release) *TestSuite {
testManifests, err := extractTestManifestsFromHooks(rel.Hooks) testManifests := extractTestManifestsFromHooks(rel.Hooks)
if err != nil {
return nil, err
}
results := []*release.TestRun{} results := []*release.TestRun{}
return &TestSuite{ return &TestSuite{
TestManifests: testManifests, TestManifests: testManifests,
Results: results, Results: results,
}, nil }
} }
// Run executes tests in a test suite and stores a result within a given environment // Run executes tests in a test suite and stores a result within a given environment
@ -152,7 +149,7 @@ func expectedSuccess(hookTypes []string) (bool, error) {
return false, fmt.Errorf("No %s or %s hook found", hooks.ReleaseTestSuccess, hooks.ReleaseTestFailure) return false, fmt.Errorf("No %s or %s hook found", hooks.ReleaseTestSuccess, hooks.ReleaseTestFailure)
} }
func extractTestManifestsFromHooks(h []*release.Hook) ([]string, error) { func extractTestManifestsFromHooks(h []*release.Hook) []string {
testHooks := hooks.FilterTestHooks(h) testHooks := hooks.FilterTestHooks(h)
tests := []string{} tests := []string{}
@ -162,7 +159,7 @@ func extractTestManifestsFromHooks(h []*release.Hook) ([]string, error) {
tests = append(tests, t) tests = append(tests, t)
} }
} }
return tests, nil return tests
} }
func newTest(testManifest string) (*test, error) { func newTest(testManifest string) (*test, error) {

@ -24,7 +24,6 @@ import (
"github.com/golang/protobuf/ptypes/timestamp" "github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context" "golang.org/x/net/context"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core"
@ -73,15 +72,6 @@ data:
name: value name: value
` `
func TestNewTestSuite(t *testing.T) {
rel := releaseStub()
_, err := NewTestSuite(rel)
if err != nil {
t.Errorf("%s", err)
}
}
func TestRun(t *testing.T) { func TestRun(t *testing.T) {
testManifests := []string{manifestWithTestSuccessHook, manifestWithTestFailureHook} testManifests := []string{manifestWithTestSuccessHook, manifestWithTestFailureHook}
@ -209,10 +199,7 @@ func TestRunSuccessWithTestFailureHook(t *testing.T) {
func TestExtractTestManifestsFromHooks(t *testing.T) { func TestExtractTestManifestsFromHooks(t *testing.T) {
rel := releaseStub() rel := releaseStub()
testManifests, err := extractTestManifestsFromHooks(rel.Hooks) testManifests := extractTestManifestsFromHooks(rel.Hooks)
if err != nil {
t.Errorf("Expected no error, Got: %s", err)
}
if len(testManifests) != 1 { if len(testManifests) != 1 {
t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests)) t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests))
@ -297,7 +284,6 @@ func mockTillerEnvironment() *tillerEnv.Environment {
} }
type mockStream struct { type mockStream struct {
stream grpc.ServerStream
messages []*services.TestReleaseResponse messages []*services.TestReleaseResponse
} }

@ -17,23 +17,20 @@ limitations under the License.
package tiller package tiller
import ( import (
ctx "golang.org/x/net/context" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
) )
// GetReleaseContent gets all of the stored information for the given release. // GetReleaseContent gets all of the stored information for the given release.
func (s *ReleaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { func (s *ReleaseServer) GetReleaseContent(req *services.GetReleaseContentRequest) (*release.Release, error) {
if err := validateReleaseName(req.Name); err != nil { if err := validateReleaseName(req.Name); err != nil {
s.Log("releaseContent: Release name is invalid: %s", req.Name) s.Log("releaseContent: Release name is invalid: %s", req.Name)
return nil, err return nil, err
} }
if req.Version <= 0 { if req.Version <= 0 {
rel, err := s.env.Releases.Last(req.Name) return s.env.Releases.Last(req.Name)
return &services.GetReleaseContentResponse{Release: rel}, err
} }
rel, err := s.env.Releases.Get(req.Name, req.Version) return s.env.Releases.Get(req.Name, req.Version)
return &services.GetReleaseContentResponse{Release: rel}, err
} }

@ -17,26 +17,24 @@ limitations under the License.
package tiller package tiller
import ( import (
"context"
"testing" "testing"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
) )
func TestGetReleaseContent(t *testing.T) { func TestGetReleaseContent(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil { if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err) t.Fatalf("Could not store mock release: %s", err)
} }
res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1}) res, err := rs.GetReleaseContent(&services.GetReleaseContentRequest{Name: rel.Name, Version: 1})
if err != nil { if err != nil {
t.Errorf("Error getting release content: %s", err) t.Errorf("Error getting release content: %s", err)
} }
if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name { if res.Chart.Metadata.Name != rel.Chart.Metadata.Name {
t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name) t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Chart.Metadata.Name)
} }
} }

@ -17,14 +17,13 @@ limitations under the License.
package tiller package tiller
import ( import (
"golang.org/x/net/context" "k8s.io/helm/pkg/proto/hapi/release"
tpb "k8s.io/helm/pkg/proto/hapi/services" tpb "k8s.io/helm/pkg/proto/hapi/services"
relutil "k8s.io/helm/pkg/releaseutil" relutil "k8s.io/helm/pkg/releaseutil"
) )
// GetHistory gets the history for a given release. // GetHistory gets the history for a given release.
func (s *ReleaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryRequest) (*tpb.GetHistoryResponse, error) { func (s *ReleaseServer) GetHistory(req *tpb.GetHistoryRequest) ([]*release.Release, error) {
if err := validateReleaseName(req.Name); err != nil { if err := validateReleaseName(req.Name); err != nil {
s.Log("getHistory: Release name is invalid: %s", req.Name) s.Log("getHistory: Release name is invalid: %s", req.Name)
return nil, err return nil, err
@ -38,12 +37,12 @@ func (s *ReleaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryReque
relutil.Reverse(h, relutil.SortByRevision) relutil.Reverse(h, relutil.SortByRevision)
var resp tpb.GetHistoryResponse var rels []*release.Release
for i := 0; i < min(len(h), int(req.Max)); i++ { for i := 0; i < min(len(h), int(req.Max)); i++ {
resp.Releases = append(resp.Releases, h[i]) rels = append(rels, h[i])
} }
return &resp, nil return rels, nil
} }
func min(x, y int) int { func min(x, y int) int {

@ -20,8 +20,6 @@ import (
"reflect" "reflect"
"testing" "testing"
"golang.org/x/net/context"
rpb "k8s.io/helm/pkg/proto/hapi/release" rpb "k8s.io/helm/pkg/proto/hapi/release"
tpb "k8s.io/helm/pkg/proto/hapi/services" tpb "k8s.io/helm/pkg/proto/hapi/services"
) )
@ -39,25 +37,25 @@ func TestGetHistory_WithRevisions(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
req *tpb.GetHistoryRequest req *tpb.GetHistoryRequest
res *tpb.GetHistoryResponse res []*rpb.Release
}{ }{
{ {
desc: "get release with history and default limit (max=256)", desc: "get release with history and default limit (max=256)",
req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 256}, req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 256},
res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{ res: []*rpb.Release{
mk("angry-bird", 4, rpb.Status_DEPLOYED), mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED), mk("angry-bird", 3, rpb.Status_SUPERSEDED),
mk("angry-bird", 2, rpb.Status_SUPERSEDED), mk("angry-bird", 2, rpb.Status_SUPERSEDED),
mk("angry-bird", 1, rpb.Status_SUPERSEDED), mk("angry-bird", 1, rpb.Status_SUPERSEDED),
}}, },
}, },
{ {
desc: "get release with history using result limit (max=2)", desc: "get release with history using result limit (max=2)",
req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 2}, req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 2},
res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{ res: []*rpb.Release{
mk("angry-bird", 4, rpb.Status_DEPLOYED), mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED), mk("angry-bird", 3, rpb.Status_SUPERSEDED),
}}, },
}, },
} }
@ -78,7 +76,7 @@ func TestGetHistory_WithRevisions(t *testing.T) {
// run tests // run tests
for _, tt := range tests { for _, tt := range tests {
res, err := srv.GetHistory(context.TODO(), tt.req) res, err := srv.GetHistory(tt.req)
if err != nil { if err != nil {
t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err) t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err)
} }
@ -105,12 +103,12 @@ func TestGetHistory_WithNoRevisions(t *testing.T) {
srv.env.Releases.Create(rls) srv.env.Releases.Create(rls)
for _, tt := range tests { for _, tt := range tests {
res, err := srv.GetHistory(context.TODO(), tt.req) res, err := srv.GetHistory(tt.req)
if err != nil { if err != nil {
t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err) t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err)
} }
if len(res.Releases) > 1 { if len(res) > 1 {
t.Fatalf("%s:\nExpected zero items, got %d", tt.desc, len(res.Releases)) t.Fatalf("%s:\nExpected zero items, got %d", tt.desc, len(res))
} }
} }
} }

@ -20,8 +20,6 @@ import (
"fmt" "fmt"
"strings" "strings"
ctx "golang.org/x/net/context"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
@ -31,19 +29,18 @@ import (
) )
// InstallRelease installs a release and stores the release record. // InstallRelease installs a release and stores the release record.
func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { func (s *ReleaseServer) InstallRelease(req *services.InstallReleaseRequest) (*release.Release, error) {
s.Log("preparing install for %s", req.Name) s.Log("preparing install for %s", req.Name)
rel, err := s.prepareRelease(req) rel, err := s.prepareRelease(req)
if err != nil { if err != nil {
s.Log("failed install prepare step: %s", err) s.Log("failed install prepare step: %s", err)
res := &services.InstallReleaseResponse{Release: rel}
// On dry run, append the manifest contents to a failed release. This is // On dry run, append the manifest contents to a failed release. This is
// a stop-gap until we can revisit an error backchannel post-2.0. // a stop-gap until we can revisit an error backchannel post-2.0.
if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
err = fmt.Errorf("%s\n%s", err, rel.Manifest) err = fmt.Errorf("%s\n%s", err, rel.Manifest)
} }
return res, err return rel, err
} }
s.Log("performing install for %s", req.Name) s.Log("performing install for %s", req.Name)
@ -132,19 +129,18 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
} }
// performRelease runs a release. // performRelease runs a release.
func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*release.Release, error) {
res := &services.InstallReleaseResponse{Release: r}
if req.DryRun { if req.DryRun {
s.Log("dry run for %s", r.Name) s.Log("dry run for %s", r.Name)
res.Release.Info.Description = "Dry run complete" r.Info.Description = "Dry run complete"
return res, nil return r, nil
} }
// pre-install hooks // pre-install hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil {
return res, err return r, err
} }
} else { } else {
s.Log("install hooks disabled for %s", req.Name) s.Log("install hooks disabled for %s", req.Name)
@ -181,7 +177,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
r.Info.Description = msg r.Info.Description = msg
s.recordRelease(old, true) s.recordRelease(old, true)
s.recordRelease(r, true) s.recordRelease(r, true)
return res, err return r, err
} }
default: default:
@ -194,7 +190,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
r.Info.Status.Code = release.Status_FAILED r.Info.Status.Code = release.Status_FAILED
r.Info.Description = msg r.Info.Description = msg
s.recordRelease(r, true) s.recordRelease(r, true)
return res, fmt.Errorf("release %s failed: %s", r.Name, err) return r, fmt.Errorf("release %s failed: %s", r.Name, err)
} }
} }
@ -206,7 +202,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
r.Info.Status.Code = release.Status_FAILED r.Info.Status.Code = release.Status_FAILED
r.Info.Description = msg r.Info.Description = msg
s.recordRelease(r, true) s.recordRelease(r, true)
return res, err return r, err
} }
} }
@ -221,5 +217,5 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
// this stored in the future. // this stored in the future.
s.recordRelease(r, true) s.recordRelease(r, true)
return res, nil return r, nil
} }

@ -21,32 +21,29 @@ import (
"strings" "strings"
"testing" "testing"
"golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest() req := installRequest()
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if res.Release.Namespace != "spaced" { if res.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Namespace)
} }
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) rel, err := rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Errorf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
t.Logf("rel: %v", rel) t.Logf("rel: %v", rel)
@ -65,8 +62,8 @@ func TestInstallRelease(t *testing.T) {
t.Errorf("Expected event 0 is pre-delete") t.Errorf("Expected event 0 is pre-delete")
} }
if len(res.Release.Manifest) == 0 { if len(res.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res)
} }
if len(rel.Manifest) == 0 { if len(rel.Manifest) == 0 {
@ -83,26 +80,25 @@ func TestInstallRelease(t *testing.T) {
} }
func TestInstallRelease_WithNotes(t *testing.T) { func TestInstallRelease_WithNotes(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest( req := installRequest(
withChart(withNotes(notesText)), withChart(withNotes(notesText)),
) )
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if res.Release.Namespace != "spaced" { if res.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Namespace)
} }
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) rel, err := rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Errorf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
t.Logf("rel: %v", rel) t.Logf("rel: %v", rel)
@ -125,8 +121,8 @@ func TestInstallRelease_WithNotes(t *testing.T) {
t.Errorf("Expected event 0 is pre-delete") t.Errorf("Expected event 0 is pre-delete")
} }
if len(res.Release.Manifest) == 0 { if len(res.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res)
} }
if len(rel.Manifest) == 0 { if len(rel.Manifest) == 0 {
@ -143,26 +139,25 @@ func TestInstallRelease_WithNotes(t *testing.T) {
} }
func TestInstallRelease_WithNotesRendered(t *testing.T) { func TestInstallRelease_WithNotesRendered(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest( req := installRequest(
withChart(withNotes(notesText + " {{.Release.Name}}")), withChart(withNotes(notesText + " {{.Release.Name}}")),
) )
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if res.Release.Namespace != "spaced" { if res.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Namespace)
} }
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) rel, err := rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Errorf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
t.Logf("rel: %v", rel) t.Logf("rel: %v", rel)
@ -174,7 +169,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest)
} }
expectedNotes := fmt.Sprintf("%s %s", notesText, res.Release.Name) expectedNotes := fmt.Sprintf("%s %s", notesText, res.Name)
if rel.Info.Status.Notes != expectedNotes { if rel.Info.Status.Notes != expectedNotes {
t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes) t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes)
} }
@ -186,8 +181,8 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
t.Errorf("Expected event 0 is pre-delete") t.Errorf("Expected event 0 is pre-delete")
} }
if len(res.Release.Manifest) == 0 { if len(res.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res)
} }
if len(rel.Manifest) == 0 { if len(rel.Manifest) == 0 {
@ -205,13 +200,12 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
func TestInstallRelease_TillerVersion(t *testing.T) { func TestInstallRelease_TillerVersion(t *testing.T) {
version.Version = "2.2.0" version.Version = "2.2.0"
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest( req := installRequest(
withChart(withTiller(">=2.2.0")), withChart(withTiller(">=2.2.0")),
) )
_, err := rs.InstallRelease(c, req) _, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Expected valid range. Got %q", err) t.Fatalf("Expected valid range. Got %q", err)
} }
@ -219,13 +213,12 @@ func TestInstallRelease_TillerVersion(t *testing.T) {
func TestInstallRelease_WrongTillerVersion(t *testing.T) { func TestInstallRelease_WrongTillerVersion(t *testing.T) {
version.Version = "2.2.0" version.Version = "2.2.0"
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest( req := installRequest(
withChart(withTiller("<2.0.0")), withChart(withTiller("<2.0.0")),
) )
_, err := rs.InstallRelease(c, req) _, err := rs.InstallRelease(req)
if err == nil { if err == nil {
t.Fatalf("Expected to fail because of wrong version") t.Fatalf("Expected to fail because of wrong version")
} }
@ -237,24 +230,23 @@ func TestInstallRelease_WrongTillerVersion(t *testing.T) {
} }
func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest(withChart( req := installRequest(withChart(
withNotes(notesText), withNotes(notesText),
withDependency(withNotes(notesText+" child")), withDependency(withNotes(notesText+" child")),
)) ))
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) rel, err := rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Errorf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
t.Logf("rel: %v", rel) t.Logf("rel: %v", rel)
@ -269,92 +261,88 @@ func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
} }
func TestInstallRelease_DryRun(t *testing.T) { func TestInstallRelease_DryRun(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest(withDryRun(), req := installRequest(withDryRun(),
withChart(withSampleTemplates()), withChart(withSampleTemplates()),
) )
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Errorf("Failed install: %s", err) t.Errorf("Failed install: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest) t.Errorf("unexpected output: %s", res.Manifest)
} }
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") { if !strings.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest) t.Errorf("unexpected output: %s", res.Manifest)
} }
if !strings.Contains(res.Release.Manifest, "hello: Earth") { if !strings.Contains(res.Manifest, "hello: Earth") {
t.Errorf("Should contain partial content. %s", res.Release.Manifest) t.Errorf("Should contain partial content. %s", res.Manifest)
} }
if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") { if strings.Contains(res.Manifest, "hello: {{ template \"_planet\" . }}") {
t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest) t.Errorf("Should not contain partial templates itself. %s", res.Manifest)
} }
if strings.Contains(res.Release.Manifest, "empty") { if strings.Contains(res.Manifest, "empty") {
t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) t.Errorf("Should not contain template data for an empty file. %s", res.Manifest)
} }
if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil { if _, err := rs.env.Releases.Get(res.Name, res.Version); err == nil {
t.Errorf("Expected no stored release.") t.Errorf("Expected no stored release.")
} }
if l := len(res.Release.Hooks); l != 1 { if l := len(res.Hooks); l != 1 {
t.Fatalf("Expected 1 hook, got %d", l) t.Fatalf("Expected 1 hook, got %d", l)
} }
if res.Release.Hooks[0].LastRun != nil { if res.Hooks[0].LastRun != nil {
t.Error("Expected hook to not be marked as run.") t.Error("Expected hook to not be marked as run.")
} }
if res.Release.Info.Description != "Dry run complete" { if res.Info.Description != "Dry run complete" {
t.Errorf("unexpected description: %s", res.Release.Info.Description) t.Errorf("unexpected description: %s", res.Info.Description)
} }
} }
func TestInstallRelease_NoHooks(t *testing.T) { func TestInstallRelease_NoHooks(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
req := installRequest(withDisabledHooks()) req := installRequest(withDisabledHooks())
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Errorf("Failed install: %s", err) t.Errorf("Failed install: %s", err)
} }
if hl := res.Release.Hooks[0].LastRun; hl != nil { if hl := res.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl) t.Errorf("Expected that no hooks were run. Got %d", hl)
} }
} }
func TestInstallRelease_FailedHooks(t *testing.T) { func TestInstallRelease_FailedHooks(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
rs.env.KubeClient = newHookFailingKubeClient() rs.env.KubeClient = newHookFailingKubeClient()
req := installRequest() req := installRequest()
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err == nil { if err == nil {
t.Error("Expected failed install") t.Error("Expected failed install")
} }
if hl := res.Release.Info.Status.Code; hl != release.Status_FAILED { if hl := res.Info.Status.Code; hl != release.Status_FAILED {
t.Errorf("Expected FAILED release. Got %d", hl) t.Errorf("Expected FAILED release. Got %d", hl)
} }
} }
func TestInstallRelease_ReuseName(t *testing.T) { func TestInstallRelease_ReuseName(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rel.Info.Status.Code = release.Status_DELETED rel.Info.Status.Code = release.Status_DELETED
@ -364,17 +352,17 @@ func TestInstallRelease_ReuseName(t *testing.T) {
withReuseName(), withReuseName(),
withName(rel.Name), withName(rel.Name),
) )
res, err := rs.InstallRelease(c, req) res, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
if res.Release.Name != rel.Name { if res.Name != rel.Name {
t.Errorf("expected %q, got %q", rel.Name, res.Release.Name) t.Errorf("expected %q, got %q", rel.Name, res.Name)
} }
getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 0} getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 0}
getres, err := rs.GetReleaseStatus(c, getreq) getres, err := rs.GetReleaseStatus(getreq)
if err != nil { if err != nil {
t.Errorf("Failed to retrieve release: %s", err) t.Errorf("Failed to retrieve release: %s", err)
} }
@ -384,27 +372,25 @@ func TestInstallRelease_ReuseName(t *testing.T) {
} }
func TestInstallRelease_KubeVersion(t *testing.T) { func TestInstallRelease_KubeVersion(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest( req := installRequest(
withChart(withKube(">=0.0.0")), withChart(withKube(">=0.0.0")),
) )
_, err := rs.InstallRelease(c, req) _, err := rs.InstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Expected valid range. Got %q", err) t.Fatalf("Expected valid range. Got %q", err)
} }
} }
func TestInstallRelease_WrongKubeVersion(t *testing.T) { func TestInstallRelease_WrongKubeVersion(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
req := installRequest( req := installRequest(
withChart(withKube(">=5.0.0")), withChart(withKube(">=5.0.0")),
) )
_, err := rs.InstallRelease(c, req) _, err := rs.InstallRelease(req)
if err == nil { if err == nil {
t.Fatalf("Expected to fail because of wrong version") t.Fatalf("Expected to fail because of wrong version")
} }

@ -19,8 +19,6 @@ package tiller
import ( import (
"fmt" "fmt"
ctx "golang.org/x/net/context"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
@ -28,7 +26,7 @@ import (
) )
// RollbackRelease rolls back to a previous version of the given release. // RollbackRelease rolls back to a previous version of the given release.
func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { func (s *ReleaseServer) RollbackRelease(req *services.RollbackReleaseRequest) (*release.Release, error) {
s.Log("preparing rollback of %s", req.Name) s.Log("preparing rollback of %s", req.Name)
currentRelease, targetRelease, err := s.prepareRollback(req) currentRelease, targetRelease, err := s.prepareRollback(req)
if err != nil { if err != nil {
@ -111,18 +109,17 @@ func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*
return currentRelease, targetRelease, nil return currentRelease, targetRelease, nil
} }
func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*release.Release, error) {
res := &services.RollbackReleaseResponse{Release: targetRelease}
if req.DryRun { if req.DryRun {
s.Log("dry run for %s", targetRelease.Name) s.Log("dry run for %s", targetRelease.Name)
return res, nil return targetRelease, nil
} }
// pre-rollback hooks // pre-rollback hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil { if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil {
return res, err return targetRelease, err
} }
} else { } else {
s.Log("rollback hooks disabled for %s", req.Name) s.Log("rollback hooks disabled for %s", req.Name)
@ -136,13 +133,13 @@ func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.R
targetRelease.Info.Description = msg targetRelease.Info.Description = msg
s.recordRelease(currentRelease, true) s.recordRelease(currentRelease, true)
s.recordRelease(targetRelease, true) s.recordRelease(targetRelease, true)
return res, err return targetRelease, err
} }
// post-rollback hooks // post-rollback hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil { if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil {
return res, err return targetRelease, err
} }
} }
@ -159,5 +156,5 @@ func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.R
targetRelease.Info.Status.Code = release.Status_DEPLOYED targetRelease.Info.Status.Code = release.Status_DEPLOYED
return res, nil return targetRelease, nil
} }

@ -20,14 +20,11 @@ import (
"strings" "strings"
"testing" "testing"
"golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
) )
func TestRollbackRelease(t *testing.T) { func TestRollbackRelease(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -52,30 +49,30 @@ func TestRollbackRelease(t *testing.T) {
req := &services.RollbackReleaseRequest{ req := &services.RollbackReleaseRequest{
Name: rel.Name, Name: rel.Name,
} }
res, err := rs.RollbackRelease(c, req) res, err := rs.RollbackRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed rollback: %s", err) t.Fatalf("Failed rollback: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if res.Release.Name != rel.Name { if res.Name != rel.Name {
t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Name)
} }
if res.Release.Namespace != rel.Namespace { if res.Namespace != rel.Namespace {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Namespace)
} }
if res.Release.Version != 3 { if res.Version != 3 {
t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) t.Errorf("Expected release version to be %v, got %v", 3, res.Version)
} }
updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) updated, err := rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Errorf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
if len(updated.Hooks) != 2 { if len(updated.Hooks) != 2 {
@ -90,14 +87,14 @@ func TestRollbackRelease(t *testing.T) {
rs.env.Releases.Update(upgradedRel) rs.env.Releases.Update(upgradedRel)
rs.env.Releases.Create(anotherUpgradedRelease) rs.env.Releases.Create(anotherUpgradedRelease)
res, err = rs.RollbackRelease(c, req) res, err = rs.RollbackRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed rollback: %s", err) t.Fatalf("Failed rollback: %s", err)
} }
updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version) updated, err = rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Errorf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
if len(updated.Hooks) != 1 { if len(updated.Hooks) != 1 {
@ -108,8 +105,8 @@ func TestRollbackRelease(t *testing.T) {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
} }
if res.Release.Version != 4 { if res.Version != 4 {
t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) t.Errorf("Expected release version to be %v, got %v", 3, res.Version)
} }
if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK { if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK {
@ -120,8 +117,8 @@ func TestRollbackRelease(t *testing.T) {
t.Errorf("Expected event 1 to be post rollback") t.Errorf("Expected event 1 to be post rollback")
} }
if len(res.Release.Manifest) == 0 { if len(res.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res)
} }
if len(updated.Manifest) == 0 { if len(updated.Manifest) == 0 {
@ -132,13 +129,12 @@ func TestRollbackRelease(t *testing.T) {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Manifest)
} }
if res.Release.Info.Description != "Rollback to 2" { if res.Info.Description != "Rollback to 2" {
t.Errorf("Expected rollback to 2, got %q", res.Release.Info.Description) t.Errorf("Expected rollback to 2, got %q", res.Info.Description)
} }
} }
func TestRollbackWithReleaseVersion(t *testing.T) { func TestRollbackWithReleaseVersion(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rs.Log = t.Logf rs.Log = t.Logf
rs.env.Releases.Log = t.Logf rs.env.Releases.Log = t.Logf
@ -163,7 +159,7 @@ func TestRollbackWithReleaseVersion(t *testing.T) {
Version: 1, Version: 1,
} }
_, err := rs.RollbackRelease(c, req) _, err := rs.RollbackRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed rollback: %s", err) t.Fatalf("Failed rollback: %s", err)
} }
@ -186,7 +182,6 @@ func TestRollbackWithReleaseVersion(t *testing.T) {
} }
func TestRollbackReleaseNoHooks(t *testing.T) { func TestRollbackReleaseNoHooks(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rel.Hooks = []*release.Hook{ rel.Hooks = []*release.Hook{
@ -211,18 +206,17 @@ func TestRollbackReleaseNoHooks(t *testing.T) {
DisableHooks: true, DisableHooks: true,
} }
res, err := rs.RollbackRelease(c, req) res, err := rs.RollbackRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed rollback: %s", err) t.Fatalf("Failed rollback: %s", err)
} }
if hl := res.Release.Hooks[0].LastRun; hl != nil { if hl := res.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl) t.Errorf("Expected that no hooks were run. Got %d", hl)
} }
} }
func TestRollbackReleaseFailure(t *testing.T) { func TestRollbackReleaseFailure(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -236,12 +230,12 @@ func TestRollbackReleaseFailure(t *testing.T) {
} }
rs.env.KubeClient = newUpdateFailingKubeClient() rs.env.KubeClient = newUpdateFailingKubeClient()
res, err := rs.RollbackRelease(c, req) res, err := rs.RollbackRelease(req)
if err == nil { if err == nil {
t.Error("Expected failed rollback") t.Error("Expected failed rollback")
} }
if targetStatus := res.Release.Info.Status.Code; targetStatus != release.Status_FAILED { if targetStatus := res.Info.Status.Code; targetStatus != release.Status_FAILED {
t.Errorf("Expected FAILED release. Got %v", targetStatus) t.Errorf("Expected FAILED release. Got %v", targetStatus)
} }

@ -38,6 +38,7 @@ import (
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
relutil "k8s.io/helm/pkg/releaseutil" relutil "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/tiller/environment" "k8s.io/helm/pkg/tiller/environment"
"k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/timeconv"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
@ -97,6 +98,10 @@ func NewReleaseServer(env *environment.Environment, clientset internalclientset.
} }
} }
func (s *ReleaseServer) Storage() *storage.Storage {
return s.env.Releases
}
// reuseValues copies values from the current release to a new release if the // reuseValues copies values from the current release to a new release if the
// new release does not have any values. // new release does not have any values.
// //

@ -428,15 +428,13 @@ func (h *hookFailingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout
type mockRunReleaseTestServer struct{} type mockRunReleaseTestServer struct{}
func (rs mockRunReleaseTestServer) Send(m *services.TestReleaseResponse) error { func (rs mockRunReleaseTestServer) Send(m *services.TestReleaseResponse) error { return nil }
return nil func (rs mockRunReleaseTestServer) SetHeader(m metadata.MD) error { return nil }
} func (rs mockRunReleaseTestServer) SendHeader(m metadata.MD) error { return nil }
func (rs mockRunReleaseTestServer) SetHeader(m metadata.MD) error { return nil } func (rs mockRunReleaseTestServer) SetTrailer(m metadata.MD) {}
func (rs mockRunReleaseTestServer) SendHeader(m metadata.MD) error { return nil } func (rs mockRunReleaseTestServer) SendMsg(v interface{}) error { return nil }
func (rs mockRunReleaseTestServer) SetTrailer(m metadata.MD) {} func (rs mockRunReleaseTestServer) RecvMsg(v interface{}) error { return nil }
func (rs mockRunReleaseTestServer) SendMsg(v interface{}) error { return nil } func (rs mockRunReleaseTestServer) Context() context.Context { return context.TODO() }
func (rs mockRunReleaseTestServer) RecvMsg(v interface{}) error { return nil }
func (rs mockRunReleaseTestServer) Context() context.Context { return context.TODO() }
type mockHooksManifest struct { type mockHooksManifest struct {
Metadata struct { Metadata struct {

@ -20,14 +20,12 @@ import (
"errors" "errors"
"fmt" "fmt"
ctx "golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
) )
// GetReleaseStatus gets the status information for a named release. // GetReleaseStatus gets the status information for a named release.
func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { func (s *ReleaseServer) GetReleaseStatus(req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) {
if err := validateReleaseName(req.Name); err != nil { if err := validateReleaseName(req.Name); err != nil {
s.Log("getStatus: Release name is invalid: %s", req.Name) s.Log("getStatus: Release name is invalid: %s", req.Name)
return nil, err return nil, err

@ -19,21 +19,18 @@ package tiller
import ( import (
"testing" "testing"
"golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
) )
func TestGetReleaseStatus(t *testing.T) { func TestGetReleaseStatus(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil { if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err) t.Fatalf("Could not store mock release: %s", err)
} }
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) res, err := rs.GetReleaseStatus(&services.GetReleaseStatusRequest{Name: rel.Name, Version: 1})
if err != nil { if err != nil {
t.Errorf("Error getting release content: %s", err) t.Errorf("Error getting release content: %s", err)
} }
@ -47,7 +44,6 @@ func TestGetReleaseStatus(t *testing.T) {
} }
func TestGetReleaseStatusDeleted(t *testing.T) { func TestGetReleaseStatusDeleted(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rel.Info.Status.Code = release.Status_DELETED rel.Info.Status.Code = release.Status_DELETED
@ -55,7 +51,7 @@ func TestGetReleaseStatusDeleted(t *testing.T) {
t.Fatalf("Could not store mock release: %s", err) t.Fatalf("Could not store mock release: %s", err)
} }
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) res, err := rs.GetReleaseStatus(&services.GetReleaseStatusRequest{Name: rel.Name, Version: 1})
if err != nil { if err != nil {
t.Fatalf("Error getting release content: %s", err) t.Fatalf("Error getting release content: %s", err)
} }

@ -43,11 +43,7 @@ func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream
Stream: stream, Stream: stream,
} }
s.Log("running tests for release %s", rel.Name) s.Log("running tests for release %s", rel.Name)
tSuite, err := reltesting.NewTestSuite(rel) tSuite := reltesting.NewTestSuite(rel)
if err != nil {
s.Log("error creating test suite for %s: %s", rel.Name, err)
return err
}
if err := tSuite.Run(testEnv); err != nil { if err := tSuite.Run(testEnv); err != nil {
s.Log("error running test suite for %s: %s", rel.Name, err) s.Log("error running test suite for %s: %s", rel.Name, err)

@ -20,8 +20,6 @@ import (
"fmt" "fmt"
"strings" "strings"
ctx "golang.org/x/net/context"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
@ -30,7 +28,7 @@ import (
) )
// UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED. // UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED.
func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { func (s *ReleaseServer) UninstallRelease(req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
if err := validateReleaseName(req.Name); err != nil { if err := validateReleaseName(req.Name); err != nil {
s.Log("uninstallRelease: Release name is invalid: %s", req.Name) s.Log("uninstallRelease: Release name is invalid: %s", req.Name)
return nil, err return nil, err

@ -20,14 +20,11 @@ import (
"strings" "strings"
"testing" "testing"
"golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
) )
func TestUninstallRelease(t *testing.T) { func TestUninstallRelease(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -35,7 +32,7 @@ func TestUninstallRelease(t *testing.T) {
Name: "angry-panda", Name: "angry-panda",
} }
res, err := rs.UninstallRelease(c, req) res, err := rs.UninstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed uninstall: %s", err) t.Fatalf("Failed uninstall: %s", err)
} }
@ -62,7 +59,6 @@ func TestUninstallRelease(t *testing.T) {
} }
func TestUninstallPurgeRelease(t *testing.T) { func TestUninstallPurgeRelease(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -75,7 +71,7 @@ func TestUninstallPurgeRelease(t *testing.T) {
Purge: true, Purge: true,
} }
res, err := rs.UninstallRelease(c, req) res, err := rs.UninstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed uninstall: %s", err) t.Fatalf("Failed uninstall: %s", err)
} }
@ -91,17 +87,16 @@ func TestUninstallPurgeRelease(t *testing.T) {
if res.Release.Info.Deleted.Seconds <= 0 { if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
} }
rels, err := rs.GetHistory(context.TODO(), &services.GetHistoryRequest{Name: "angry-panda"}) rels, err := rs.GetHistory(&services.GetHistoryRequest{Name: "angry-panda"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(rels.Releases) != 0 { if len(rels) != 0 {
t.Errorf("Expected no releases in storage, got %d", len(rels.Releases)) t.Errorf("Expected no releases in storage, got %d", len(rels))
} }
} }
func TestUninstallPurgeDeleteRelease(t *testing.T) { func TestUninstallPurgeDeleteRelease(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -109,7 +104,7 @@ func TestUninstallPurgeDeleteRelease(t *testing.T) {
Name: "angry-panda", Name: "angry-panda",
} }
_, err := rs.UninstallRelease(c, req) _, err := rs.UninstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed uninstall: %s", err) t.Fatalf("Failed uninstall: %s", err)
} }
@ -119,14 +114,13 @@ func TestUninstallPurgeDeleteRelease(t *testing.T) {
Purge: true, Purge: true,
} }
_, err2 := rs.UninstallRelease(c, req2) _, err2 := rs.UninstallRelease(req2)
if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" { if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" {
t.Errorf("Failed uninstall: %s", err2) t.Errorf("Failed uninstall: %s", err2)
} }
} }
func TestUninstallReleaseWithKeepPolicy(t *testing.T) { func TestUninstallReleaseWithKeepPolicy(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
name := "angry-bunny" name := "angry-bunny"
rs.env.Releases.Create(releaseWithKeepStub(name)) rs.env.Releases.Create(releaseWithKeepStub(name))
@ -135,7 +129,7 @@ func TestUninstallReleaseWithKeepPolicy(t *testing.T) {
Name: name, Name: name,
} }
res, err := rs.UninstallRelease(c, req) res, err := rs.UninstallRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed uninstall: %s", err) t.Fatalf("Failed uninstall: %s", err)
} }
@ -158,7 +152,6 @@ func TestUninstallReleaseWithKeepPolicy(t *testing.T) {
} }
func TestUninstallReleaseNoHooks(t *testing.T) { func TestUninstallReleaseNoHooks(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rs.env.Releases.Create(releaseStub()) rs.env.Releases.Create(releaseStub())
@ -167,7 +160,7 @@ func TestUninstallReleaseNoHooks(t *testing.T) {
DisableHooks: true, DisableHooks: true,
} }
res, err := rs.UninstallRelease(c, req) res, err := rs.UninstallRelease(req)
if err != nil { if err != nil {
t.Errorf("Failed uninstall: %s", err) t.Errorf("Failed uninstall: %s", err)
} }

@ -20,8 +20,6 @@ import (
"fmt" "fmt"
"strings" "strings"
ctx "golang.org/x/net/context"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
@ -30,7 +28,7 @@ import (
) )
// UpdateRelease takes an existing release and new information, and upgrades the release. // UpdateRelease takes an existing release and new information, and upgrades the release.
func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *ReleaseServer) UpdateRelease(req *services.UpdateReleaseRequest) (*release.Release, error) {
if err := validateReleaseName(req.Name); err != nil { if err := validateReleaseName(req.Name); err != nil {
s.Log("updateRelease: Release name is invalid: %s", req.Name) s.Log("updateRelease: Release name is invalid: %s", req.Name)
return nil, err return nil, err
@ -143,7 +141,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
} }
// performUpdateForce performs the same action as a `helm delete && helm install --replace`. // performUpdateForce performs the same action as a `helm delete && helm install --replace`.
func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (*release.Release, error) {
// find the last release with the given name // find the last release with the given name
oldRelease, err := s.env.Releases.Last(req.Name) oldRelease, err := s.env.Releases.Last(req.Name)
if err != nil { if err != nil {
@ -161,7 +159,6 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
Timeout: req.Timeout, Timeout: req.Timeout,
Wait: req.Wait, Wait: req.Wait,
}) })
res := &services.UpdateReleaseResponse{Release: newRelease}
if err != nil { if err != nil {
s.Log("failed update prepare step: %s", err) s.Log("failed update prepare step: %s", err)
// On dry run, append the manifest contents to a failed release. This is // On dry run, append the manifest contents to a failed release. This is
@ -169,7 +166,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
err = fmt.Errorf("%s\n%s", err, newRelease.Manifest) err = fmt.Errorf("%s\n%s", err, newRelease.Manifest)
} }
return res, err return newRelease, err
} }
// From here on out, the release is considered to be in Status_DELETING or Status_DELETED // From here on out, the release is considered to be in Status_DELETING or Status_DELETED
@ -182,7 +179,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
// pre-delete hooks // pre-delete hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil { if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil {
return res, err return newRelease, err
} }
} else { } else {
s.Log("hooks disabled for %s", req.Name) s.Log("hooks disabled for %s", req.Name)
@ -201,20 +198,20 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
s.Log("error: %v", e) s.Log("error: %v", e)
es = append(es, e.Error()) es = append(es, e.Error())
} }
return res, fmt.Errorf("Upgrade --force successfully deleted the previous release, but encountered %d error(s) and cannot continue: %s", len(es), strings.Join(es, "; ")) return newRelease, fmt.Errorf("Upgrade --force successfully deleted the previous release, but encountered %d error(s) and cannot continue: %s", len(es), strings.Join(es, "; "))
} }
// post-delete hooks // post-delete hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil { if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil {
return res, err return newRelease, err
} }
} }
// pre-install hooks // pre-install hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil { if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil {
return res, err return newRelease, err
} }
} }
@ -227,7 +224,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
newRelease.Info.Status.Code = release.Status_FAILED newRelease.Info.Status.Code = release.Status_FAILED
newRelease.Info.Description = msg newRelease.Info.Description = msg
s.recordRelease(newRelease, true) s.recordRelease(newRelease, true)
return res, err return newRelease, err
} }
// post-install hooks // post-install hooks
@ -238,7 +235,7 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
newRelease.Info.Status.Code = release.Status_FAILED newRelease.Info.Status.Code = release.Status_FAILED
newRelease.Info.Description = msg newRelease.Info.Description = msg
s.recordRelease(newRelease, true) s.recordRelease(newRelease, true)
return res, err return newRelease, err
} }
} }
@ -246,22 +243,21 @@ func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (
newRelease.Info.Description = "Upgrade complete" newRelease.Info.Description = "Upgrade complete"
s.recordRelease(newRelease, true) s.recordRelease(newRelease, true)
return res, nil return newRelease, nil
} }
func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*release.Release, error) {
res := &services.UpdateReleaseResponse{Release: updatedRelease}
if req.DryRun { if req.DryRun {
s.Log("dry run for %s", updatedRelease.Name) s.Log("dry run for %s", updatedRelease.Name)
res.Release.Info.Description = "Dry run complete" updatedRelease.Info.Description = "Dry run complete"
return res, nil return updatedRelease, nil
} }
// pre-upgrade hooks // pre-upgrade hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil {
return res, err return updatedRelease, err
} }
} else { } else {
s.Log("update hooks disabled for %s", req.Name) s.Log("update hooks disabled for %s", req.Name)
@ -273,13 +269,13 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
updatedRelease.Info.Description = msg updatedRelease.Info.Description = msg
s.recordRelease(originalRelease, true) s.recordRelease(originalRelease, true)
s.recordRelease(updatedRelease, true) s.recordRelease(updatedRelease, true)
return res, err return updatedRelease, err
} }
// post-upgrade hooks // post-upgrade hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil {
return res, err return updatedRelease, err
} }
} }
@ -289,5 +285,5 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
updatedRelease.Info.Status.Code = release.Status_DEPLOYED updatedRelease.Info.Status.Code = release.Status_DEPLOYED
updatedRelease.Info.Description = "Upgrade complete" updatedRelease.Info.Description = "Upgrade complete"
return res, nil return updatedRelease, nil
} }

@ -22,7 +22,6 @@ import (
"testing" "testing"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
@ -30,7 +29,6 @@ import (
) )
func TestUpdateRelease(t *testing.T) { func TestUpdateRelease(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -45,24 +43,24 @@ func TestUpdateRelease(t *testing.T) {
}, },
}, },
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
if res.Release.Name == "" { if res.Name == "" {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if res.Release.Name != rel.Name { if res.Name != rel.Name {
t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Name)
} }
if res.Release.Namespace != rel.Namespace { if res.Namespace != rel.Namespace {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Namespace)
} }
updated := compareStoredAndReturnedRelease(t, *rs, *res) updated := compareStoredAndReturnedRelease(t, *rs, res)
if len(updated.Hooks) != 1 { if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
@ -83,27 +81,26 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected manifest in %v", res) t.Errorf("Expected manifest in %v", res)
} }
if res.Release.Config == nil { if res.Config == nil {
t.Errorf("Got release without config: %#v", res.Release) t.Errorf("Got release without config: %#v", res)
} else if res.Release.Config.Raw != rel.Config.Raw { } else if res.Config.Raw != rel.Config.Raw {
t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Config.Raw)
} }
if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", updated.Manifest) t.Errorf("unexpected output: %s", updated.Manifest)
} }
if res.Release.Version != 2 { if res.Version != 2 {
t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version) t.Errorf("Expected release version to be %v, got %v", 2, res.Version)
} }
edesc := "Upgrade complete" edesc := "Upgrade complete"
if got := res.Release.Info.Description; got != edesc { if got := res.Info.Description; got != edesc {
t.Errorf("Expected description %q, got %q", edesc, got) t.Errorf("Expected description %q, got %q", edesc, got)
} }
} }
func TestUpdateRelease_ResetValues(t *testing.T) { func TestUpdateRelease_ResetValues(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -119,19 +116,18 @@ func TestUpdateRelease_ResetValues(t *testing.T) {
}, },
ResetValues: true, ResetValues: true,
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
// This should have been unset. Config: &chart.Config{Raw: `name: value`}, // This should have been unset. Config: &chart.Config{Raw: `name: value`},
if res.Release.Config != nil && res.Release.Config.Raw != "" { if res.Config != nil && res.Config.Raw != "" {
t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) t.Errorf("Expected chart config to be empty, got %q", res.Config.Raw)
} }
} }
// This is a regression test for bug found in issue #3655 // This is a regression test for bug found in issue #3655
func TestUpdateRelease_ComplexReuseValues(t *testing.T) { func TestUpdateRelease_ComplexReuseValues(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
installReq := &services.InstallReleaseRequest{ installReq := &services.InstallReleaseRequest{
@ -148,12 +144,12 @@ func TestUpdateRelease_ComplexReuseValues(t *testing.T) {
} }
fmt.Println("Running Install release with foo: bar override") fmt.Println("Running Install release with foo: bar override")
installResp, err := rs.InstallRelease(c, installReq) installResp, err := rs.InstallRelease(installReq)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
rel := installResp.Release rel := installResp
req := &services.UpdateReleaseRequest{ req := &services.UpdateReleaseRequest{
Name: rel.Name, Name: rel.Name,
Chart: &chart.Chart{ Chart: &chart.Chart{
@ -167,17 +163,17 @@ func TestUpdateRelease_ComplexReuseValues(t *testing.T) {
} }
fmt.Println("Running Update release with no overrides and no reuse-values flag") fmt.Println("Running Update release with no overrides and no reuse-values flag")
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
expect := "foo: bar" expect := "foo: bar"
if res.Release.Config != nil && res.Release.Config.Raw != expect { if res.Config != nil && res.Config.Raw != expect {
t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Config.Raw) t.Errorf("Expected chart values to be %q, got %q", expect, res.Config.Raw)
} }
rel = res.Release rel = res
req = &services.UpdateReleaseRequest{ req = &services.UpdateReleaseRequest{
Name: rel.Name, Name: rel.Name,
Chart: &chart.Chart{ Chart: &chart.Chart{
@ -193,18 +189,17 @@ func TestUpdateRelease_ComplexReuseValues(t *testing.T) {
} }
fmt.Println("Running Update release with foo2: bar2 override and reuse-values") fmt.Println("Running Update release with foo2: bar2 override and reuse-values")
res, err = rs.UpdateRelease(c, req) rel, err = rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
// This should have the newly-passed overrides. // This should have the newly-passed overrides.
expect = "foo: bar\nfoo2: bar2\n" expect = "foo: bar\nfoo2: bar2\n"
if res.Release.Config != nil && res.Release.Config.Raw != expect { if rel.Config != nil && rel.Config.Raw != expect {
t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw) t.Errorf("Expected request config to be %q, got %q", expect, rel.Config.Raw)
} }
rel = res.Release
req = &services.UpdateReleaseRequest{ req = &services.UpdateReleaseRequest{
Name: rel.Name, Name: rel.Name,
Chart: &chart.Chart{ Chart: &chart.Chart{
@ -220,18 +215,17 @@ func TestUpdateRelease_ComplexReuseValues(t *testing.T) {
} }
fmt.Println("Running Update release with foo=baz override with reuse-values flag") fmt.Println("Running Update release with foo=baz override with reuse-values flag")
res, err = rs.UpdateRelease(c, req) res, err = rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
expect = "foo: baz\nfoo2: bar2\n" expect = "foo: baz\nfoo2: bar2\n"
if res.Release.Config != nil && res.Release.Config.Raw != expect { if res.Config != nil && res.Config.Raw != expect {
t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Config.Raw) t.Errorf("Expected chart values to be %q, got %q", expect, res.Config.Raw)
} }
} }
func TestUpdateRelease_ReuseValues(t *testing.T) { func TestUpdateRelease_ReuseValues(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -250,26 +244,25 @@ func TestUpdateRelease_ReuseValues(t *testing.T) {
Values: &chart.Config{Raw: "name2: val2"}, Values: &chart.Config{Raw: "name2: val2"},
ReuseValues: true, ReuseValues: true,
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
// This should have been overwritten with the old value. // This should have been overwritten with the old value.
expect := "name: value\n" expect := "name: value\n"
if res.Release.Chart.Values != nil && res.Release.Chart.Values.Raw != expect { if res.Chart.Values != nil && res.Chart.Values.Raw != expect {
t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Chart.Values.Raw) t.Errorf("Expected chart values to be %q, got %q", expect, res.Chart.Values.Raw)
} }
// This should have the newly-passed overrides and any other computed values. `name: value` comes from release Config via releaseStub() // This should have the newly-passed overrides and any other computed values. `name: value` comes from release Config via releaseStub()
expect = "name: value\nname2: val2\n" expect = "name: value\nname2: val2\n"
if res.Release.Config != nil && res.Release.Config.Raw != expect { if res.Config != nil && res.Config.Raw != expect {
t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw) t.Errorf("Expected request config to be %q, got %q", expect, res.Config.Raw)
} }
compareStoredAndReturnedRelease(t, *rs, *res) compareStoredAndReturnedRelease(t, *rs, res)
} }
func TestUpdateRelease_ResetReuseValues(t *testing.T) { func TestUpdateRelease_ResetReuseValues(t *testing.T) {
// This verifies that when both reset and reuse are set, reset wins. // This verifies that when both reset and reuse are set, reset wins.
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -286,19 +279,18 @@ func TestUpdateRelease_ResetReuseValues(t *testing.T) {
ResetValues: true, ResetValues: true,
ReuseValues: true, ReuseValues: true,
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
// This should have been unset. Config: &chart.Config{Raw: `name: value`}, // This should have been unset. Config: &chart.Config{Raw: `name: value`},
if res.Release.Config != nil && res.Release.Config.Raw != "" { if res.Config != nil && res.Config.Raw != "" {
t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) t.Errorf("Expected chart config to be empty, got %q", res.Config.Raw)
} }
compareStoredAndReturnedRelease(t, *rs, *res) compareStoredAndReturnedRelease(t, *rs, res)
} }
func TestUpdateReleaseFailure(t *testing.T) { func TestUpdateReleaseFailure(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -316,19 +308,19 @@ func TestUpdateReleaseFailure(t *testing.T) {
}, },
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err == nil { if err == nil {
t.Error("Expected failed update") t.Error("Expected failed update")
} }
if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_FAILED { if updatedStatus := res.Info.Status.Code; updatedStatus != release.Status_FAILED {
t.Errorf("Expected FAILED release. Got %d", updatedStatus) t.Errorf("Expected FAILED release. Got %d", updatedStatus)
} }
compareStoredAndReturnedRelease(t, *rs, *res) compareStoredAndReturnedRelease(t, *rs, res)
expectedDescription := "Upgrade \"angry-panda\" failed: Failed update in kube client" expectedDescription := "Upgrade \"angry-panda\" failed: Failed update in kube client"
if got := res.Release.Info.Description; got != expectedDescription { if got := res.Info.Description; got != expectedDescription {
t.Errorf("Expected description %q, got %q", expectedDescription, got) t.Errorf("Expected description %q, got %q", expectedDescription, got)
} }
@ -342,7 +334,6 @@ func TestUpdateReleaseFailure(t *testing.T) {
} }
func TestUpdateReleaseFailure_Force(t *testing.T) { func TestUpdateReleaseFailure_Force(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := namedReleaseStub("forceful-luke", release.Status_FAILED) rel := namedReleaseStub("forceful-luke", release.Status_FAILED)
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -360,19 +351,19 @@ func TestUpdateReleaseFailure_Force(t *testing.T) {
Force: true, Force: true,
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Errorf("Expected successful update, got %v", err) t.Errorf("Expected successful update, got %v", err)
} }
if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_DEPLOYED { if updatedStatus := res.Info.Status.Code; updatedStatus != release.Status_DEPLOYED {
t.Errorf("Expected DEPLOYED release. Got %d", updatedStatus) t.Errorf("Expected DEPLOYED release. Got %d", updatedStatus)
} }
compareStoredAndReturnedRelease(t, *rs, *res) compareStoredAndReturnedRelease(t, *rs, res)
expectedDescription := "Upgrade complete" expectedDescription := "Upgrade complete"
if got := res.Release.Info.Description; got != expectedDescription { if got := res.Info.Description; got != expectedDescription {
t.Errorf("Expected description %q, got %q", expectedDescription, got) t.Errorf("Expected description %q, got %q", expectedDescription, got)
} }
@ -386,7 +377,6 @@ func TestUpdateReleaseFailure_Force(t *testing.T) {
} }
func TestUpdateReleaseNoHooks(t *testing.T) { func TestUpdateReleaseNoHooks(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -403,19 +393,18 @@ func TestUpdateReleaseNoHooks(t *testing.T) {
}, },
} }
res, err := rs.UpdateRelease(c, req) res, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
if hl := res.Release.Hooks[0].LastRun; hl != nil { if hl := res.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl) t.Errorf("Expected that no hooks were run. Got %d", hl)
} }
} }
func TestUpdateReleaseNoChanges(t *testing.T) { func TestUpdateReleaseNoChanges(t *testing.T) {
c := context.TODO()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
@ -426,19 +415,19 @@ func TestUpdateReleaseNoChanges(t *testing.T) {
Chart: rel.GetChart(), Chart: rel.GetChart(),
} }
_, err := rs.UpdateRelease(c, req) _, err := rs.UpdateRelease(req)
if err != nil { if err != nil {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
} }
func compareStoredAndReturnedRelease(t *testing.T, rs ReleaseServer, res services.UpdateReleaseResponse) *release.Release { func compareStoredAndReturnedRelease(t *testing.T, rs ReleaseServer, res *release.Release) *release.Release {
storedRelease, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) storedRelease, err := rs.env.Releases.Get(res.Name, res.Version)
if err != nil { if err != nil {
t.Fatalf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) t.Fatalf("Expected release for %s (%v).", res.Name, rs.env.Releases)
} }
if !proto.Equal(storedRelease, res.Release) { if !proto.Equal(storedRelease, res) {
t.Errorf("Stored release doesn't match returned Release") t.Errorf("Stored release doesn't match returned Release")
} }

@ -1,30 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tiller
import (
ctx "golang.org/x/net/context"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/version"
)
// GetVersion sends the server version.
func (s *ReleaseServer) GetVersion(c ctx.Context, req *services.GetVersionRequest) (*services.GetVersionResponse, error) {
v := version.GetVersionProto()
return &services.GetVersionResponse{Version: v}, nil
}

@ -1,96 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tiller
import (
"fmt"
"log"
"strings"
goprom "github.com/grpc-ecosystem/go-grpc-prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"k8s.io/helm/pkg/version"
)
// maxMsgSize use 20MB as the default message size limit.
// grpc library default is 4MB
const maxMsgSize = 1024 * 1024 * 20
// DefaultServerOpts returns the set of default grpc ServerOption's that Tiller requires.
func DefaultServerOpts() []grpc.ServerOption {
return []grpc.ServerOption{
grpc.MaxRecvMsgSize(maxMsgSize),
grpc.MaxSendMsgSize(maxMsgSize),
grpc.UnaryInterceptor(newUnaryInterceptor()),
grpc.StreamInterceptor(newStreamInterceptor()),
}
}
// NewServer creates a new grpc server.
func NewServer(opts ...grpc.ServerOption) *grpc.Server {
return grpc.NewServer(append(DefaultServerOpts(), opts...)...)
}
func newUnaryInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if err := checkClientVersion(ctx); err != nil {
// whitelist GetVersion() from the version check
if _, m := splitMethod(info.FullMethod); m != "GetVersion" {
log.Println(err)
return nil, err
}
}
return goprom.UnaryServerInterceptor(ctx, req, info, handler)
}
}
func newStreamInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if err := checkClientVersion(ss.Context()); err != nil {
log.Println(err)
return err
}
return goprom.StreamServerInterceptor(srv, ss, info, handler)
}
}
func splitMethod(fullMethod string) (string, string) {
if frags := strings.Split(fullMethod, "/"); len(frags) == 3 {
return frags[1], frags[2]
}
return "unknown", "unknown"
}
func versionFromContext(ctx context.Context) string {
if md, ok := metadata.FromIncomingContext(ctx); ok {
if v, ok := md["x-helm-api-client"]; ok && len(v) > 0 {
return v[0]
}
}
return ""
}
func checkClientVersion(ctx context.Context) error {
clientVersion := versionFromContext(ctx)
if !version.IsCompatible(clientVersion, version.GetVersion()) {
return fmt.Errorf("incompatible versions client[%s] server[%s]", clientVersion, version.GetVersion())
}
return nil
}
Loading…
Cancel
Save