Merge branch 'master' into deploymentsReady-when-newRS-has-minimumReplicas

pull/2143/head
kiich 9 years ago committed by GitHub
commit 85a91394aa

@ -46,4 +46,6 @@ message Hook {
repeated Event events = 5; repeated Event events = 5;
// LastRun indicates the date/time this was last run. // LastRun indicates the date/time this was last run.
google.protobuf.Timestamp last_run = 6; google.protobuf.Timestamp last_run = 6;
// Weight indicates the sort order for execution among similar Hook type
int32 weight = 7;
} }

@ -203,6 +203,9 @@ message UpdateReleaseRequest {
// wait, if true, will wait until all Pods, PVCs, and Services are in a ready state // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state
// before marking the release as successful. It will wait for as long as timeout // before marking the release as successful. It will wait for as long as timeout
bool wait = 9; bool wait = 9;
// ReuseValues will cause Tiller to reuse the values from the last release.
// This is ignored if reset_values is set.
bool reuse_values = 10;
} }
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.

@ -47,10 +47,10 @@ For example, this requirements file declares two dependencies:
dependencies: dependencies:
- name: nginx - name: nginx
version: "1.2.3" version: "1.2.3"
repository: "https://example.com/charts" repository: "https://example.com/charts"
- name: memcached - name: memcached
version: "3.2.1" version: "3.2.1"
repository: "https://another.example.com/charts" repository: "https://another.example.com/charts"
The 'name' should be the name of a chart, where that name must match the name The 'name' should be the name of a chart, where that name must match the name
in that chart's 'Chart.yaml' file. in that chart's 'Chart.yaml' file.

@ -45,6 +45,14 @@ const (
tillerNamespaceEnvVar = "TILLER_NAMESPACE" tillerNamespaceEnvVar = "TILLER_NAMESPACE"
) )
var (
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
tlsKeyFile string // path to TLS key file
tlsVerify bool // enable TLS and verify remote certificates
tlsEnable bool // enable TLS
)
var ( var (
helmHome string helmHome string
tillerHost string tillerHost string

@ -62,15 +62,17 @@ const (
) )
type initCmd struct { type initCmd struct {
image string image string
clientOnly bool clientOnly bool
canary bool canary bool
upgrade bool upgrade bool
namespace string namespace string
dryRun bool dryRun bool
out io.Writer skipRefresh bool
home helmpath.Home out io.Writer
kubeClient internalclientset.Interface home helmpath.Home
opts installer.Options
kubeClient internalclientset.Interface
} }
func newInitCmd(out io.Writer) *cobra.Command { func newInitCmd(out io.Writer) *cobra.Command {
@ -98,26 +100,106 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if tiller is already installed") f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if tiller is already installed")
f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller") f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller")
f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote")
f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
// f.BoolVar(&tlsEnable, "tiller-tls", false, "install tiller with TLS enabled")
// f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install tiller with TLS enabled and to verify remote certificates")
// f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with tiller")
// f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with tiller")
// f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate")
return cmd return cmd
} }
// tlsOptions sanitizes the tls flags as well as checks for the existence of required
// tls files indicated by those flags, if any.
func (i *initCmd) tlsOptions() error {
i.opts.EnableTLS = tlsEnable || tlsVerify
i.opts.VerifyTLS = tlsVerify
if i.opts.EnableTLS {
missing := func(file string) bool {
_, err := os.Stat(file)
return os.IsNotExist(err)
}
if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) {
return errors.New("missing required TLS key file")
}
if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) {
return errors.New("missing required TLS certificate file")
}
if i.opts.VerifyTLS {
if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) {
return errors.New("missing required TLS CA file")
}
}
}
return nil
}
// runInit initializes local config and installs tiller to Kubernetes Cluster // runInit initializes local config and installs tiller to Kubernetes Cluster
func (i *initCmd) run() error { func (i *initCmd) run() error {
if err := i.tlsOptions(); err != nil {
return err
}
i.opts.Namespace = i.namespace
i.opts.UseCanary = i.canary
i.opts.ImageSpec = i.image
if flagDebug { if flagDebug {
dm, err := installer.DeploymentManifest(i.namespace, i.image, i.canary) writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error {
if err != nil { w := i.out
if !first {
// YAML starting document boundary marker
if _, err := fmt.Fprintln(w, "---"); err != nil {
return err
}
}
if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil {
return err
}
if _, err := fmt.Fprintln(w, "kind:", kind); err != nil {
return err
}
if _, err := fmt.Fprint(w, body); err != nil {
return err
}
if !last {
return nil
}
// YAML ending document boundary marker
_, err := fmt.Fprintln(w, "...")
return err return err
} }
fm := fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", dm)
fmt.Fprintln(i.out, fm)
sm, err := installer.ServiceManifest(i.namespace) var body string
if err != nil { var err error
// write Deployment manifest
if body, err = installer.DeploymentManifest(&i.opts); err != nil {
return err
}
if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil {
return err
}
// write Service manifest
if body, err = installer.ServiceManifest(i.namespace); err != nil {
return err
}
if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil {
return err return err
} }
fm = fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", sm)
fmt.Fprintln(i.out, fm) // write Secret manifest
if i.opts.EnableTLS {
if body, err = installer.SecretManifest(&i.opts); err != nil {
return err
}
if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil {
return err
}
}
} }
if i.dryRun { if i.dryRun {
@ -127,7 +209,7 @@ func (i *initCmd) run() error {
if err := ensureDirectories(i.home, i.out); err != nil { if err := ensureDirectories(i.home, i.out); err != nil {
return err return err
} }
if err := ensureDefaultRepos(i.home, i.out); err != nil { if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil {
return err return err
} }
if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil { if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil {
@ -143,13 +225,12 @@ func (i *initCmd) run() error {
} }
i.kubeClient = c i.kubeClient = c
} }
opts := &installer.Options{Namespace: i.namespace, ImageSpec: i.image, UseCanary: i.canary} if err := installer.Install(i.kubeClient, &i.opts); err != nil {
if err := installer.Install(i.kubeClient, opts); err != nil {
if !kerrors.IsAlreadyExists(err) { if !kerrors.IsAlreadyExists(err) {
return fmt.Errorf("error installing: %s", err) return fmt.Errorf("error installing: %s", err)
} }
if i.upgrade { if i.upgrade {
if err := installer.Upgrade(i.kubeClient, opts); err != nil { if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil {
return fmt.Errorf("error when upgrading: %s", err) return fmt.Errorf("error when upgrading: %s", err)
} }
fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.") fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.")
@ -194,12 +275,12 @@ func ensureDirectories(home helmpath.Home, out io.Writer) error {
return nil return nil
} }
func ensureDefaultRepos(home helmpath.Home, out io.Writer) error { func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error {
repoFile := home.RepositoryFile() repoFile := home.RepositoryFile()
if fi, err := os.Stat(repoFile); err != nil { if fi, err := os.Stat(repoFile); err != nil {
fmt.Fprintf(out, "Creating %s \n", repoFile) fmt.Fprintf(out, "Creating %s \n", repoFile)
f := repo.NewRepoFile() f := repo.NewRepoFile()
sr, err := initStableRepo(home.CacheIndex(stableRepository)) sr, err := initStableRepo(home.CacheIndex(stableRepository), skipRefresh)
if err != nil { if err != nil {
return err return err
} }
@ -218,7 +299,7 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer) error {
return nil return nil
} }
func initStableRepo(cacheFile string) (*repo.Entry, error) { func initStableRepo(cacheFile string, skipRefresh bool) (*repo.Entry, error) {
c := repo.Entry{ c := repo.Entry{
Name: stableRepository, Name: stableRepository,
URL: stableRepositoryURL, URL: stableRepositoryURL,
@ -229,6 +310,10 @@ func initStableRepo(cacheFile string) (*repo.Entry, error) {
return nil, err return nil, err
} }
if skipRefresh {
return &c, nil
}
// In this case, the cacheFile is always absolute. So passing empty string // In this case, the cacheFile is always absolute. So passing empty string
// is safe. // is safe.
if err := r.DownloadIndexFile(""); err != nil { if err := r.DownloadIndexFile(""); err != nil {

@ -156,13 +156,19 @@ func TestInitCmd_dryRun(t *testing.T) {
if err := cmd.run(); err != nil { if err := cmd.run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(fc.Actions()) != 0 { if got := len(fc.Actions()); got != 0 {
t.Error("expected no server calls") t.Errorf("expected no server calls, got %d", got)
} }
var y map[string]interface{} docs := bytes.Split(buf.Bytes(), []byte("\n---"))
if err := yaml.Unmarshal(buf.Bytes(), &y); err != nil { if got, want := len(docs), 2; got != want {
t.Errorf("Expected parseable YAML, got %q\n\t%s", buf.String(), err) t.Fatalf("Expected document count of %d, got %d", want, got)
}
for _, doc := range docs {
var y map[string]interface{}
if err := yaml.Unmarshal(doc, &y); err != nil {
t.Errorf("Expected parseable YAML, got %q\n\t%s", doc, err)
}
} }
} }
@ -179,7 +185,10 @@ func TestEnsureHome(t *testing.T) {
if err := ensureDirectories(hh, b); err != nil { if err := ensureDirectories(hh, b); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensureDefaultRepos(hh, b); err != nil { if err := ensureDefaultRepos(hh, b, false); err != nil {
t.Error(err)
}
if err := ensureDefaultRepos(hh, b, true); err != nil {
t.Error(err) t.Error(err)
} }
if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil { if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil {

@ -17,7 +17,7 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer" package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"fmt" "io/ioutil"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
@ -28,22 +28,23 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
"k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/helm/pkg/version"
) )
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Install uses kubernetes client to install tiller. // Install uses kubernetes client to install tiller.
// //
// Returns an error if the command failed. // Returns an error if the command failed.
func Install(client internalclientset.Interface, opts *Options) error { func Install(client internalclientset.Interface, opts *Options) error {
if err := createDeployment(client.Extensions(), opts.Namespace, opts.ImageSpec, opts.UseCanary); err != nil { if err := createDeployment(client.Extensions(), opts); err != nil {
return err return err
} }
if err := createService(client.Core(), opts.Namespace); err != nil { if err := createService(client.Core(), opts.Namespace); err != nil {
return err return err
} }
if opts.tls() {
if err := createSecret(client.Core(), opts); err != nil {
return err
}
}
return nil return nil
} }
@ -55,7 +56,7 @@ func Upgrade(client internalclientset.Interface, opts *Options) error {
if err != nil { if err != nil {
return err return err
} }
obj.Spec.Template.Spec.Containers[0].Image = selectImage(opts.ImageSpec, opts.UseCanary) obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil { if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil {
return err return err
} }
@ -73,15 +74,15 @@ func Upgrade(client internalclientset.Interface, opts *Options) error {
} }
// createDeployment creates the Tiller deployment reource // createDeployment creates the Tiller deployment reource
func createDeployment(client extensionsclient.DeploymentsGetter, namespace, image string, canary bool) error { func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj := deployment(namespace, image, canary) obj := deployment(opts)
_, err := client.Deployments(obj.Namespace).Create(obj) _, err := client.Deployments(obj.Namespace).Create(obj)
return err return err
} }
// deployment gets the deployment object that installs Tiller. // deployment gets the deployment object that installs Tiller.
func deployment(namespace, image string, canary bool) *extensions.Deployment { func deployment(opts *Options) *extensions.Deployment {
return generateDeployment(namespace, selectImage(image, canary)) return generateDeployment(opts)
} }
// createService creates the Tiller service resource // createService creates the Tiller service resource
@ -96,21 +97,10 @@ func service(namespace string) *api.Service {
return generateService(namespace) return generateService(namespace)
} }
func selectImage(image string, canary bool) string {
switch {
case canary:
image = defaultImage + ":canary"
case image == "":
image = fmt.Sprintf("%s:%s", defaultImage, version.Version)
}
return image
}
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource. // resource.
func DeploymentManifest(namespace, image string, canary bool) (string, error) { func DeploymentManifest(opts *Options) (string, error) {
obj := deployment(namespace, image, canary) obj := deployment(opts)
buf, err := yaml.Marshal(obj) buf, err := yaml.Marshal(obj)
return string(buf), err return string(buf), err
} }
@ -129,11 +119,11 @@ func generateLabels(labels map[string]string) map[string]string {
return labels return labels
} }
func generateDeployment(namespace, image string) *extensions.Deployment { func generateDeployment(opts *Options) *extensions.Deployment {
labels := generateLabels(map[string]string{"name": "tiller"}) labels := generateLabels(map[string]string{"name": "tiller"})
d := &extensions.Deployment{ d := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Namespace: namespace, Namespace: opts.Namespace,
Name: "tiller-deploy", Name: "tiller-deploy",
Labels: labels, Labels: labels,
}, },
@ -147,13 +137,13 @@ func generateDeployment(namespace, image string) *extensions.Deployment {
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: "tiller", Name: "tiller",
Image: image, Image: opts.selectImage(),
ImagePullPolicy: "IfNotPresent", ImagePullPolicy: "IfNotPresent",
Ports: []api.ContainerPort{ Ports: []api.ContainerPort{
{ContainerPort: 44134, Name: "tiller"}, {ContainerPort: 44134, Name: "tiller"},
}, },
Env: []api.EnvVar{ Env: []api.EnvVar{
{Name: "TILLER_NAMESPACE", Value: namespace}, {Name: "TILLER_NAMESPACE", Value: opts.Namespace},
}, },
LivenessProbe: &api.Probe{ LivenessProbe: &api.Probe{
Handler: api.Handler{ Handler: api.Handler{
@ -181,6 +171,37 @@ func generateDeployment(namespace, image string) *extensions.Deployment {
}, },
}, },
} }
if opts.tls() {
const certsDir = "/etc/certs"
var tlsVerify, tlsEnable = "", "1"
if opts.VerifyTLS {
tlsVerify = "1"
}
// Mount secret to "/etc/certs"
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, api.VolumeMount{
Name: "tiller-certs",
ReadOnly: true,
MountPath: certsDir,
})
// Add environment variable required for enabling TLS
d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []api.EnvVar{
{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
{Name: "TILLER_TLS_CERTS", Value: certsDir},
}...)
// Add secret volume to deployment
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, api.Volume{
Name: "tiller-certs",
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: "tiller-secret",
},
},
})
}
return d return d
} }
@ -206,3 +227,54 @@ func generateService(namespace string) *api.Service {
} }
return s return s
} }
// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource.
func SecretManifest(opts *Options) (string, error) {
o, err := generateSecret(opts)
if err != nil {
return "", err
}
buf, err := yaml.Marshal(o)
return string(buf), err
}
// createSecret creates the Tiller secret resource.
func createSecret(client internalversion.SecretsGetter, opts *Options) error {
o, err := generateSecret(opts)
if err != nil {
return err
}
_, err = client.Secrets(o.Namespace).Create(o)
return err
}
// generateSecret builds the secret object that hold Tiller secrets.
func generateSecret(opts *Options) (*api.Secret, error) {
const secretName = "tiller-secret"
labels := generateLabels(map[string]string{"name": "tiller"})
secret := &api.Secret{
Type: api.SecretTypeOpaque,
Data: make(map[string][]byte),
ObjectMeta: api.ObjectMeta{
Name: secretName,
Labels: labels,
Namespace: opts.Namespace,
},
}
var err error
if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
return nil, err
}
if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
return nil, err
}
if opts.VerifyTLS {
if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
return nil, err
}
}
return secret, nil
}
func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }

@ -45,7 +45,7 @@ func TestDeploymentManifest(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
o, err := DeploymentManifest(api.NamespaceDefault, tt.image, tt.canary) o, err := DeploymentManifest(&Options{Namespace: api.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary})
if err != nil { if err != nil {
t.Fatalf("%s: error %q", tt.name, err) t.Fatalf("%s: error %q", tt.name, err)
} }
@ -146,7 +146,11 @@ func TestInstall_canary(t *testing.T) {
func TestUpgrade(t *testing.T) { func TestUpgrade(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
existingService := service(api.NamespaceDefault) existingService := service(api.NamespaceDefault)
fc := &fake.Clientset{} fc := &fake.Clientset{}
@ -178,7 +182,11 @@ func TestUpgrade(t *testing.T) {
func TestUpgrade_serviceNotFound(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {

@ -16,6 +16,13 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer" package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"k8s.io/helm/pkg/version"
)
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Options control how to install tiller into a cluster, upgrade, and uninstall tiller from a cluster. // Options control how to install tiller into a cluster, upgrade, and uninstall tiller from a cluster.
type Options struct { type Options struct {
// EnableTLS instructs tiller to serve with TLS enabled. // EnableTLS instructs tiller to serve with TLS enabled.
@ -43,7 +50,7 @@ type Options struct {
// key tiller should use. // key tiller should use.
// //
// Required and valid if and only if EnableTLS or VerifyTLS is set. // Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSKey string TLSKeyFile string
// TLSCertFile identifies the file containing the pem encoded TLS // TLSCertFile identifies the file containing the pem encoded TLS
// certificate tiller should use. // certificate tiller should use.
@ -57,3 +64,16 @@ type Options struct {
// Required and valid if and only if VerifyTLS is set. // Required and valid if and only if VerifyTLS is set.
TLSCaCertFile string TLSCaCertFile string
} }
func (opts *Options) selectImage() string {
switch {
case opts.UseCanary:
return defaultImage + ":canary"
case opts.ImageSpec == "":
return fmt.Sprintf("%s:%s", defaultImage, version.Version)
default:
return opts.ImageSpec
}
}
func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS }

@ -55,7 +55,11 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e
func TestUninstall(t *testing.T) { func TestUninstall(t *testing.T) {
existingService := service(api.NamespaceDefault) existingService := service(api.NamespaceDefault)
existingDeployment := deployment(api.NamespaceDefault, "image", false) existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "image",
UseCanary: false,
})
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
@ -92,7 +96,7 @@ func TestUninstall(t *testing.T) {
} }
func TestUninstall_serviceNotFound(t *testing.T) { func TestUninstall_serviceNotFound(t *testing.T) {
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) existingDeployment := deployment(&Options{Namespace: api.NamespaceDefault, ImageSpec: "imageToReplace", UseCanary: false})
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {

@ -48,14 +48,16 @@ Versioned chart archives are used by Helm package repositories.
` `
type packageCmd struct { type packageCmd struct {
save bool save bool
sign bool sign bool
path string path string
key string key string
keyring string keyring string
version string version string
out io.Writer destination string
home helmpath.Home
out io.Writer
home helmpath.Home
} }
func newPackageCmd(out io.Writer) *cobra.Command { func newPackageCmd(out io.Writer) *cobra.Command {
@ -96,6 +98,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true")
f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring") f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version") f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version")
f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.")
return cmd return cmd
} }
@ -129,12 +132,19 @@ func (p *packageCmd) run(cmd *cobra.Command, args []string) error {
checkDependencies(ch, reqs, p.out) checkDependencies(ch, reqs, p.out)
} }
// Save to the current working directory. var dest string
cwd, err := os.Getwd() if p.destination == "." {
if err != nil { // Save to the current working directory.
return err dest, err = os.Getwd()
if err != nil {
return err
}
} else {
// Otherwise save to set destination
dest = p.destination
} }
name, err := chartutil.Save(ch, cwd)
name, err := chartutil.Save(ch, dest)
if err == nil && flagDebug { if err == nil && flagDebug {
fmt.Fprintf(p.out, "Saved %s to current directory\n", name) fmt.Fprintf(p.out, "Saved %s to current directory\n", name)
} }

@ -94,6 +94,20 @@ func TestPackage(t *testing.T) {
expect: "", expect: "",
hasfile: "alpine-0.1.0.tgz", hasfile: "alpine-0.1.0.tgz",
}, },
{
name: "package --destination toot",
args: []string{"testdata/testcharts/alpine"},
flags: map[string]string{"destination": "toot"},
expect: "",
hasfile: "toot/alpine-0.1.0.tgz",
},
{
name: "package --destination does-not-exist",
args: []string{"testdata/testcharts/alpine"},
flags: map[string]string{"destination": "does-not-exist"},
expect: "stat does-not-exist: no such file or directory",
err: true,
},
{ {
name: "package --sign --key=KEY --keyring=KEYRING testdata/testcharts/alpine", name: "package --sign --key=KEY --keyring=KEYRING testdata/testcharts/alpine",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
@ -124,6 +138,10 @@ func TestPackage(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := os.Mkdir("toot", 0777); err != nil {
t.Fatal(err)
}
ensureTestHome(helmpath.Home(tmp), t) ensureTestHome(helmpath.Home(tmp), t)
oldhome := homePath() oldhome := homePath()
helmHome = tmp helmHome = tmp

@ -72,6 +72,7 @@ type upgradeCmd struct {
version string version string
timeout int64 timeout int64
resetValues bool resetValues bool
reuseValues bool
wait bool wait bool
} }
@ -114,6 +115,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used")
f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)")
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.MarkDeprecated("disable-hooks", "use --no-hooks instead") f.MarkDeprecated("disable-hooks", "use --no-hooks instead")
@ -177,6 +179,7 @@ func (u *upgradeCmd) run() error {
helm.UpgradeDisableHooks(u.disableHooks), helm.UpgradeDisableHooks(u.disableHooks),
helm.UpgradeTimeout(u.timeout), helm.UpgradeTimeout(u.timeout),
helm.ResetValues(u.resetValues), helm.ResetValues(u.resetValues),
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", prettyError(err))

@ -109,6 +109,13 @@ func TestUpgradeCmd(t *testing.T) {
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 4, chart: ch2}), resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 4, chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
}, },
{
name: "upgrade a release with --reuse-values",
args: []string{"funny-bunny", chartPath},
flags: []string{"--reuse-values", "true"},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 5, chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
},
{ {
name: "install a release with 'upgrade --install'", name: "install a release with 'upgrade --install'",
args: []string{"zany-bunny", chartPath}, args: []string{"zany-bunny", chartPath},

@ -16,6 +16,8 @@ This will match version 1.2.0 and any patches to that release (1.2.1, 1.2.999, a
Where possible, use `https://` repository URLs, followed by `http://` URLs. Where possible, use `https://` repository URLs, followed by `http://` URLs.
If the repository has been added to the repository index file, the repository name can be used as an alias of URL. Use `alias:` or `@` followed by repository names.
File URLs (`file://...`) are considered a "special case" for charts that are assembled by a fixed deployment pipeline. Charts that use `file://` in a `requirements.yaml` file are not allowed in the official Helm repository. File URLs (`file://...`) are considered a "special case" for charts that are assembled by a fixed deployment pipeline. Charts that use `file://` in a `requirements.yaml` file are not allowed in the official Helm repository.
## Conditions and Tags ## Conditions and Tags

@ -137,7 +137,7 @@ data:
salad: {{ .Values.global.salad }} salad: {{ .Values.global.salad }}
``` ```
`mysubchart/tempaltes/configmap.yaml`: `mysubchart/templates/configmap.yaml`:
```yaml ```yaml
apiVersion: v1 apiVersion: v1

@ -302,6 +302,107 @@ helm install --set tags.front-end=true --set subchart2.enabled=false
* The `tags:` key in values must be a top level key. Globals and nested `tags:` tables * The `tags:` key in values must be a top level key. Globals and nested `tags:` tables
are not currently supported. are not currently supported.
#### Importing Child Values via requirements.yaml
In some cases it is desirable to allow a child chart's values to propagate to the parent chart and be
shared as common defaults. An additional benefit of using the `exports` format is that it will enable future
tooling to introspect user-settable values.
The keys containing the values to be imported can be specified in the parent chart's `requirements.yaml` file
using a YAML list. Each item in the list is a key which is imported from the child chart's `exports` field.
To import values not contained in the `exports` key, use the [child/parent](#using-the-child/parent-format) format.
Examples of both formats are described below.
##### Using the exports format
If a child chart's `values.yaml` file contains an `exports` field at the root, its contents may be imported
directly into the parent's values by specifying the keys to import as in the example below:
```yaml
# parent's requirements.yaml file
...
import-values:
- data
```
```yaml
# child's values.yaml file
...
exports:
data:
myint: 99
```
Since we are specifying the key `data` in our import list, Helm looks in the the `exports` field of the child
chart for `data` key and imports its contents.
The final parent values would contain our exported field:
```yaml
# parent's values file
...
myint: 99
```
Please note the parent key `data` is not contained in the parent's final values. If you need to specify the
parent key, use the 'child/parent' format.
##### Using the child/parent format
To access values that are not contained in the `exports` key of the child chart's values, you will need to
specify the source key of the values to be imported (`child`) and the destination path in the parent chart's
values (`parent`).
The `import-values` in the example below instructs Helm to take any values found at `child:` path and copy them
to the parent's values at the path specified in `parent:`
```yaml
# parent's requirements.yaml file
dependencies:
- name: subchart1
repository: http://localhost:10191
version: 0.1.0
...
import-values:
- child: default.data
parent: myimports
```
In the above example, values found at `default.data` in the subchart1's values will be imported
to the `myimports` key in the parent chart's values as detailed below:
```yaml
# parent's values.yaml file
myimports:
myint: 0
mybool: false
mystring: "helm rocks!"
```
```yaml
# subchart1's values.yaml file
default:
data:
myint: 999
mybool: true
```
The parent chart's resulting values would be:
```yaml
# parent's final values
myimports:
myint: 999
mybool: true
mystring: "helm rocks!"
```
The parent's final values now contains the `myint` and `mybool` fields imported from subchart1.
## Templates and Values ## Templates and Values
Helm Chart templates are written in the Helm Chart templates are written in the

@ -58,16 +58,18 @@ hooks, the lifecycle is altered like this:
1. User runs `helm install foo` 1. User runs `helm install foo`
2. Chart is loaded into Tiller 2. Chart is loaded into Tiller
3. After some verification, Tiller renders the `foo` templates 3. After some verification, Tiller renders the `foo` templates
4. Tiller executes the `pre-install` hook (loading hook resources into 4. Tiller prepares to execute the `pre-install` hooks (loading hook resources into
Kubernetes) Kubernetes)
5. Tiller waits until the hook is "Ready" 5. Tiller sorts hooks by weight (assigning a weight of 0 by default) and by name for those hooks with the same weight in ascending order.
6. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait` 6. Tiller then loads the hook with the lowest weight first (negative to positive)
7. Tiller waits until the hook is "Ready"
8. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait`
flag is set, Tiller will wait until all resources are in a ready state flag is set, Tiller will wait until all resources are in a ready state
and will not run the `post-install` hook until they are ready. and will not run the `post-install` hook until they are ready.
7. Tiller executes the `post-install` hook (loading hook resources) 9. Tiller executes the `post-install` hook (loading hook resources)
8. Tiller waits until the hook is "Ready" 10. Tiller waits until the hook is "Ready"
9. Tiller returns the release name (and other data) to the client 11. Tiller returns the release name (and other data) to the client
10. The client exits 12. The client exits
What does it mean to wait until a hook is ready? This depends on the What does it mean to wait until a hook is ready? This depends on the
resource declared in the hook. If the resources is a `Job` kind, Tiller resource declared in the hook. If the resources is a `Job` kind, Tiller
@ -114,6 +116,7 @@ metadata:
# This is what defines this resource as a hook. Without this line, the # This is what defines this resource as a hook. Without this line, the
# job is considered part of the release. # job is considered part of the release.
"helm.sh/hook": post-install "helm.sh/hook": post-install
"helm.sh/hook-weight": "-5"
spec: spec:
template: template:
metadata: metadata:
@ -154,3 +157,12 @@ When subcharts declare hooks, those are also evaluated. There is no way
for a top-level chart to disable the hooks declared by subcharts. And for a top-level chart to disable the hooks declared by subcharts. And
again, there is no guaranteed ordering. again, there is no guaranteed ordering.
It is also possible to define a weight for a hook which will help build a deterministic executing order. Weights are defined using the following annotation:
```
annotations:
"helm.sh/hook-weight": "5"
```
Hook weights can be positive or negative numbers but must be represented as strings. When Tiller starts the execution cycle of hooks of a particular Kind it will sort those hooks in ascending order.

@ -36,8 +36,20 @@ The 'version' field should contain a semantic version or version range.
The 'repository' URL should point to a Chart Repository. Helm expects that by The 'repository' URL should point to a Chart Repository. Helm expects that by
appending '/index.yaml' to the URL, it should be able to retrieve the chart appending '/index.yaml' to the URL, it should be able to retrieve the chart
repository's index. Note: 'repository' cannot be a repository alias. It must be repository's index.
a URL.
A repository can also be represented by a repository name defined in the index file
in lieu of a repository URL. If a repository alias is used, it is expected to start with
'alias:' or '@', followed by a repository name. For example,
# requirements.yaml
dependencies:
- name: nginx
version: "1.2.3"
repository: "alias:stable"
Note: In the above example, if the '@' syntax is used, the repository alias '@stable'
must be quoted, as YAML requires to use quotes if the value includes a special character
like '@'.
Starting from 2.2.0, repository can be defined as the path to the directory of Starting from 2.2.0, repository can be defined as the path to the directory of
the dependency charts stored locally. The path should start with a prefix of the dependency charts stored locally. The path should start with a prefix of
@ -53,7 +65,6 @@ If the dependency chart is retrieved locally, it is not required to have the
repository added to helm by "helm add repo". Version matching is also supported repository added to helm by "helm add repo". Version matching is also supported
for this case. for this case.
### Options inherited from parent commands ### Options inherited from parent commands
``` ```

@ -8,17 +8,20 @@ or [pull request](https://github.com/kubernetes/helm/pulls).
## Article, Blogs, How-Tos, and Extra Documentation ## Article, Blogs, How-Tos, and Extra Documentation
- [Using Helm to Deploy to Kubernetes](https://daemonza.github.io/2017/02/20/using-helm-to-deploy-to-kubernetes/) - [Using Helm to Deploy to Kubernetes](https://daemonza.github.io/2017/02/20/using-helm-to-deploy-to-kubernetes/)
- [Honestbee's Helm Chart Conventions](https://gist.github.com/so0k/f927a4b60003cedd101a0911757c605a) - [Honestbee's Helm Chart Conventions](https://gist.github.com/so0k/f927a4b60003cedd101a0911757c605a)
- [Deploying Kubernetes Applications with Helm](http://cloudacademy.com/blog/deploying-kubernetes-applications-with-helm/)
- [Releasing backward-incompatible changes: Kubernetes, Jenkins, Prometheus Operator, Helm and Traefik](https://medium.com/@enxebre/releasing-backward-incompatible-changes-kubernetes-jenkins-plugin-prometheus-operator-helm-self-6263ca61a1b1#.e0c7elxhq) - [Releasing backward-incompatible changes: Kubernetes, Jenkins, Prometheus Operator, Helm and Traefik](https://medium.com/@enxebre/releasing-backward-incompatible-changes-kubernetes-jenkins-plugin-prometheus-operator-helm-self-6263ca61a1b1#.e0c7elxhq)
- [CI/CD with Kubernetes, Helm & Wercker ](http://www.slideshare.net/Diacode/cicd-with-kubernetes-helm-wercker-madscalability) - [CI/CD with Kubernetes, Helm & Wercker ](http://www.slideshare.net/Diacode/cicd-with-kubernetes-helm-wercker-madscalability)
- [The missing CI/CD Kubernetes component: Helm package manager](https://hackernoon.com/the-missing-ci-cd-kubernetes-component-helm-package-manager-1fe002aac680#.691sk2zhu) - [The missing CI/CD Kubernetes component: Helm package manager](https://hackernoon.com/the-missing-ci-cd-kubernetes-component-helm-package-manager-1fe002aac680#.691sk2zhu)
- [CI/CD with Jenkins, Kubernetes, and Helm](https://www.youtube.com/watch?v=NVoln4HdZOY)
- [The Workflow "Umbrella" Helm Chart](https://deis.com/blog/2017/workflow-chart-assembly) - [The Workflow "Umbrella" Helm Chart](https://deis.com/blog/2017/workflow-chart-assembly)
- [GitLab, Consumer Driven Contracts, Helm and Kubernetes](https://medium.com/@enxebre/gitlab-consumer-driven-contracts-helm-and-kubernetes-b7235a60a1cb#.xwp1y4tgi) - [GitLab, Consumer Driven Contracts, Helm and Kubernetes](https://medium.com/@enxebre/gitlab-consumer-driven-contracts-helm-and-kubernetes-b7235a60a1cb#.xwp1y4tgi)
- [Writing a Helm Chart](https://www.influxdata.com/packaged-kubernetes-deployments-writing-helm-chart/) - [Writing a Helm Chart](https://www.influxdata.com/packaged-kubernetes-deployments-writing-helm-chart/)
- [Creating a Helm Plugin in 3 Steps](http://technosophos.com/2017/03/21/creating-a-helm-plugin.html)
## Videos ## Video, Audio, and Podcast
- [CI/CD with Jenkins, Kubernetes, and Helm](https://www.youtube.com/watch?v=NVoln4HdZOY): AKA "The Infamous Croc Hunter Video".
- [KubeCon2016: Delivering Kubernetes-Native Applications by Michelle Noorali](https://www.youtube.com/watch?v=zBc1goRfk3k&index=49&list=PLj6h78yzYM2PqgIGU1Qmi8nY7dqn9PCr4) - [KubeCon2016: Delivering Kubernetes-Native Applications by Michelle Noorali](https://www.youtube.com/watch?v=zBc1goRfk3k&index=49&list=PLj6h78yzYM2PqgIGU1Qmi8nY7dqn9PCr4)
- [Helm with Michelle Noorali and Matthew Butcher](https://gcppodcast.com/post/episode-50-helm-with-michelle-noorali-and-matthew-butcher/): The official Google CloudPlatform Podcast interviews Michelle and Matt about Helm.
## Helm Plugins ## Helm Plugins
@ -32,6 +35,7 @@ or [pull request](https://github.com/kubernetes/helm/pulls).
Tools layered on top of Helm or Tiller. Tools layered on top of Helm or Tiller.
- [Quay App Registry](https://coreos.com/blog/quay-application-registry-for-kubernetes.html) - Open Kubernetes application registry, including a Helm access client
- [Chartify](https://github.com/appscode/chartify) - Generate Helm charts from existing Kubernetes resources. - [Chartify](https://github.com/appscode/chartify) - Generate Helm charts from existing Kubernetes resources.
- [VIM-Kubernetes](https://github.com/andrewstuart/vim-kubernetes) - VIM plugin for Kubernetes and Helm - [VIM-Kubernetes](https://github.com/andrewstuart/vim-kubernetes) - VIM plugin for Kubernetes and Helm
- [Landscaper](https://github.com/Eneco/landscaper/) - "Landscaper takes a set of Helm Chart references with values (a desired state), and realizes this in a Kubernetes cluster." - [Landscaper](https://github.com/Eneco/landscaper/) - "Landscaper takes a set of Helm Chart references with values (a desired state), and realizes this in a Kubernetes cluster."

8
glide.lock generated

@ -1,5 +1,5 @@
hash: 0d1c5b7304a853820dcaa296d3aa1f5f3466a8491dcef80cbcaf43c954acb2a8 hash: df0fa621e6a6f80dbfeb815d9d8aa308c50346a9821e401b19b6f10782da3774
updated: 2017-03-15T15:56:18.814305691-06:00 updated: 2017-04-03T17:00:07.670429885-06:00
imports: imports:
- name: cloud.google.com/go - name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821 version: 3b1ae45394a234c385be014e9a488f2bb6eef821
@ -182,7 +182,7 @@ imports:
- jlexer - jlexer
- jwriter - jwriter
- name: github.com/Masterminds/semver - name: github.com/Masterminds/semver
version: 59c29afe1a994eacb71c833025ca7acf874bb1da version: 3f0ab6d4ab4bed1c61caf056b63a6e62190c7801
- name: github.com/Masterminds/sprig - name: github.com/Masterminds/sprig
version: 23597e5f6ad0e4d590e71314bfd0251a4a3cf849 version: 23597e5f6ad0e4d590e71314bfd0251a4a3cf849
- name: github.com/mattn/go-runewidth - name: github.com/mattn/go-runewidth
@ -300,7 +300,7 @@ imports:
- name: gopkg.in/yaml.v2 - name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4 version: a83829b6f1293c91addabc89d0571c246397bbf4
- name: k8s.io/kubernetes - name: k8s.io/kubernetes
version: 00a1fb254bd8e5235575fba1398b958943e39078 version: ea8f6637b639246faa14a8d5c6f864100fcb77a9
subpackages: subpackages:
- cmd/kubeadm/app/apis/kubeadm - cmd/kubeadm/app/apis/kubeadm
- cmd/kubeadm/app/apis/kubeadm/install - cmd/kubeadm/app/apis/kubeadm/install

@ -12,7 +12,7 @@ import:
version: ^2.10 version: ^2.10
- package: github.com/ghodss/yaml - package: github.com/ghodss/yaml
- package: github.com/Masterminds/semver - package: github.com/Masterminds/semver
version: ~1.2.2 version: ~1.2.3
- package: github.com/technosophos/moniker - package: github.com/technosophos/moniker
- package: github.com/golang/protobuf - package: github.com/golang/protobuf
version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e

@ -62,6 +62,9 @@ type Dependency struct {
Tags []string `json:"tags"` Tags []string `json:"tags"`
// Enabled bool determines if chart should be loaded // Enabled bool determines if chart should be loaded
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
// string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values"`
} }
// ErrNoRequirementsFile to detect error condition // ErrNoRequirementsFile to detect error condition
@ -266,3 +269,128 @@ func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error {
return nil return nil
} }
// pathToMap creates a nested map given a YAML path in dot notation.
func pathToMap(path string, data map[string]interface{}) map[string]interface{} {
if path == "." {
return data
}
ap := strings.Split(path, ".")
if len(ap) == 0 {
return nil
}
n := []map[string]interface{}{}
// created nested map for each key, adding to slice
for _, v := range ap {
nm := make(map[string]interface{})
nm[v] = make(map[string]interface{})
n = append(n, nm)
}
// find the last key (map) and set our data
for i, d := range n {
for k := range d {
z := i + 1
if z == len(n) {
n[i][k] = data
break
}
n[i][k] = n[z]
}
}
return n[0]
}
// getParents returns a slice of parent charts in reverse order.
func getParents(c *chart.Chart, out []*chart.Chart) []*chart.Chart {
if len(out) == 0 {
out = []*chart.Chart{c}
}
for _, ch := range c.Dependencies {
if len(ch.Dependencies) > 0 {
out = append(out, ch)
out = getParents(ch, out)
}
}
return out
}
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
func processImportValues(c *chart.Chart, v *chart.Config) error {
reqs, err := LoadRequirements(c)
if err != nil {
return err
}
// combine chart values and its dependencies' values
cvals, err := CoalesceValues(c, v)
if err != nil {
return err
}
nv := v.GetValues()
b := make(map[string]interface{}, len(nv))
// convert values to map
for kk, vvv := range nv {
b[kk] = vvv
}
// import values from each dependency if specified in import-values
for _, r := range reqs.Dependencies {
if len(r.ImportValues) > 0 {
var outiv []interface{}
for _, riv := range r.ImportValues {
switch iv := riv.(type) {
case map[string]interface{}:
nm := map[string]string{
"child": iv["child"].(string),
"parent": iv["parent"].(string),
}
outiv = append(outiv, nm)
s := r.Name + "." + nm["child"]
// get child table
vv, err := cvals.Table(s)
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
// create value map from child to be merged into parent
vm := pathToMap(nm["parent"], vv.AsMap())
b = coalesceTables(cvals, vm)
case string:
nm := map[string]string{
"child": "exports." + iv,
"parent": ".",
}
outiv = append(outiv, nm)
s := r.Name + "." + nm["child"]
vm, err := cvals.Table(s)
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
b = coalesceTables(b, vm.AsMap())
}
}
// set our formatted import values
r.ImportValues = outiv
}
}
b = coalesceTables(b, cvals)
y, err := yaml.Marshal(b)
if err != nil {
return err
}
// set the new values
c.Values.Raw = string(y)
return nil
}
// ProcessRequirementsImportValues imports specified chart values from child to parent.
func ProcessRequirementsImportValues(c *chart.Chart, v *chart.Config) error {
pc := getParents(c, nil)
for i := len(pc) - 1; i >= 0; i-- {
processImportValues(pc[i], v)
}
return nil
}

@ -18,6 +18,8 @@ import (
"sort" "sort"
"testing" "testing"
"strconv"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
) )
@ -206,3 +208,114 @@ func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart {
} }
return out return out
} }
func TestProcessRequirementsImportValues(t *testing.T) {
c, err := Load("testdata/subpop")
if err != nil {
t.Fatalf("Failed to load testdata: %s", err)
}
v := &chart.Config{Raw: ""}
e := make(map[string]string)
e["imported-chart1.SC1bool"] = "true"
e["imported-chart1.SC1float"] = "3.14"
e["imported-chart1.SC1int"] = "100"
e["imported-chart1.SC1string"] = "dollywood"
e["imported-chart1.SC1extra1"] = "11"
e["imported-chart1.SPextra1"] = "helm rocks"
e["imported-chart1.SC1extra1"] = "11"
e["imported-chartA.SCAbool"] = "false"
e["imported-chartA.SCAfloat"] = "3.1"
e["imported-chartA.SCAint"] = "55"
e["imported-chartA.SCAstring"] = "jabba"
e["imported-chartA.SPextra3"] = "1.337"
e["imported-chartA.SC1extra2"] = "1.337"
e["imported-chartA.SCAnested1.SCAnested2"] = "true"
e["imported-chartA-B.SCAbool"] = "false"
e["imported-chartA-B.SCAfloat"] = "3.1"
e["imported-chartA-B.SCAint"] = "55"
e["imported-chartA-B.SCAstring"] = "jabba"
e["imported-chartA-B.SCBbool"] = "true"
e["imported-chartA-B.SCBfloat"] = "7.77"
e["imported-chartA-B.SCBint"] = "33"
e["imported-chartA-B.SCBstring"] = "boba"
e["imported-chartA-B.SPextra5"] = "k8s"
e["imported-chartA-B.SC1extra5"] = "tiller"
e["overridden-chart1.SC1bool"] = "false"
e["overridden-chart1.SC1float"] = "3.141592"
e["overridden-chart1.SC1int"] = "99"
e["overridden-chart1.SC1string"] = "pollywog"
e["overridden-chart1.SPextra2"] = "42"
e["overridden-chartA.SCAbool"] = "true"
e["overridden-chartA.SCAfloat"] = "41.3"
e["overridden-chartA.SCAint"] = "808"
e["overridden-chartA.SCAstring"] = "jaberwocky"
e["overridden-chartA.SPextra4"] = "true"
e["overridden-chartA-B.SCAbool"] = "true"
e["overridden-chartA-B.SCAfloat"] = "41.3"
e["overridden-chartA-B.SCAint"] = "808"
e["overridden-chartA-B.SCAstring"] = "jaberwocky"
e["overridden-chartA-B.SCBbool"] = "false"
e["overridden-chartA-B.SCBfloat"] = "1.99"
e["overridden-chartA-B.SCBint"] = "77"
e["overridden-chartA-B.SCBstring"] = "jango"
e["overridden-chartA-B.SPextra6"] = "111"
e["overridden-chartA-B.SCAextra1"] = "23"
e["overridden-chartA-B.SCBextra1"] = "13"
e["overridden-chartA-B.SC1extra6"] = "77"
// `exports` style
e["SCBexported1B"] = "1965"
e["SC1extra7"] = "true"
e["SCBexported2A"] = "blaster"
e["global.SC1exported2.all.SC1exported3"] = "SC1expstr"
verifyRequirementsImportValues(t, c, v, e)
}
func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v *chart.Config, e map[string]string) {
err := ProcessRequirementsImportValues(c, v)
if err != nil {
t.Errorf("Error processing import values requirements %v", err)
}
cv := c.GetValues()
cc, err := ReadValues([]byte(cv.Raw))
if err != nil {
t.Errorf("Error reading import values %v", err)
}
for kk, vv := range e {
pv, err := cc.PathValue(kk)
if err != nil {
t.Fatalf("Error retrieving import values table %v %v", kk, err)
return
}
switch pv.(type) {
case float64:
s := strconv.FormatFloat(pv.(float64), 'f', -1, 64)
if s != vv {
t.Errorf("Failed to match imported float value %v with expected %v", s, vv)
return
}
case bool:
b := strconv.FormatBool(pv.(bool))
if b != vv {
t.Errorf("Failed to match imported bool value %v with expected %v", b, vv)
return
}
default:
if pv.(string) != vv {
t.Errorf("Failed to match imported string value %v with expected %v", pv, vv)
return
}
}
}
}

@ -1,21 +1,17 @@
# Default values for subchart. # Default values for subchart.
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
replicaCount: 1 # subchartA
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
service: service:
name: nginx name: nginx
type: ClusterIP type: ClusterIP
externalPort: 80 externalPort: 80
internalPort: 80 internalPort: 80
resources: SCAdata:
limits: SCAbool: false
cpu: 100m SCAfloat: 3.1
memory: 128Mi SCAint: 55
requests: SCAstring: "jabba"
cpu: 100m SCAnested1:
memory: 128Mi SCAnested2: true

@ -1,21 +1,35 @@
# Default values for subchart. # Default values for subchart.
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
service: service:
name: nginx name: nginx
type: ClusterIP type: ClusterIP
externalPort: 80 externalPort: 80
internalPort: 80 internalPort: 80
resources:
limits: SCBdata:
cpu: 100m SCBbool: true
memory: 128Mi SCBfloat: 7.77
requests: SCBint: 33
cpu: 100m SCBstring: "boba"
memory: 128Mi
exports:
SCBexported1:
SCBexported1A:
SCBexported1B: 1965
SCBexported2:
SCBexported2A: "blaster"
global:
kolla:
nova:
api:
all:
port: 8774
metadata:
all:
port: 8775

@ -6,10 +6,27 @@ dependencies:
tags: tags:
- front-end - front-end
- subcharta - subcharta
import-values:
- child: SCAdata
parent: imported-chartA
- child: SCAdata
parent: overridden-chartA
- child: SCAdata
parent: imported-chartA-B
- name: subchartb - name: subchartb
repository: http://localhost:10191 repository: http://localhost:10191
version: 0.1.0 version: 0.1.0
condition: subchartb.enabled condition: subchartb.enabled
import-values:
- child: SCBdata
parent: imported-chartB
- child: SCBdata
parent: imported-chartA-B
- child: exports.SCBexported2
parent: exports.SCBexported2
- SCBexported1
tags: tags:
- front-end - front-end
- subchartb - subchartb

@ -1,21 +1,55 @@
# Default values for subchart. # Default values for subchart.
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
replicaCount: 1 # subchart1
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
service: service:
name: nginx name: nginx
type: ClusterIP type: ClusterIP
externalPort: 80 externalPort: 80
internalPort: 80 internalPort: 80
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
SC1data:
SC1bool: true
SC1float: 3.14
SC1int: 100
SC1string: "dollywood"
SC1extra1: 11
imported-chartA:
SC1extra2: 1.337
overridden-chartA:
SCAbool: true
SCAfloat: 3.14
SCAint: 100
SCAstring: "jabathehut"
SC1extra3: true
imported-chartA-B:
SC1extra5: "tiller"
overridden-chartA-B:
SCAbool: true
SCAfloat: 3.33
SCAint: 555
SCAstring: "wormwood"
SCAextra1: 23
SCBbool: true
SCBfloat: 0.25
SCBint: 98
SCBstring: "murkwood"
SCBextra1: 13
SC1extra6: 77
SCBexported1A:
SC1extra7: true
exports:
SC1exported1:
global:
SC1exported2:
all:
SC1exported3: "SC1expstr"

@ -6,6 +6,22 @@ dependencies:
tags: tags:
- front-end - front-end
- subchart1 - subchart1
import-values:
- child: SC1data
parent: imported-chart1
- child: SC1data
parent: overridden-chart1
- child: imported-chartA
parent: imported-chartA
- child: imported-chartA-B
parent: imported-chartA-B
- child: overridden-chartA-B
parent: overridden-chartA-B
- child: SCBexported1A
parent: .
- SCBexported2
- SC1exported1
- name: subchart2 - name: subchart2
repository: http://localhost:10191 repository: http://localhost:10191
version: 0.1.0 version: 0.1.0

@ -1,6 +1,40 @@
# parent/values.yaml # parent/values.yaml
# switch-like imported-chart1:
SPextra1: "helm rocks"
overridden-chart1:
SC1bool: false
SC1float: 3.141592
SC1int: 99
SC1string: "pollywog"
SPextra2: 42
imported-chartA:
SPextra3: 1.337
overridden-chartA:
SCAbool: true
SCAfloat: 41.3
SCAint: 808
SCAstring: "jaberwocky"
SPextra4: true
imported-chartA-B:
SPextra5: "k8s"
overridden-chartA-B:
SCAbool: true
SCAfloat: 41.3
SCAint: 808
SCAstring: "jaberwocky"
SCBbool: false
SCBfloat: 1.99
SCBint: 77
SCBstring: "jango"
SPextra6: 111
tags: tags:
front-end: true front-end: true
back-end: false back-end: false

@ -124,6 +124,13 @@ func (m *Manager) Update() error {
} }
return err return err
} }
// Hash requirements.yaml
hash, err := resolver.HashReq(req)
if err != nil {
return err
}
// Check that all of the repos we're dependent on actually exist and // Check that all of the repos we're dependent on actually exist and
// the repo index names. // the repo index names.
repoNames, err := m.getRepoNames(req.Dependencies) repoNames, err := m.getRepoNames(req.Dependencies)
@ -140,7 +147,7 @@ func (m *Manager) Update() error {
// Now we need to find out which version of a chart best satisfies the // Now we need to find out which version of a chart best satisfies the
// requirements the requirements.yaml // requirements the requirements.yaml
lock, err := m.resolve(req, repoNames) lock, err := m.resolve(req, repoNames, hash)
if err != nil { if err != nil {
return err return err
} }
@ -172,9 +179,9 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
// resolve takes a list of requirements and translates them into an exact version to download. // resolve takes a list of requirements and translates them into an exact version to download.
// //
// This returns a lock file, which has all of the requirements normalized to a specific version. // This returns a lock file, which has all of the requirements normalized to a specific version.
func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]string) (*chartutil.RequirementsLock, error) { func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]string, hash string) (*chartutil.RequirementsLock, error) {
res := resolver.New(m.ChartPath, m.HelmHome) res := resolver.New(m.ChartPath, m.HelmHome)
return res.Resolve(req, repoNames) return res.Resolve(req, repoNames, hash)
} }
// downloadAll takes a list of dependencies and downloads them into charts/ // downloadAll takes a list of dependencies and downloads them into charts/
@ -325,14 +332,7 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string,
for _, dd := range deps { for _, dd := range deps {
// if dep chart is from local path, verify the path is valid // if dep chart is from local path, verify the path is valid
if strings.HasPrefix(dd.Repository, "file://") { if strings.HasPrefix(dd.Repository, "file://") {
depPath, err := filepath.Abs(strings.TrimPrefix(dd.Repository, "file://")) if _, err := resolver.GetLocalPath(dd.Repository, m.ChartPath); err != nil {
if err != nil {
return nil, err
}
if _, err = os.Stat(depPath); os.IsNotExist(err) {
return nil, fmt.Errorf("directory %s not found", depPath)
} else if err != nil {
return nil, err return nil, err
} }
@ -346,7 +346,13 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string,
found := false found := false
for _, repo := range repos { for _, repo := range repos {
if urlutil.Equal(repo.URL, dd.Repository) { if (strings.HasPrefix(dd.Repository, "@") && strings.TrimPrefix(dd.Repository, "@") == repo.Name) ||
(strings.HasPrefix(dd.Repository, "alias:") && strings.TrimPrefix(dd.Repository, "alias:") == repo.Name) {
found = true
dd.Repository = repo.URL
reposMap[dd.Name] = repo.Name
break
} else if urlutil.Equal(repo.URL, dd.Repository) {
found = true found = true
reposMap[dd.Name] = repo.Name reposMap[dd.Name] = repo.Name
break break
@ -537,17 +543,11 @@ func tarFromLocalDir(chartpath string, name string, repo string, version string)
return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo) return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo)
} }
origPath, err := filepath.Abs(strings.TrimPrefix(repo, "file://")) origPath, err := resolver.GetLocalPath(repo, chartpath)
if err != nil { if err != nil {
return "", err return "", err
} }
if _, err = os.Stat(origPath); os.IsNotExist(err) {
return "", fmt.Errorf("directory %s not found: %s", origPath, err)
} else if err != nil {
return "", err
}
ch, err := chartutil.LoadDir(origPath) ch, err := chartutil.LoadDir(origPath)
if err != nil { if err != nil {
return "", err return "", err

@ -120,6 +120,20 @@ func TestGetRepoNames(t *testing.T) {
}, },
expect: map[string]string{"local-dep": "file://./testdata/signtest"}, expect: map[string]string{"local-dep": "file://./testdata/signtest"},
}, },
{
name: "repo alias (alias:)",
req: []*chartutil.Dependency{
{Name: "oedipus-rex", Repository: "alias:testing"},
},
expect: map[string]string{"oedipus-rex": "testing"},
},
{
name: "repo alias (@)",
req: []*chartutil.Dependency{
{Name: "oedipus-rex", Repository: "@testing"},
},
expect: map[string]string{"oedipus-rex": "testing"},
},
} }
for _, tt := range tests { for _, tt := range tests {

@ -97,6 +97,10 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = chartutil.ProcessRequirementsImportValues(req.Chart, req.Values)
if err != nil {
return nil, err
}
return h.install(ctx, req) return h.install(ctx, req)
} }
@ -155,6 +159,7 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts
req.DisableHooks = h.opts.disableHooks req.DisableHooks = h.opts.disableHooks
req.Recreate = h.opts.recreate req.Recreate = h.opts.recreate
req.ResetValues = h.opts.resetValues req.ResetValues = h.opts.resetValues
req.ReuseValues = h.opts.reuseValues
ctx := NewContext() ctx := NewContext()
if h.opts.before != nil { if h.opts.before != nil {
@ -166,6 +171,10 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = chartutil.ProcessRequirementsImportValues(req.Chart, req.Values)
if err != nil {
return nil, err
}
return h.update(ctx, req) return h.update(ctx, req)
} }

@ -87,7 +87,9 @@ func TestListReleases_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).ListReleases(ops...) if _, err := NewClient(b4c).ListReleases(ops...); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
// Verify InstallOption's are applied to an InstallReleaseRequest correctly. // Verify InstallOption's are applied to an InstallReleaseRequest correctly.
@ -99,6 +101,7 @@ func TestInstallRelease_VerifyOptions(t *testing.T) {
var reuseName = true var reuseName = true
var dryRun = true var dryRun = true
var chartName = "alpine" var chartName = "alpine"
var chartPath = filepath.Join(chartsDir, chartName)
var overrides = []byte("key1=value1,key2=value2") var overrides = []byte("key1=value1,key2=value2")
// Expected InstallReleaseRequest message // Expected InstallReleaseRequest message
@ -133,7 +136,9 @@ func TestInstallRelease_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).InstallRelease(chartName, namespace, ops...) if _, err := NewClient(b4c).InstallRelease(chartPath, namespace, ops...); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
// Verify DeleteOptions's are applied to an UninstallReleaseRequest correctly. // Verify DeleteOptions's are applied to an UninstallReleaseRequest correctly.
@ -168,13 +173,16 @@ func TestDeleteRelease_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).DeleteRelease(releaseName, ops...) if _, err := NewClient(b4c).DeleteRelease(releaseName, ops...); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
// Verify UpdateOption's are applied to an UpdateReleaseRequest correctly. // Verify UpdateOption's are applied to an UpdateReleaseRequest correctly.
func TestUpdateRelease_VerifyOptions(t *testing.T) { func TestUpdateRelease_VerifyOptions(t *testing.T) {
// Options testdata // Options testdata
var chartName = "alpine" var chartName = "alpine"
var chartPath = filepath.Join(chartsDir, chartName)
var releaseName = "test" var releaseName = "test"
var disableHooks = true var disableHooks = true
var overrides = []byte("key1=value1,key2=value2") var overrides = []byte("key1=value1,key2=value2")
@ -208,7 +216,9 @@ func TestUpdateRelease_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).UpdateRelease(releaseName, chartName, ops...) if _, err := NewClient(b4c).UpdateRelease(releaseName, chartPath, ops...); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
// Verify RollbackOption's are applied to a RollbackReleaseRequest correctly. // Verify RollbackOption's are applied to a RollbackReleaseRequest correctly.
@ -246,7 +256,9 @@ func TestRollbackRelease_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).RollbackRelease(releaseName, ops...) if _, err := NewClient(b4c).RollbackRelease(releaseName, ops...); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
// Verify StatusOption's are applied to a GetReleaseStatusRequest correctly. // Verify StatusOption's are applied to a GetReleaseStatusRequest correctly.
@ -273,7 +285,9 @@ func TestReleaseStatus_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).ReleaseStatus(releaseName, StatusReleaseVersion(revision)) if _, err := NewClient(b4c).ReleaseStatus(releaseName, StatusReleaseVersion(revision)); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
// Verify ContentOption's are applied to a GetReleaseContentRequest correctly. // Verify ContentOption's are applied to a GetReleaseContentRequest correctly.
@ -300,7 +314,9 @@ func TestReleaseContent_VerifyOptions(t *testing.T) {
return errSkip return errSkip
}) })
NewClient(b4c).ReleaseContent(releaseName, ContentReleaseVersion(revision)) if _, err := NewClient(b4c).ReleaseContent(releaseName, ContentReleaseVersion(revision)); err != errSkip {
t.Fatalf("did not expect error but got (%v)\n``", err)
}
} }
func assert(t *testing.T, expect, actual interface{}) { func assert(t *testing.T, expect, actual interface{}) {

@ -66,6 +66,8 @@ type options struct {
histReq rls.GetHistoryRequest histReq rls.GetHistoryRequest
// resetValues instructs Tiller to reset values to their defaults. // resetValues instructs Tiller to reset values to their defaults.
resetValues bool resetValues bool
// reuseValues instructs Tiller to reuse the values from the last release.
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
} }
@ -323,6 +325,13 @@ func ResetValues(reset bool) UpdateOption {
} }
} }
// ReuseValues will (if true) trigger resetting the values to their original state.
func ReuseValues(reuse bool) UpdateOption {
return func(opts *options) {
opts.reuseValues = reuse
}
}
// UpgradeRecreate will (if true) recreate pods after upgrade. // UpgradeRecreate will (if true) recreate pods after upgrade.
func UpgradeRecreate(recreate bool) UpdateOption { func UpgradeRecreate(recreate bool) UpdateOption {
return func(opts *options) { return func(opts *options) {

@ -23,6 +23,9 @@ import (
// HookAnno is the label name for a hook // HookAnno is the label name for a hook
const HookAnno = "helm.sh/hook" const HookAnno = "helm.sh/hook"
// HookWeightAnno is the label name for a hook weight
const HookWeightAnno = "helm.sh/hook-weight"
// Types of hooks // Types of hooks
const ( const (
PreInstall = "pre-install" PreInstall = "pre-install"

@ -159,11 +159,14 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
missing := []string{}
err = perform(c, namespace, infos, func(info *resource.Info) error { err = perform(c, namespace, infos, func(info *resource.Info) error {
log.Printf("Doing get for: '%s'", info.Name) log.Printf("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name)
obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export) obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export)
if err != nil { if err != nil {
return err log.Printf("WARNING: Failed Get for resource %q: %s", info.Name, err)
missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name))
return nil
} }
// We need to grab the ObjectReference so we can correctly group the objects. // We need to grab the ObjectReference so we can correctly group the objects.
or, err := api.GetReference(obj) or, err := api.GetReference(obj)
@ -194,7 +197,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
} }
for _, o := range ot { for _, o := range ot {
if err := p.PrintObj(o, buf); err != nil { if err := p.PrintObj(o, buf); err != nil {
log.Printf("failed to print object type '%s', object: '%s' :\n %v", t, o, err) log.Printf("failed to print object type %s, object: %q :\n %v", t, o, err)
return "", err return "", err
} }
} }
@ -202,6 +205,12 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
return "", err return "", err
} }
} }
if len(missing) > 0 {
buf.WriteString("==> MISSING\nKIND\t\tNAME\n")
for _, s := range missing {
fmt.Fprintln(buf, s)
}
}
return buf.String(), nil return buf.String(), nil
} }
@ -241,17 +250,17 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader
} }
kind := info.Mapping.GroupVersionKind.Kind kind := info.Mapping.GroupVersionKind.Kind
log.Printf("Created a new %s called %s\n", kind, info.Name) log.Printf("Created a new %s called %q\n", kind, info.Name)
return nil return nil
} }
originalInfo := original.Get(info) originalInfo := original.Get(info)
if originalInfo == nil { if originalInfo == nil {
return fmt.Errorf("no resource with the name %s found", info.Name) return fmt.Errorf("no resource with the name %q found", info.Name)
} }
if err := updateResource(c, info, originalInfo.Object, recreate); err != nil { if err := updateResource(c, info, originalInfo.Object, recreate); err != nil {
log.Printf("error updating the resource %s:\n\t %v", info.Name, err) log.Printf("error updating the resource %q:\n\t %v", info.Name, err)
updateErrors = append(updateErrors, err.Error()) updateErrors = append(updateErrors, err.Error())
} }
@ -266,9 +275,9 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader
} }
for _, info := range original.Difference(target) { for _, info := range original.Difference(target) {
log.Printf("Deleting %s in %s...", info.Name, info.Namespace) log.Printf("Deleting %q in %s...", info.Name, info.Namespace)
if err := deleteResource(c, info); err != nil { if err := deleteResource(c, info); err != nil {
log.Printf("Failed to delete %s, err: %s", info.Name, err) log.Printf("Failed to delete %q, err: %s", info.Name, err)
} }
} }
if shouldWait { if shouldWait {
@ -286,7 +295,7 @@ func (c *Client) Delete(namespace string, reader io.Reader) error {
return err return err
} }
return perform(c, namespace, infos, func(info *resource.Info) error { return perform(c, namespace, infos, func(info *resource.Info) error {
log.Printf("Starting delete for %s %s", info.Name, info.Mapping.GroupVersionKind.Kind) log.Printf("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind)
err := deleteResource(c, info) err := deleteResource(c, info)
return skipIfNotFound(err) return skipIfNotFound(err)
}) })
@ -358,7 +367,7 @@ func deleteResource(c *Client, info *resource.Info) error {
} }
return err return err
} }
log.Printf("Using reaper for deleting %s", info.Name) log.Printf("Using reaper for deleting %q", info.Name)
return reaper.Stop(info.Namespace, info.Name, 0, nil) return reaper.Stop(info.Namespace, info.Name, 0, nil)
} }
@ -398,7 +407,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
return fmt.Errorf("failed to create patch: %s", err) return fmt.Errorf("failed to create patch: %s", err)
} }
if patch == nil { if patch == nil {
log.Printf("Looks like there are no changes for %s", target.Name) log.Printf("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name)
// This needs to happen to make sure that tiller has the latest info from the API // This needs to happen to make sure that tiller has the latest info from the API
// Otherwise there will be no labels and other functions that use labels will panic // Otherwise there will be no labels and other functions that use labels will panic
if err := target.Get(); err != nil { if err := target.Get(); err != nil {

@ -59,6 +59,7 @@ func newPodWithStatus(name string, status api.PodStatus, namespace string) api.P
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: name, Name: name,
Namespace: ns, Namespace: ns,
SelfLink: "/api/v1/namespaces/default/pods/" + name,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{{ Containers: []api.Container{{
@ -279,6 +280,49 @@ func TestBuild(t *testing.T) {
} }
} }
func TestGet(t *testing.T) {
list := newPodList("starfish", "otter")
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
p, m := req.URL.Path, req.Method
//actions = append(actions, p+":"+m)
t.Logf("got request %s %s", p, m)
switch {
case p == "/namespaces/default/pods/starfish" && m == "GET":
return newResponse(404, notFoundBody())
case p == "/namespaces/default/pods/otter" && m == "GET":
return newResponse(200, &list.Items[1])
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
}
}),
}
c := &Client{Factory: f}
// Test Success
data := strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: otter")
o, err := c.Get("default", data)
if err != nil {
t.Errorf("Expected missing results, got %q", err)
}
if !strings.Contains(o, "==> v1/Pod") && !strings.Contains(o, "otter") {
t.Errorf("Expected v1/Pod otter, got %s", o)
}
// Test failure
data = strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: starfish")
o, err = c.Get("default", data)
if err != nil {
t.Errorf("Expected missing results, got %q", err)
}
if !strings.Contains(o, "MISSING") && !strings.Contains(o, "pods\t\tstarfish") {
t.Errorf("Expected missing starfish, got %s", o)
}
}
func TestPerform(t *testing.T) { func TestPerform(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

@ -100,6 +100,8 @@ type Hook struct {
Events []Hook_Event `protobuf:"varint,5,rep,packed,name=events,enum=hapi.release.Hook_Event" json:"events,omitempty"` Events []Hook_Event `protobuf:"varint,5,rep,packed,name=events,enum=hapi.release.Hook_Event" json:"events,omitempty"`
// LastRun indicates the date/time this was last run. // LastRun indicates the date/time this was last run.
LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"`
// Weight indicates the sort order for execution among similar Hook type
Weight int32 `protobuf:"varint,7,opt,name=weight" json:"weight,omitempty"`
} }
func (m *Hook) Reset() { *m = Hook{} } func (m *Hook) Reset() { *m = Hook{} }
@ -122,28 +124,29 @@ func init() {
func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 354 bytes of a gzipped FileDescriptorProto // 371 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xdd, 0x6e, 0xa2, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xdf, 0x8e, 0x93, 0x40,
0x18, 0x86, 0x17, 0x41, 0xd0, 0xd1, 0x75, 0x67, 0x27, 0x9b, 0xec, 0xc4, 0x93, 0x35, 0x1e, 0x79, 0x14, 0x87, 0x65, 0x5b, 0xa0, 0x3d, 0x5d, 0xd7, 0x71, 0x62, 0x74, 0xd2, 0x1b, 0xc9, 0x5e, 0x71,
0x34, 0x6c, 0x6c, 0x7a, 0x01, 0xa8, 0xd3, 0xd6, 0x48, 0xd0, 0x0c, 0x90, 0x26, 0x3d, 0x21, 0x98, 0x35, 0x98, 0x35, 0x3e, 0x00, 0xdb, 0x1d, 0x75, 0xb3, 0x84, 0x36, 0x03, 0xc4, 0xc4, 0x1b, 0xc2,
0x8e, 0x4a, 0x14, 0x86, 0x08, 0xf6, 0x72, 0x7a, 0x55, 0xbd, 0xa0, 0x66, 0x86, 0x9f, 0x34, 0xe9, 0xc6, 0x69, 0x21, 0x2d, 0x0c, 0x29, 0x53, 0x7d, 0x33, 0x9f, 0xc4, 0x07, 0x32, 0x33, 0xfc, 0x89,
0xd9, 0xc7, 0xf3, 0x3e, 0x7c, 0x33, 0xef, 0x80, 0xbf, 0xa7, 0x38, 0x4f, 0xec, 0x2b, 0xbf, 0xf0, 0x89, 0x77, 0x67, 0xbe, 0xdf, 0xc7, 0x39, 0x9c, 0x03, 0xef, 0xca, 0xa2, 0xad, 0x82, 0xb3, 0x38,
0xb8, 0xe0, 0xf6, 0x49, 0x88, 0x33, 0xc9, 0xaf, 0xa2, 0x14, 0x68, 0x28, 0x03, 0x52, 0x07, 0xe3, 0x89, 0xa2, 0x13, 0x41, 0x29, 0xe5, 0x91, 0xb6, 0x67, 0xa9, 0x24, 0xbe, 0xd6, 0x01, 0x1d, 0x82,
0x7f, 0x47, 0x21, 0x8e, 0x17, 0x6e, 0xab, 0x6c, 0x7f, 0x3b, 0xd8, 0x65, 0x92, 0xf2, 0xa2, 0x8c, 0xf5, 0xfb, 0x83, 0x94, 0x87, 0x93, 0x08, 0x4c, 0xf6, 0x7c, 0xd9, 0x07, 0xaa, 0xaa, 0x45, 0xa7,
0xd3, 0xbc, 0xd2, 0xa7, 0xef, 0x3a, 0x30, 0x9e, 0x84, 0x38, 0x23, 0x04, 0x8c, 0x2c, 0x4e, 0x39, 0x8a, 0xba, 0xed, 0xf5, 0xdb, 0xdf, 0x33, 0x98, 0x7f, 0x95, 0xf2, 0x88, 0x31, 0xcc, 0x9b, 0xa2,
0xd6, 0x26, 0xda, 0xac, 0xcf, 0xd4, 0x2c, 0xd9, 0x39, 0xc9, 0x5e, 0x71, 0xa7, 0x62, 0x72, 0x96, 0x16, 0xc4, 0xf2, 0x2c, 0x7f, 0xc9, 0x4d, 0xad, 0xd9, 0xb1, 0x6a, 0x7e, 0x90, 0xab, 0x9e, 0xe9,
0x2c, 0x8f, 0xcb, 0x13, 0xd6, 0x2b, 0x26, 0x67, 0x34, 0x06, 0xbd, 0x34, 0xce, 0x92, 0x03, 0x2f, 0x5a, 0xb3, 0xb6, 0x50, 0x25, 0x99, 0xf5, 0x4c, 0xd7, 0x78, 0x0d, 0x8b, 0xba, 0x68, 0xaa, 0xbd,
0x4a, 0x6c, 0x28, 0xde, 0x7e, 0xa3, 0xff, 0xc0, 0xe4, 0x6f, 0x3c, 0x2b, 0x0b, 0xdc, 0x9d, 0xe8, 0xe8, 0x14, 0x99, 0x1b, 0x3e, 0xbd, 0xf1, 0x07, 0x70, 0xc4, 0x4f, 0xd1, 0xa8, 0x8e, 0xd8, 0xde,
0xb3, 0xd1, 0x1c, 0x93, 0xaf, 0x17, 0x24, 0xf2, 0x6c, 0x42, 0xa5, 0xc0, 0x6a, 0x0f, 0xdd, 0x83, 0xcc, 0xbf, 0xb9, 0x23, 0xf4, 0xdf, 0x1f, 0xa4, 0x7a, 0x36, 0x65, 0x5a, 0xe0, 0x83, 0x87, 0x3f,
0xde, 0x25, 0x2e, 0xca, 0xe8, 0x7a, 0xcb, 0xb0, 0x39, 0xd1, 0x66, 0x83, 0xf9, 0x98, 0x54, 0x35, 0xc1, 0xe2, 0x54, 0x74, 0x2a, 0x3f, 0x5f, 0x1a, 0xe2, 0x78, 0x96, 0xbf, 0xba, 0x5b, 0xd3, 0x7e,
0x48, 0x53, 0x83, 0x04, 0x4d, 0x0d, 0x66, 0x49, 0x97, 0xdd, 0xb2, 0xe9, 0x87, 0x06, 0xba, 0x6a, 0x0d, 0x3a, 0xae, 0x41, 0xd3, 0x71, 0x0d, 0xee, 0x6a, 0x97, 0x5f, 0x1a, 0xfc, 0x16, 0x9c, 0x5f,
0x11, 0x1a, 0x00, 0x2b, 0xf4, 0x36, 0xde, 0xf6, 0xd9, 0x83, 0x3f, 0xd0, 0x2f, 0x30, 0xd8, 0x31, 0xa2, 0x3a, 0x94, 0x8a, 0xb8, 0x9e, 0xe5, 0xdb, 0x7c, 0x78, 0xdd, 0xfe, 0xb1, 0xc0, 0x36, 0x03,
0x1a, 0xad, 0x3d, 0x3f, 0x70, 0x5c, 0x17, 0x6a, 0x08, 0x82, 0xe1, 0x6e, 0xeb, 0x07, 0x2d, 0xe9, 0xf0, 0x0a, 0xdc, 0x2c, 0x7e, 0x8a, 0xb7, 0xdf, 0x62, 0xf4, 0x02, 0xbf, 0x82, 0xd5, 0x8e, 0xb3,
0xa0, 0x11, 0x00, 0x52, 0x59, 0x51, 0x97, 0x06, 0x14, 0xea, 0xea, 0x17, 0x69, 0xd4, 0xc0, 0x68, 0xfc, 0x31, 0x4e, 0xd2, 0x30, 0x8a, 0x90, 0x85, 0x11, 0x5c, 0xef, 0xb6, 0x49, 0x3a, 0x91, 0x2b,
0x76, 0x84, 0xbb, 0x47, 0xe6, 0xac, 0x28, 0xec, 0xb6, 0x3b, 0x1a, 0x62, 0x2a, 0xc2, 0x68, 0xc4, 0x7c, 0x03, 0xa0, 0x95, 0x07, 0x16, 0xb1, 0x94, 0xa1, 0x99, 0xf9, 0x44, 0x1b, 0x03, 0x98, 0x8f,
0xb6, 0xae, 0xbb, 0x70, 0x96, 0x1b, 0x68, 0xa1, 0xdf, 0xe0, 0xa7, 0x72, 0x5a, 0xd4, 0x43, 0x18, 0x3d, 0xb2, 0xdd, 0x17, 0x1e, 0x3e, 0x30, 0x64, 0x4f, 0x3d, 0x46, 0xe2, 0x18, 0xc2, 0x59, 0xce,
0xfc, 0x61, 0xd4, 0xa5, 0x8e, 0x4f, 0xa3, 0x80, 0xfa, 0x41, 0xe4, 0x87, 0xcb, 0x25, 0xf5, 0x7d, 0xb7, 0x51, 0x74, 0x1f, 0x6e, 0x9e, 0x90, 0x8b, 0x5f, 0xc3, 0x4b, 0xe3, 0x4c, 0x68, 0x81, 0x09,
0xd8, 0xff, 0x96, 0x3c, 0x38, 0x6b, 0x37, 0x64, 0x14, 0x82, 0x45, 0xff, 0xc5, 0xaa, 0xdf, 0x6a, 0xbc, 0xe1, 0x2c, 0x62, 0x61, 0xc2, 0xf2, 0x94, 0x25, 0x69, 0x9e, 0x64, 0x9b, 0x0d, 0x4b, 0x12,
0x6f, 0xaa, 0xfa, 0x77, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x69, 0x41, 0x62, 0x57, 0xfc, 0x01, 0xb4, 0xfc, 0x2f, 0xf9, 0x1c, 0x3e, 0x46, 0x19, 0x67, 0x08, 0xee, 0x97, 0xdf, 0xdd, 0xe1, 0x86,
0x00, 0x00, 0xcf, 0x8e, 0x39, 0xcb, 0xc7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x3c, 0x7a, 0x0e, 0x14,
0x02, 0x00, 0x00,
} }

@ -259,6 +259,9 @@ type UpdateReleaseRequest struct {
// wait, if true, will wait until all Pods, PVCs, and Services are in a ready state // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state
// before marking the release as successful. It will wait for as long as timeout // before marking the release as successful. It will wait for as long as timeout
Wait bool `protobuf:"varint,9,opt,name=wait" json:"wait,omitempty"` Wait bool `protobuf:"varint,9,opt,name=wait" json:"wait,omitempty"`
// ReuseValues will cause Tiller to reuse the values from the last release.
// This is ignored if reset_values is set.
ReuseValues bool `protobuf:"varint,10,opt,name=reuse_values,json=reuseValues" json:"reuse_values,omitempty"`
} }
func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} }
@ -996,78 +999,79 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 1162 bytes of a gzipped FileDescriptorProto // 1170 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdb, 0x6e, 0xe3, 0x44,
0x17, 0xaf, 0xe3, 0x7c, 0x9e, 0x76, 0xfb, 0x4f, 0xa7, 0x5f, 0xae, 0xf5, 0x07, 0x15, 0x23, 0x68, 0x18, 0xae, 0xe3, 0x1c, 0xff, 0x1e, 0x48, 0xa7, 0x27, 0xd7, 0x02, 0x54, 0x8c, 0xa0, 0xd9, 0x85,
0x76, 0x61, 0x53, 0x08, 0x57, 0x48, 0x08, 0xa9, 0xdb, 0x8d, 0xda, 0x42, 0xe9, 0x4a, 0xce, 0x76, 0x4d, 0x21, 0x5c, 0x21, 0x21, 0xa4, 0x6e, 0x37, 0x6a, 0x0b, 0xa5, 0x2b, 0x39, 0xdb, 0x45, 0x42,
0x91, 0x10, 0x22, 0x72, 0x93, 0x49, 0x6b, 0xd6, 0xf1, 0x04, 0xcf, 0xb8, 0x6c, 0x6f, 0xb9, 0xe3, 0x88, 0xc8, 0x4d, 0x26, 0xad, 0x59, 0xc7, 0x13, 0x3c, 0xe3, 0xb2, 0xbd, 0xe5, 0x8e, 0x47, 0xe1,
0x51, 0xe0, 0x29, 0x78, 0x02, 0x5e, 0x80, 0x97, 0x41, 0x9e, 0x0f, 0xd7, 0xe3, 0xda, 0xad, 0xe9, 0x2d, 0x78, 0x01, 0x78, 0x01, 0x5e, 0x06, 0x79, 0x0e, 0x6e, 0xc6, 0xb5, 0x5b, 0x6f, 0x6e, 0x62,
0x4d, 0x3c, 0x33, 0xe7, 0xcc, 0xf9, 0xf8, 0x9d, 0x33, 0xbf, 0x99, 0x80, 0x7d, 0xe5, 0x2d, 0xfc, 0xcf, 0xfc, 0xe7, 0xef, 0xff, 0xfd, 0xcd, 0x04, 0xec, 0x6b, 0x6f, 0xe6, 0x1f, 0x50, 0x1c, 0xdd,
0x7d, 0x8a, 0xa3, 0x6b, 0x7f, 0x82, 0xe9, 0x3e, 0xf3, 0x83, 0x00, 0x47, 0xfd, 0x45, 0x44, 0x18, 0xf8, 0x23, 0x4c, 0x0f, 0x98, 0x1f, 0x04, 0x38, 0xea, 0xce, 0x22, 0xc2, 0x08, 0xda, 0x4c, 0x64,
0x41, 0x1b, 0x89, 0xac, 0xaf, 0x64, 0x7d, 0x21, 0xb3, 0xb7, 0xf8, 0x8e, 0xc9, 0x95, 0x17, 0x31, 0x5d, 0x25, 0xeb, 0x0a, 0x99, 0xbd, 0xcd, 0x2d, 0x46, 0xd7, 0x5e, 0xc4, 0xc4, 0xaf, 0xd0, 0xb6,
0xf1, 0x2b, 0xb4, 0xed, 0xed, 0xec, 0x3a, 0x09, 0x67, 0xfe, 0xa5, 0x14, 0x08, 0x17, 0x11, 0x0e, 0x77, 0xe6, 0xf7, 0x49, 0x38, 0xf1, 0xaf, 0xa4, 0x40, 0x84, 0x88, 0x70, 0x80, 0x3d, 0x8a, 0xd5,
0xb0, 0x47, 0xb1, 0xfa, 0x6a, 0x9b, 0x94, 0xcc, 0x0f, 0x67, 0x44, 0x0a, 0x76, 0x34, 0x01, 0x65, 0x53, 0x33, 0x52, 0x32, 0x3f, 0x9c, 0x10, 0x29, 0xd8, 0xd5, 0x04, 0x94, 0x79, 0x2c, 0xa6, 0x9a,
0x1e, 0x8b, 0xa9, 0x66, 0xef, 0x1a, 0x47, 0xd4, 0x27, 0xa1, 0xfa, 0x0a, 0x99, 0xf3, 0x57, 0x0d, 0xbf, 0x1b, 0x1c, 0x51, 0x9f, 0x84, 0xea, 0x29, 0x64, 0xce, 0xdf, 0x15, 0xd8, 0x38, 0xf3, 0x29,
0xd6, 0x4f, 0x7d, 0xca, 0x5c, 0xb1, 0x91, 0xba, 0xf8, 0x97, 0x18, 0x53, 0x86, 0x36, 0xa0, 0x11, 0x73, 0x85, 0x21, 0x75, 0xf1, 0x6f, 0x31, 0xa6, 0x0c, 0x6d, 0x42, 0x2d, 0xf0, 0xa7, 0x3e, 0xb3,
0xf8, 0x73, 0x9f, 0x59, 0xc6, 0xae, 0xd1, 0x33, 0x5d, 0x31, 0x41, 0x5b, 0xd0, 0x24, 0xb3, 0x19, 0x8c, 0x3d, 0xa3, 0x63, 0xba, 0x62, 0x81, 0xb6, 0xa1, 0x4e, 0x26, 0x13, 0x8a, 0x99, 0x55, 0xd9,
0xc5, 0xcc, 0xaa, 0xed, 0x1a, 0xbd, 0x8e, 0x2b, 0x67, 0xe8, 0x6b, 0x68, 0x51, 0x12, 0xb1, 0xf1, 0x33, 0x3a, 0x2d, 0x57, 0xae, 0xd0, 0xb7, 0xd0, 0xa0, 0x24, 0x62, 0xc3, 0xcb, 0x5b, 0xcb, 0xdc,
0xc5, 0x8d, 0x65, 0xee, 0x1a, 0xbd, 0xd5, 0xc1, 0x47, 0xfd, 0x22, 0x28, 0xfa, 0x89, 0xa7, 0x11, 0x33, 0x3a, 0x6b, 0xbd, 0x4f, 0xba, 0x79, 0x50, 0x74, 0x93, 0x48, 0x03, 0x12, 0xb1, 0x6e, 0xf2,
0x89, 0x58, 0x3f, 0xf9, 0x79, 0x71, 0xe3, 0x36, 0x29, 0xff, 0x26, 0x76, 0x67, 0x7e, 0xc0, 0x70, 0xf3, 0xfc, 0xd6, 0xad, 0x53, 0xfe, 0x4c, 0xfc, 0x4e, 0xfc, 0x80, 0xe1, 0xc8, 0xaa, 0x0a, 0xbf,
0x64, 0xd5, 0x85, 0x5d, 0x31, 0x43, 0x47, 0x00, 0xdc, 0x2e, 0x89, 0xa6, 0x38, 0xb2, 0x1a, 0xdc, 0x62, 0x85, 0x8e, 0x01, 0xb8, 0x5f, 0x12, 0x8d, 0x71, 0x64, 0xd5, 0xb8, 0xeb, 0x4e, 0x09, 0xd7,
0x74, 0xaf, 0x82, 0xe9, 0x57, 0x89, 0xbe, 0xdb, 0xa1, 0x6a, 0x88, 0xbe, 0x82, 0x15, 0x01, 0xc9, 0x2f, 0x13, 0x7d, 0xb7, 0x45, 0xd5, 0x2b, 0xfa, 0x06, 0x56, 0x04, 0x24, 0xc3, 0x11, 0x19, 0x63,
0x78, 0x42, 0xa6, 0x98, 0x5a, 0xcd, 0x5d, 0xb3, 0xb7, 0x3a, 0xd8, 0x11, 0xa6, 0x14, 0xc2, 0x23, 0x6a, 0xd5, 0xf7, 0xcc, 0xce, 0x5a, 0x6f, 0x57, 0xb8, 0x52, 0x08, 0x0f, 0x04, 0x68, 0x47, 0x64,
0x01, 0xda, 0x21, 0x99, 0x62, 0x77, 0x59, 0xa8, 0x27, 0x63, 0x8a, 0xfe, 0x0f, 0x9d, 0xd0, 0x9b, 0x8c, 0xdd, 0x65, 0xa1, 0x9e, 0xbc, 0x53, 0xf4, 0x3e, 0xb4, 0x42, 0x6f, 0x8a, 0xe9, 0xcc, 0x1b,
0x63, 0xba, 0xf0, 0x26, 0xd8, 0x6a, 0xf1, 0x08, 0x6f, 0x17, 0x9c, 0x9f, 0xa0, 0xad, 0x9c, 0x3b, 0x61, 0xab, 0xc1, 0x33, 0xbc, 0xdb, 0x70, 0x7e, 0x81, 0xa6, 0x0a, 0xee, 0xf4, 0xa0, 0x2e, 0x4a,
0x03, 0x68, 0x8a, 0xd4, 0xd0, 0x32, 0xb4, 0xce, 0xcf, 0xbe, 0x3d, 0x7b, 0xf5, 0xfd, 0x59, 0x77, 0x43, 0xcb, 0xd0, 0xb8, 0x38, 0xff, 0xfe, 0xfc, 0xe5, 0x8f, 0xe7, 0xed, 0x25, 0xd4, 0x84, 0xea,
0x09, 0xb5, 0xa1, 0x7e, 0x76, 0xf0, 0xdd, 0xb0, 0x6b, 0xa0, 0x35, 0x78, 0x72, 0x7a, 0x30, 0x7a, 0xf9, 0xe1, 0x0f, 0xfd, 0xb6, 0x81, 0xd6, 0x61, 0xf5, 0xec, 0x70, 0xf0, 0x6a, 0xe8, 0xf6, 0xcf,
0x3d, 0x76, 0x87, 0xa7, 0xc3, 0x83, 0xd1, 0xf0, 0x65, 0xb7, 0xe6, 0xbc, 0x0f, 0x9d, 0x34, 0x66, 0xfa, 0x87, 0x83, 0xfe, 0x8b, 0x76, 0xc5, 0xf9, 0x10, 0x5a, 0x69, 0xce, 0xa8, 0x01, 0xe6, 0xe1,
0xd4, 0x02, 0xf3, 0x60, 0x74, 0x28, 0xb6, 0xbc, 0x1c, 0x8e, 0x0e, 0xbb, 0x86, 0xf3, 0xbb, 0x01, 0xe0, 0x48, 0x98, 0xbc, 0xe8, 0x0f, 0x8e, 0xda, 0x86, 0xf3, 0xa7, 0x01, 0x9b, 0x7a, 0x8b, 0xe8,
0x1b, 0x7a, 0x89, 0xe8, 0x82, 0x84, 0x14, 0x27, 0x35, 0x9a, 0x90, 0x38, 0x4c, 0x6b, 0xc4, 0x27, 0x8c, 0x84, 0x14, 0x27, 0x3d, 0x1a, 0x91, 0x38, 0x4c, 0x7b, 0xc4, 0x17, 0x08, 0x41, 0x35, 0xc4,
0x08, 0x41, 0x3d, 0xc4, 0xef, 0x54, 0x85, 0xf8, 0x38, 0xd1, 0x64, 0x84, 0x79, 0x01, 0xaf, 0x8e, 0x6f, 0x55, 0x87, 0xf8, 0x7b, 0xa2, 0xc9, 0x08, 0xf3, 0x02, 0xde, 0x1d, 0xd3, 0x15, 0x0b, 0xf4,
0xe9, 0x8a, 0x09, 0xfa, 0x1c, 0xda, 0x32, 0x75, 0x6a, 0xd5, 0x77, 0xcd, 0xde, 0xf2, 0x60, 0x53, 0x25, 0x34, 0x65, 0xe9, 0xd4, 0xaa, 0xee, 0x99, 0x9d, 0xe5, 0xde, 0x96, 0x0e, 0x88, 0x8c, 0xe8,
0x07, 0x44, 0x7a, 0x74, 0x53, 0x35, 0xe7, 0x08, 0xb6, 0x8f, 0xb0, 0x8a, 0x44, 0xe0, 0xa5, 0x3a, 0xa6, 0x6a, 0xce, 0x31, 0xec, 0x1c, 0x63, 0x95, 0x89, 0xc0, 0x4b, 0x4d, 0x4c, 0x12, 0xd7, 0x9b,
0x26, 0xf1, 0xeb, 0xcd, 0x31, 0x0f, 0x26, 0xf1, 0xeb, 0xcd, 0x31, 0xb2, 0xa0, 0x25, 0xdb, 0x8d, 0x62, 0x9e, 0x4c, 0x12, 0xd7, 0x9b, 0x62, 0x64, 0x41, 0x43, 0x8e, 0x1b, 0x4f, 0xa7, 0xe6, 0xaa,
0x87, 0xd3, 0x70, 0xd5, 0xd4, 0x61, 0x60, 0xdd, 0x35, 0x24, 0xf3, 0x2a, 0xb2, 0xf4, 0x31, 0xd4, 0xa5, 0xc3, 0xc0, 0xba, 0xef, 0x48, 0xd6, 0x95, 0xe7, 0xe9, 0x53, 0xa8, 0x26, 0xc3, 0xce, 0xdd,
0x93, 0x66, 0xe7, 0x66, 0x96, 0x07, 0x48, 0x8f, 0xf3, 0x24, 0x9c, 0x11, 0x97, 0xcb, 0xf5, 0x52, 0x2c, 0xf7, 0x90, 0x9e, 0xe7, 0x69, 0x38, 0x21, 0x2e, 0x97, 0xeb, 0xad, 0x32, 0xb3, 0xad, 0x3a,
0x99, 0xf9, 0x52, 0x1d, 0x67, 0xbd, 0x1e, 0x92, 0x90, 0xe1, 0x90, 0x3d, 0x2e, 0xfe, 0x53, 0xd8, 0x99, 0x8f, 0x7a, 0x44, 0x42, 0x86, 0x43, 0xb6, 0x58, 0xfe, 0x67, 0xb0, 0x9b, 0xe3, 0x49, 0x16,
0x29, 0xb0, 0x24, 0x13, 0xd8, 0x87, 0x96, 0x0c, 0x8d, 0x5b, 0x2b, 0xc5, 0x55, 0x69, 0x39, 0x7f, 0x70, 0x00, 0x0d, 0x99, 0x1a, 0xf7, 0x56, 0x88, 0xab, 0xd2, 0x72, 0xfe, 0xa9, 0xc0, 0xe6, 0xc5,
0xd6, 0x60, 0xe3, 0x7c, 0x31, 0xf5, 0x18, 0x56, 0xa2, 0x7b, 0x82, 0xda, 0x83, 0x06, 0x27, 0x0d, 0x6c, 0xec, 0x31, 0xac, 0x44, 0x0f, 0x24, 0xb5, 0x0f, 0x35, 0x4e, 0x1a, 0x12, 0x8b, 0x75, 0xe1,
0x89, 0xc5, 0x9a, 0xb0, 0x2d, 0x98, 0xe5, 0x30, 0xf9, 0x75, 0x85, 0x1c, 0x3d, 0x83, 0xe6, 0xb5, 0x5b, 0x30, 0xcb, 0x51, 0xf2, 0xeb, 0x0a, 0x39, 0x7a, 0x0a, 0xf5, 0x1b, 0x2f, 0x88, 0x31, 0xe5,
0x17, 0xc4, 0x98, 0x72, 0x20, 0x52, 0xd4, 0xa4, 0x26, 0x67, 0x1c, 0x57, 0x6a, 0xa0, 0x6d, 0x68, 0x40, 0xa4, 0xa8, 0x49, 0x4d, 0xce, 0x38, 0xae, 0xd4, 0x40, 0x3b, 0xd0, 0x18, 0x47, 0xb7, 0xc3,
0x4d, 0xa3, 0x9b, 0x71, 0x14, 0x87, 0xfc, 0x08, 0xb6, 0xdd, 0xe6, 0x34, 0xba, 0x71, 0xe3, 0x10, 0x28, 0x0e, 0xf9, 0x27, 0xd8, 0x74, 0xeb, 0xe3, 0xe8, 0xd6, 0x8d, 0x43, 0xf4, 0x31, 0xac, 0x8e,
0x7d, 0x08, 0x4f, 0xa6, 0x3e, 0xf5, 0x2e, 0x02, 0x3c, 0xbe, 0x22, 0xe4, 0x2d, 0xe5, 0xa7, 0xb0, 0x7d, 0xea, 0x5d, 0x06, 0x78, 0x78, 0x4d, 0xc8, 0x1b, 0xca, 0xbf, 0xc2, 0xa6, 0xbb, 0x22, 0x37,
0xed, 0xae, 0xc8, 0xc5, 0xe3, 0x64, 0x0d, 0xd9, 0x49, 0x27, 0x4d, 0x22, 0xec, 0x31, 0x6c, 0x35, 0x4f, 0x92, 0x3d, 0x64, 0x27, 0x93, 0x34, 0x8a, 0xb0, 0xc7, 0xb0, 0x55, 0xe7, 0xf2, 0x74, 0x9d,
0xb9, 0x3c, 0x9d, 0x27, 0x18, 0x32, 0x7f, 0x8e, 0x49, 0xcc, 0xf8, 0xd1, 0x31, 0x5d, 0x35, 0x45, 0x60, 0xc8, 0xfc, 0x29, 0x26, 0x31, 0xe3, 0x9f, 0x8e, 0xe9, 0xaa, 0x25, 0xfa, 0x08, 0x56, 0x22,
0x1f, 0xc0, 0x4a, 0x84, 0x29, 0x66, 0x63, 0x19, 0x65, 0x9b, 0xef, 0x5c, 0xe6, 0x6b, 0x6f, 0x44, 0x4c, 0x31, 0x1b, 0xca, 0x2c, 0x9b, 0xdc, 0x72, 0x99, 0xef, 0xbd, 0x16, 0x69, 0x21, 0xa8, 0xfe,
0x58, 0x08, 0xea, 0xbf, 0x7a, 0x3e, 0xb3, 0x3a, 0x5c, 0xc4, 0xc7, 0xce, 0x31, 0x6c, 0xe6, 0xb0, 0xee, 0xf9, 0xcc, 0x6a, 0x71, 0x11, 0x7f, 0x17, 0x66, 0x31, 0xc5, 0xca, 0x0c, 0x94, 0x59, 0x4c,
0x7a, 0x2c, 0xec, 0x7f, 0x1b, 0xb0, 0xe5, 0x92, 0x20, 0xb8, 0xf0, 0x26, 0x6f, 0x2b, 0x00, 0x9f, 0xb1, 0x30, 0x73, 0x4e, 0x60, 0x2b, 0x03, 0xe7, 0xa2, 0x9d, 0xf9, 0xd7, 0x80, 0x6d, 0x97, 0x04,
0xc1, 0xa8, 0x76, 0x3f, 0x46, 0x66, 0x01, 0x46, 0x99, 0x5e, 0xaa, 0x6b, 0xbd, 0xa4, 0xa1, 0xd7, 0xc1, 0xa5, 0x37, 0x7a, 0x53, 0xa2, 0x37, 0x73, 0x30, 0x56, 0x1e, 0x86, 0xd1, 0xcc, 0x81, 0x71,
0x28, 0x47, 0xaf, 0xa9, 0xa3, 0xa7, 0xa0, 0x69, 0x65, 0xa0, 0xf9, 0x06, 0xb6, 0xef, 0xe4, 0xf3, 0x6e, 0xdc, 0xaa, 0xda, 0xb8, 0x69, 0x00, 0xd7, 0x8a, 0x01, 0xae, 0xeb, 0x00, 0x2b, 0xf4, 0x1a,
0x58, 0x70, 0xfe, 0xa8, 0xc1, 0xe6, 0x49, 0x48, 0x99, 0x17, 0x04, 0x39, 0x6c, 0xd2, 0x06, 0x34, 0x77, 0xe8, 0x39, 0xdf, 0xc1, 0xce, 0xbd, 0x7a, 0x16, 0x05, 0xe7, 0xaf, 0x0a, 0x6c, 0x9d, 0x86,
0x2a, 0x37, 0x60, 0xed, 0xbf, 0x34, 0xa0, 0xa9, 0x81, 0xab, 0x2a, 0x51, 0xcf, 0x54, 0xa2, 0x52, 0x94, 0x79, 0x41, 0x90, 0xc1, 0x26, 0x9d, 0x51, 0xa3, 0xf4, 0x8c, 0x56, 0xde, 0x65, 0x46, 0x4d,
0x53, 0x6a, 0x54, 0xd0, 0xcc, 0x51, 0x01, 0x7a, 0x0f, 0x20, 0xc2, 0x31, 0xc5, 0x63, 0x6e, 0x5c, 0x0d, 0x5c, 0xd5, 0x89, 0xea, 0x5c, 0x27, 0x4a, 0xcd, 0xad, 0xc6, 0x16, 0xf5, 0x0c, 0x5b, 0xa0,
0x80, 0xd8, 0xe1, 0x2b, 0x67, 0xf2, 0xe4, 0x2b, 0xdc, 0xdb, 0xc5, 0xb8, 0x67, 0x5b, 0xf2, 0x04, 0x0f, 0x00, 0xc4, 0xa0, 0x71, 0xe7, 0x02, 0xc4, 0x16, 0xdf, 0x39, 0x97, 0xe4, 0xa0, 0x70, 0x6f,
0xb6, 0xf2, 0x50, 0x3d, 0x16, 0xf6, 0xdf, 0x0c, 0xd8, 0x3e, 0x0f, 0xfd, 0x42, 0xe0, 0x8b, 0x9a, 0xe6, 0xe3, 0x3e, 0x37, 0xb5, 0xce, 0x29, 0x6c, 0x67, 0xa1, 0x5a, 0x14, 0xf6, 0x3f, 0x0c, 0xd8,
0xf2, 0x0e, 0x14, 0xb5, 0x02, 0x28, 0x36, 0xa0, 0xb1, 0x88, 0xa3, 0x4b, 0x2c, 0xa1, 0x15, 0x93, 0xb9, 0x08, 0xfd, 0x5c, 0xe0, 0xf3, 0x86, 0xf2, 0x1e, 0x14, 0x95, 0x1c, 0x28, 0x36, 0xa1, 0x36,
0x6c, 0x8e, 0x75, 0x2d, 0x47, 0x67, 0x0c, 0xd6, 0xdd, 0x18, 0x1e, 0x99, 0x51, 0x12, 0x75, 0x4a, 0x8b, 0xa3, 0x2b, 0x2c, 0xa1, 0x15, 0x8b, 0xf9, 0x1a, 0xab, 0x5a, 0x8d, 0xce, 0x10, 0xac, 0xfb,
0xdd, 0x1d, 0x41, 0xd3, 0xce, 0x3a, 0xac, 0x1d, 0x61, 0xf6, 0x46, 0x1c, 0x00, 0x99, 0x9e, 0x33, 0x39, 0x2c, 0x58, 0x51, 0x92, 0x75, 0xca, 0xee, 0x2d, 0xc1, 0xe4, 0xce, 0x06, 0xac, 0x1f, 0x63,
0x04, 0x94, 0x5d, 0xbc, 0xf5, 0x27, 0x97, 0x74, 0x7f, 0xea, 0x1d, 0xa3, 0xf4, 0x95, 0x96, 0xf3, 0xf6, 0x5a, 0x7c, 0x00, 0xb2, 0x3c, 0xa7, 0x0f, 0x68, 0x7e, 0xf3, 0x2e, 0x9e, 0xdc, 0xd2, 0xe3,
0x25, 0xb7, 0x7d, 0xec, 0x53, 0x46, 0xa2, 0x9b, 0xfb, 0xa0, 0xeb, 0x82, 0x39, 0xf7, 0xde, 0x49, 0xa9, 0xab, 0x8e, 0xd2, 0x57, 0x5a, 0xce, 0xd7, 0xdc, 0xf7, 0x89, 0x4f, 0x19, 0x89, 0x6e, 0x1f,
0x66, 0x4f, 0x86, 0xce, 0x11, 0x8f, 0x20, 0xdd, 0x2a, 0x23, 0xc8, 0xde, 0x93, 0x46, 0xb5, 0x7b, 0x82, 0xae, 0x0d, 0xe6, 0xd4, 0x7b, 0x2b, 0xc9, 0x3f, 0x79, 0x75, 0x8e, 0x79, 0x06, 0xa9, 0xa9,
0xf2, 0x47, 0x40, 0xaf, 0x71, 0x7a, 0x65, 0x3f, 0x70, 0xc5, 0xa8, 0x22, 0xd4, 0xf4, 0x46, 0xb3, 0xcc, 0x60, 0xfe, 0x28, 0x35, 0xca, 0x1d, 0xa5, 0x3f, 0x03, 0x7a, 0x85, 0xd3, 0x53, 0xfd, 0x91,
0xa0, 0x35, 0x09, 0xb0, 0x17, 0xc6, 0x0b, 0x59, 0x36, 0x35, 0x75, 0xf6, 0x60, 0x5d, 0xb3, 0x2e, 0x53, 0x48, 0x35, 0xa1, 0xa2, 0x0f, 0x9a, 0x05, 0x8d, 0x51, 0x80, 0xbd, 0x30, 0x9e, 0xc9, 0xb6,
0xe3, 0x4c, 0xf2, 0xa1, 0x97, 0xd2, 0x7a, 0x32, 0x1c, 0xfc, 0xd3, 0x86, 0x55, 0x75, 0xc7, 0x8a, 0xa9, 0xa5, 0xb3, 0x0f, 0x1b, 0x9a, 0x77, 0x99, 0x67, 0x52, 0x0f, 0xbd, 0x92, 0xde, 0x93, 0xd7,
0xf7, 0x12, 0xf2, 0x61, 0x25, 0xfb, 0x98, 0x40, 0x4f, 0xcb, 0x9f, 0x53, 0xb9, 0x37, 0xa1, 0xfd, 0xde, 0x7f, 0x4d, 0x58, 0x53, 0xc7, 0xb0, 0xb8, 0x52, 0x21, 0x1f, 0x56, 0xe6, 0xef, 0x1b, 0xe8,
0xac, 0x8a, 0xaa, 0x88, 0xc5, 0x59, 0xfa, 0xcc, 0x40, 0x14, 0xba, 0xf9, 0x3b, 0x1e, 0x3d, 0x2f, 0x49, 0xf1, 0x8d, 0x2b, 0x73, 0x6d, 0xb4, 0x9f, 0x96, 0x51, 0x15, 0xb9, 0x38, 0x4b, 0x5f, 0x18,
0xb6, 0x51, 0xf2, 0xa8, 0xb0, 0xfb, 0x55, 0xd5, 0x95, 0x5b, 0x74, 0xcd, 0xab, 0xaf, 0x5f, 0xcc, 0x88, 0x42, 0x3b, 0x7b, 0x0d, 0x40, 0xcf, 0xf2, 0x7d, 0x14, 0xdc, 0x3b, 0xec, 0x6e, 0x59, 0x75,
0xe8, 0x41, 0x33, 0xfa, 0x5b, 0xc0, 0xde, 0xaf, 0xac, 0x9f, 0xfa, 0xfd, 0x19, 0x9e, 0x68, 0xb7, 0x15, 0x16, 0xdd, 0xf0, 0xee, 0xeb, 0x67, 0x37, 0x7a, 0xd4, 0x8d, 0x7e, 0x5d, 0xb0, 0x0f, 0x4a,
0x12, 0x2a, 0x41, 0xab, 0xe8, 0x9a, 0xb7, 0x3f, 0xa9, 0xa4, 0x9b, 0xfa, 0x9a, 0xc3, 0xaa, 0x4e, 0xeb, 0xa7, 0x71, 0x7f, 0x85, 0x55, 0xed, 0x54, 0x42, 0x05, 0x68, 0xe5, 0xdd, 0x04, 0xec, 0xcf,
0x37, 0xa8, 0xc4, 0x40, 0x21, 0x7f, 0xdb, 0x9f, 0x56, 0x53, 0x4e, 0xdd, 0x51, 0xe8, 0xe6, 0xd9, 0x4a, 0xe9, 0xa6, 0xb1, 0xa6, 0xb0, 0xa6, 0xd3, 0x0d, 0x2a, 0x70, 0x90, 0xcb, 0xdf, 0xf6, 0xe7,
0xa0, 0xac, 0x8e, 0x25, 0xcc, 0x55, 0x56, 0xc7, 0x32, 0x92, 0x71, 0x96, 0x90, 0x07, 0x70, 0x4b, 0xe5, 0x94, 0xd3, 0x70, 0x14, 0xda, 0x59, 0x36, 0x28, 0xea, 0x63, 0x01, 0x73, 0x15, 0xf5, 0xb1,
0x06, 0x68, 0xaf, 0xb4, 0x20, 0x3a, 0x87, 0xd8, 0xbd, 0x87, 0x15, 0x53, 0x17, 0x0b, 0xf8, 0x5f, 0x88, 0x64, 0x9c, 0x25, 0xe4, 0x01, 0xdc, 0x91, 0x01, 0xda, 0x2f, 0x6c, 0x88, 0xce, 0x21, 0x76,
0xee, 0xb6, 0x44, 0x25, 0xd0, 0x14, 0x3f, 0x12, 0xec, 0xe7, 0x15, 0xb5, 0x73, 0x49, 0x49, 0x7e, 0xe7, 0x71, 0xc5, 0x34, 0xc4, 0x0c, 0xde, 0xcb, 0x9c, 0x96, 0xa8, 0x00, 0x9a, 0xfc, 0x4b, 0x82,
0xb9, 0x27, 0x29, 0x9d, 0xbc, 0xee, 0x49, 0x2a, 0x47, 0x55, 0xce, 0x12, 0xf2, 0x61, 0xd5, 0x8d, 0xfd, 0xac, 0xa4, 0x76, 0xa6, 0x28, 0xc9, 0x2f, 0x0f, 0x14, 0xa5, 0x93, 0xd7, 0x03, 0x45, 0x65,
0x43, 0xe9, 0x3a, 0x61, 0x09, 0x54, 0xb2, 0xfb, 0x2e, 0x3f, 0xd9, 0x4f, 0x2b, 0x68, 0xde, 0x9e, 0xa8, 0xca, 0x59, 0x42, 0x3e, 0xac, 0xb9, 0x71, 0x28, 0x43, 0x27, 0x2c, 0x81, 0x0a, 0xac, 0xef,
0xef, 0x17, 0xf0, 0x43, 0x5b, 0xa9, 0x5e, 0x34, 0xf9, 0xdf, 0xc9, 0x2f, 0xfe, 0x0d, 0x00, 0x00, 0xf3, 0x93, 0xfd, 0xa4, 0x84, 0xe6, 0xdd, 0xf7, 0xfd, 0x1c, 0x7e, 0x6a, 0x2a, 0xd5, 0xcb, 0x3a,
0xff, 0xff, 0xf9, 0x32, 0x44, 0xf7, 0x1f, 0x0f, 0x00, 0x00, 0xff, 0xc7, 0xf9, 0xd5, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x30, 0x80, 0xed, 0x18, 0x42, 0x0f,
0x00, 0x00,
} }

@ -18,7 +18,7 @@ package releaseutil
import ( import (
"fmt" "fmt"
"strings" "regexp"
) )
// SimpleHead defines what the structure of the head of a manifest file // SimpleHead defines what the structure of the head of a manifest file
@ -31,17 +31,18 @@ type SimpleHead struct {
} `json:"metadata,omitempty"` } `json:"metadata,omitempty"`
} }
var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*")
// SplitManifests takes a string of manifest and returns a map contains individual manifests // SplitManifests takes a string of manifest and returns a map contains individual manifests
func SplitManifests(bigfile string) map[string]string { func SplitManifests(bigfile string) map[string]string {
// This is not the best way of doing things, but it's how k8s itself does it.
// Basically, we're quickly splitting a stream of YAML documents into an // Basically, we're quickly splitting a stream of YAML documents into an
// array of YAML docs. In the current implementation, the file name is just // array of YAML docs. In the current implementation, the file name is just
// a place holder, and doesn't have any further meaning. // a place holder, and doesn't have any further meaning.
sep := "\n---\n"
tpl := "manifest-%d" tpl := "manifest-%d"
res := map[string]string{} res := map[string]string{}
tmp := strings.Split(bigfile, sep) // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly.
for i, d := range tmp { docs := sep.Split(bigfile, -1)
for i, d := range docs {
res[fmt.Sprintf(tpl, i)] = d res[fmt.Sprintf(tpl, i)] = d
} }
return res return res

@ -161,7 +161,6 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
if !filepath.IsAbs(cp) { if !filepath.IsAbs(cp) {
cp = filepath.Join(cachePath, cp) cp = filepath.Join(cachePath, cp)
} }
println("Writing to", cp)
return ioutil.WriteFile(cp, index, 0644) return ioutil.WriteFile(cp, index, 0644)
} }

@ -243,6 +243,7 @@ func loadIndex(data []byte) (*IndexFile, error) {
if err := yaml.Unmarshal(data, i); err != nil { if err := yaml.Unmarshal(data, i); err != nil {
return i, err return i, err
} }
i.SortEntries()
if i.APIVersion == "" { if i.APIVersion == "" {
// When we leave Beta, we should remove legacy support and just // When we leave Beta, we should remove legacy support and just
// return this error: // return this error:

@ -28,8 +28,9 @@ import (
) )
const ( const (
testfile = "testdata/local-index.yaml" testfile = "testdata/local-index.yaml"
testRepo = "test-repo" unorderedTestfile = "testdata/local-index-unordered.yaml"
testRepo = "test-repo"
) )
func TestIndexFile(t *testing.T) { func TestIndexFile(t *testing.T) {
@ -82,6 +83,18 @@ func TestLoadIndexFile(t *testing.T) {
verifyLocalIndex(t, i) verifyLocalIndex(t, i)
} }
func TestLoadUnorderedIndex(t *testing.T) {
b, err := ioutil.ReadFile(unorderedTestfile)
if err != nil {
t.Fatal(err)
}
i, err := loadIndex(b)
if err != nil {
t.Fatal(err)
}
verifyLocalIndex(t, i)
}
func TestMerge(t *testing.T) { func TestMerge(t *testing.T) {
ind1 := NewIndexFile() ind1 := NewIndexFile()
ind1.Add(&chart.Metadata{ ind1.Add(&chart.Metadata{

@ -17,6 +17,8 @@ limitations under the License.
package repo package repo
import "testing" import "testing"
import "io/ioutil"
import "os"
const testRepositoriesFile = "testdata/repositories.yaml" const testRepositoriesFile = "testdata/repositories.yaml"
@ -116,3 +118,100 @@ func TestNewPreV1RepositoriesFile(t *testing.T) {
t.Errorf("expected the best charts ever. Got %#v", r.Repositories) t.Errorf("expected the best charts ever. Got %#v", r.Repositories)
} }
} }
func TestRemoveRepository(t *testing.T) {
sampleRepository := NewRepoFile()
sampleRepository.Add(
&Entry{
Name: "stable",
URL: "https://example.com/stable/charts",
Cache: "stable-index.yaml",
},
&Entry{
Name: "incubator",
URL: "https://example.com/incubator",
Cache: "incubator-index.yaml",
},
)
removeRepository := "stable"
found := sampleRepository.Remove(removeRepository)
if !found {
t.Errorf("expected repository %s not found", removeRepository)
}
found = sampleRepository.Has(removeRepository)
if found {
t.Errorf("repository %s not deleted", removeRepository)
}
}
func TestUpdateRepository(t *testing.T) {
sampleRepository := NewRepoFile()
sampleRepository.Add(
&Entry{
Name: "stable",
URL: "https://example.com/stable/charts",
Cache: "stable-index.yaml",
},
&Entry{
Name: "incubator",
URL: "https://example.com/incubator",
Cache: "incubator-index.yaml",
},
)
newRepoName := "sample"
sampleRepository.Update(&Entry{Name: newRepoName,
URL: "https://example.com/sample",
Cache: "sample-index.yaml",
})
if !sampleRepository.Has(newRepoName) {
t.Errorf("expected repository %s not found", newRepoName)
}
repoCount := len(sampleRepository.Repositories)
sampleRepository.Update(&Entry{Name: newRepoName,
URL: "https://example.com/sample",
Cache: "sample-index.yaml",
})
if repoCount != len(sampleRepository.Repositories) {
t.Errorf("invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount)
}
}
func TestWriteFile(t *testing.T) {
sampleRepository := NewRepoFile()
sampleRepository.Add(
&Entry{
Name: "stable",
URL: "https://example.com/stable/charts",
Cache: "stable-index.yaml",
},
&Entry{
Name: "incubator",
URL: "https://example.com/incubator",
Cache: "incubator-index.yaml",
},
)
repoFile, err := ioutil.TempFile("", "helm-repo")
if err != nil {
t.Errorf("failed to create test-file (%v)", err)
}
defer os.Remove(repoFile.Name())
if err := sampleRepository.WriteFile(repoFile.Name(), 744); err != nil {
t.Errorf("failed to write file (%v)", err)
}
repos, err := LoadRepositoriesFile(repoFile.Name())
if err != nil {
t.Errorf("failed to load file (%v)", err)
}
for _, repo := range sampleRepository.Repositories {
if !repos.Has(repo.Name) {
t.Errorf("expected repository %s not found", repo.Name)
}
}
}

@ -0,0 +1,39 @@
apiVersion: v1
entries:
nginx:
- urls:
- https://kubernetes-charts.storage.googleapis.com/nginx-0.1.0.tgz
name: nginx
description: string
version: 0.1.0
home: https://github.com/something
digest: "sha256:1234567890abcdef"
keywords:
- popular
- web server
- proxy
- urls:
- https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz
name: nginx
description: string
version: 0.2.0
home: https://github.com/something/else
digest: "sha256:1234567890abcdef"
keywords:
- popular
- web server
- proxy
alpine:
- urls:
- https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz
- http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz
name: alpine
description: string
version: 1.0.0
home: https://github.com/something
keywords:
- linux
- alpine
- small
- sumtin
digest: "sha256:1234567890abcdef"

@ -47,25 +47,15 @@ func New(chartpath string, helmhome helmpath.Home) *Resolver {
} }
// Resolve resolves dependencies and returns a lock file with the resolution. // Resolve resolves dependencies and returns a lock file with the resolution.
func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]string) (*chartutil.RequirementsLock, error) { func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]string, d string) (*chartutil.RequirementsLock, error) {
d, err := HashReq(reqs)
if err != nil {
return nil, err
}
// Now we clone the dependencies, locking as we go. // Now we clone the dependencies, locking as we go.
locked := make([]*chartutil.Dependency, len(reqs.Dependencies)) locked := make([]*chartutil.Dependency, len(reqs.Dependencies))
missing := []string{} missing := []string{}
for i, d := range reqs.Dependencies { for i, d := range reqs.Dependencies {
if strings.HasPrefix(d.Repository, "file://") { if strings.HasPrefix(d.Repository, "file://") {
depPath, err := filepath.Abs(strings.TrimPrefix(d.Repository, "file://"))
if err != nil {
return nil, err
}
if _, err = os.Stat(depPath); os.IsNotExist(err) { if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil {
return nil, fmt.Errorf("directory %s not found", depPath)
} else if err != nil {
return nil, err return nil, err
} }
@ -136,3 +126,28 @@ func HashReq(req *chartutil.Requirements) (string, error) {
s, err := provenance.Digest(bytes.NewBuffer(data)) s, err := provenance.Digest(bytes.NewBuffer(data))
return "sha256:" + s, err return "sha256:" + s, err
} }
// GetLocalPath generates absolute local path when use
// "file://" in repository of requirements
func GetLocalPath(repo string, chartpath string) (string, error) {
var depPath string
var err error
p := strings.TrimPrefix(repo, "file://")
// root path is absolute
if strings.HasPrefix(p, "/") {
if depPath, err = filepath.Abs(p); err != nil {
return "", err
}
} else {
depPath = filepath.Join(chartpath, p)
}
if _, err = os.Stat(depPath); os.IsNotExist(err) {
return "", fmt.Errorf("directory %s not found", depPath)
} else if err != nil {
return "", err
}
return depPath, nil
}

@ -81,12 +81,12 @@ func TestResolve(t *testing.T) {
name: "repo from valid local path", name: "repo from valid local path",
req: &chartutil.Requirements{ req: &chartutil.Requirements{
Dependencies: []*chartutil.Dependency{ Dependencies: []*chartutil.Dependency{
{Name: "signtest", Repository: "file://../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"},
}, },
}, },
expect: &chartutil.RequirementsLock{ expect: &chartutil.RequirementsLock{
Dependencies: []*chartutil.Dependency{ Dependencies: []*chartutil.Dependency{
{Name: "signtest", Repository: "file://../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"},
}, },
}, },
}, },
@ -104,7 +104,12 @@ func TestResolve(t *testing.T) {
repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"}
r := New("testdata/chartpath", "testdata/helmhome") r := New("testdata/chartpath", "testdata/helmhome")
for _, tt := range tests { for _, tt := range tests {
l, err := r.Resolve(tt.req, repoNames) hash, err := HashReq(tt.req)
if err != nil {
t.Fatal(err)
}
l, err := r.Resolve(tt.req, repoNames, hash)
if err != nil { if err != nil {
if tt.err { if tt.err {
continue continue
@ -141,7 +146,7 @@ func TestResolve(t *testing.T) {
} }
func TestHashReq(t *testing.T) { func TestHashReq(t *testing.T) {
expect := "sha256:c8250374210bd909cef274be64f871bd4e376d4ecd34a1589b5abf90b68866ba" expect := "sha256:1feffe2016ca113f64159d91c1f77d6a83bcd23510b171d9264741bf9d63f741"
req := &chartutil.Requirements{ req := &chartutil.Requirements{
Dependencies: []*chartutil.Dependency{ Dependencies: []*chartutil.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"},

@ -0,0 +1,53 @@
/*
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 (
"sort"
"k8s.io/helm/pkg/proto/hapi/release"
)
// sortByHookWeight does an in-place sort of hooks by their supplied weight.
func sortByHookWeight(hooks []*release.Hook) []*release.Hook {
hs := newHookWeightSorter(hooks)
sort.Sort(hs)
return hs.hooks
}
type hookWeightSorter struct {
hooks []*release.Hook
}
func newHookWeightSorter(h []*release.Hook) *hookWeightSorter {
return &hookWeightSorter{
hooks: h,
}
}
func (hs *hookWeightSorter) Len() int { return len(hs.hooks) }
func (hs *hookWeightSorter) Swap(i, j int) {
hs.hooks[i], hs.hooks[j] = hs.hooks[j], hs.hooks[i]
}
func (hs *hookWeightSorter) Less(i, j int) bool {
if hs.hooks[i].Weight == hs.hooks[j].Weight {
return hs.hooks[i].Name < hs.hooks[j].Name
}
return hs.hooks[i].Weight < hs.hooks[j].Weight
}

@ -0,0 +1,73 @@
/*
Copyright 2017 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 (
"testing"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestHookSorter(t *testing.T) {
hooks := []*release.Hook{
{
Name: "g",
Kind: "pre-install",
Weight: 99,
},
{
Name: "f",
Kind: "pre-install",
Weight: 3,
},
{
Name: "b",
Kind: "pre-install",
Weight: -3,
},
{
Name: "e",
Kind: "pre-install",
Weight: 3,
},
{
Name: "a",
Kind: "pre-install",
Weight: -10,
},
{
Name: "c",
Kind: "pre-install",
Weight: 0,
},
{
Name: "d",
Kind: "pre-install",
Weight: 3,
},
}
res := sortByHookWeight(hooks)
got := ""
expect := "abcdefg"
for _, r := range res {
got += r.Name
}
if got != expect {
t.Errorf("Expected %q, got %q", expect, got)
}
}

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"log" "log"
"path" "path"
"strconv"
"strings" "strings"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
@ -109,12 +110,20 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
generic = append(generic, manifest{name: n, content: c, head: &sh}) generic = append(generic, manifest{name: n, content: c, head: &sh})
continue continue
} }
hws, _ := sh.Metadata.Annotations[hooks.HookWeightAnno]
hw, err := strconv.Atoi(hws)
if err != nil {
hw = 0
}
h := &release.Hook{ h := &release.Hook{
Name: sh.Metadata.Name, Name: sh.Metadata.Name,
Kind: sh.Kind, Kind: sh.Kind,
Path: n, Path: n,
Manifest: c, Manifest: c,
Events: []release.Hook_Event{}, Events: []release.Hook_Event{},
Weight: int32(hw),
} }
isHook := false isHook := false

@ -23,11 +23,61 @@ import (
// SortOrder is an ordering of Kinds. // SortOrder is an ordering of Kinds.
type SortOrder []string type SortOrder []string
// InstallOrder is the order in which manifests should be installed (by Kind) // InstallOrder is the order in which manifests should be installed (by Kind).
var InstallOrder SortOrder = []string{"Namespace", "Secret", "ConfigMap", "PersistentVolume", "PersistentVolumeClaim", "ServiceAccount", "Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "Ingress", "Job"} //
// Those occurring earlier in the list get installed before those occurring later in the list.
var InstallOrder SortOrder = []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"Secret",
"ConfigMap",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
}
// UninstallOrder is the order in which manifests should be uninstalled (by Kind) // UninstallOrder is the order in which manifests should be uninstalled (by Kind).
var UninstallOrder SortOrder = []string{"Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "ConfigMap", "Secret", "PersistentVolumeClaim", "PersistentVolume", "ServiceAccount", "Ingress", "Job", "Namespace"} //
// Those occurring earlier in the list get uninstalled before those occurring later in the list.
var UninstallOrder SortOrder = []string{
"Ingress",
"Service",
"CronJob",
"Job",
"StatefulSet",
"Deployment",
"ReplicaSet",
"ReplicationController",
"Pod",
"DaemonSet",
"RoleBinding",
"Role",
"ClusterRoleBinding",
"ClusterRole",
"ServiceAccount",
"PersistentVolumeClaim",
"PersistentVolume",
"ConfigMap",
"Secret",
"LimitRange",
"ResourceQuota",
"Namespace",
}
// sortByKind does an in-place sort of manifests by Kind. // sortByKind does an in-place sort of manifests by Kind.
// //

@ -17,6 +17,7 @@ limitations under the License.
package tiller package tiller
import ( import (
"bytes"
"testing" "testing"
util "k8s.io/helm/pkg/releaseutil" util "k8s.io/helm/pkg/releaseutil"
@ -25,14 +26,34 @@ import (
func TestKindSorter(t *testing.T) { func TestKindSorter(t *testing.T) {
manifests := []manifest{ manifests := []manifest{
{ {
name: "m", name: "i",
content: "", content: "",
head: &util.SimpleHead{Kind: "Deployment"}, head: &util.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
name: "l", name: "j",
content: "", content: "",
head: &util.SimpleHead{Kind: "Service"}, head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
},
{
name: "e",
content: "",
head: &util.SimpleHead{Kind: "ConfigMap"},
},
{
name: "u",
content: "",
head: &util.SimpleHead{Kind: "CronJob"},
},
{
name: "n",
content: "",
head: &util.SimpleHead{Kind: "DaemonSet"},
},
{
name: "r",
content: "",
head: &util.SimpleHead{Kind: "Deployment"},
}, },
{ {
name: "!", name: "!",
@ -40,35 +61,107 @@ func TestKindSorter(t *testing.T) {
head: &util.SimpleHead{Kind: "HonkyTonkSet"}, head: &util.SimpleHead{Kind: "HonkyTonkSet"},
}, },
{ {
name: "h", name: "v",
content: "",
head: &util.SimpleHead{Kind: "Ingress"},
},
{
name: "t",
content: "",
head: &util.SimpleHead{Kind: "Job"},
},
{
name: "c",
content: "",
head: &util.SimpleHead{Kind: "LimitRange"},
},
{
name: "a",
content: "", content: "",
head: &util.SimpleHead{Kind: "Namespace"}, head: &util.SimpleHead{Kind: "Namespace"},
}, },
{ {
name: "e", name: "f",
content: "", content: "",
head: &util.SimpleHead{Kind: "ConfigMap"}, head: &util.SimpleHead{Kind: "PersistentVolume"},
},
{
name: "g",
content: "",
head: &util.SimpleHead{Kind: "PersistentVolumeClaim"},
},
{
name: "o",
content: "",
head: &util.SimpleHead{Kind: "Pod"},
},
{
name: "q",
content: "",
head: &util.SimpleHead{Kind: "ReplicaSet"},
},
{
name: "p",
content: "",
head: &util.SimpleHead{Kind: "ReplicationController"},
},
{
name: "b",
content: "",
head: &util.SimpleHead{Kind: "ResourceQuota"},
},
{
name: "k",
content: "",
head: &util.SimpleHead{Kind: "Role"},
},
{
name: "l",
content: "",
head: &util.SimpleHead{Kind: "RoleBinding"},
},
{
name: "d",
content: "",
head: &util.SimpleHead{Kind: "Secret"},
},
{
name: "m",
content: "",
head: &util.SimpleHead{Kind: "Service"},
},
{
name: "h",
content: "",
head: &util.SimpleHead{Kind: "ServiceAccount"},
},
{
name: "s",
content: "",
head: &util.SimpleHead{Kind: "StatefulSet"},
}, },
} }
res := sortByKind(manifests, InstallOrder) for _, test := range []struct {
got := "" description string
expect := "helm!" order SortOrder
for _, r := range res { expected string
got += r.name }{
} {"install", InstallOrder, "abcdefghijklmnopqrstuv!"},
if got != expect { {"uninstall", UninstallOrder, "vmutsrqponlkjihgfedcba!"},
t.Errorf("Expected %q, got %q", expect, got) } {
} var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) {
expect = "lmeh!" if got, want := len(test.expected), len(manifests); got != want {
got = "" t.Fatalf("Expected %d names in order, got %d", want, got)
res = sortByKind(manifests, UninstallOrder) }
for _, r := range res { defer buf.Reset()
got += r.name for _, r := range sortByKind(manifests, test.order) {
buf.WriteString(r.name)
}
if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got)
}
})
} }
if got != expect {
t.Errorf("Expected %q, got %q", expect, got)
}
} }

@ -354,13 +354,33 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
// //
// This is skipped if the req.ResetValues flag is set, in which case the // This is skipped if the req.ResetValues flag is set, in which case the
// request values are not altered. // request values are not altered.
func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current *release.Release) { func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current *release.Release) error {
if req.ResetValues { if req.ResetValues {
// If ResetValues is set, we comletely ignore current.Config. // If ResetValues is set, we comletely ignore current.Config.
log.Print("Reset values to the chart's original version.") log.Print("Reset values to the chart's original version.")
return return nil
} }
// If req.Values is empty, but current. config is not, copy current into the
// If the ReuseValues flag is set, we always copy the old values over the new config's values.
if req.ReuseValues {
log.Print("Reusing the old release's values")
// We have to regenerate the old coalesced values:
oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config)
if err != nil {
err := fmt.Errorf("failed to rebuild old values: %s", err)
log.Print(err)
return err
}
nv, err := oldVals.YAML()
if err != nil {
return err
}
req.Chart.Values = &chart.Config{Raw: nv}
return nil
}
// If req.Values is empty, but current.Config is not, copy current into the
// request. // request.
if (req.Values == nil || req.Values.Raw == "" || req.Values.Raw == "{}\n") && if (req.Values == nil || req.Values.Raw == "" || req.Values.Raw == "{}\n") &&
current.Config != nil && current.Config != nil &&
@ -369,6 +389,7 @@ func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current
log.Printf("Copying values from %s (v%d) to new release.", current.Name, current.Version) log.Printf("Copying values from %s (v%d) to new release.", current.Name, current.Version)
req.Values = current.Config req.Values = current.Config
} }
return nil
} }
// prepareUpdate builds an updated release for an update operation. // prepareUpdate builds an updated release for an update operation.
@ -388,7 +409,9 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
} }
// If new values were not supplied in the upgrade, re-use the existing values. // If new values were not supplied in the upgrade, re-use the existing values.
s.reuseValues(req, currentRelease) if err := s.reuseValues(req, currentRelease); err != nil {
return nil, nil, err
}
// Increment revision count. This is passed to templates, and also stored on // Increment revision count. This is passed to templates, and also stored on
// the release object. // the release object.
@ -911,17 +934,18 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
} }
log.Printf("Executing %s hooks for %s", hook, name) log.Printf("Executing %s hooks for %s", hook, name)
executingHooks := []*release.Hook{}
for _, h := range hs { for _, h := range hs {
found := false
for _, e := range h.Events { for _, e := range h.Events {
if e == code { if e == code {
found = true executingHooks = append(executingHooks, h)
} }
} }
// If this doesn't implement the hook, skip it. }
if !found {
continue executingHooks = sortByHookWeight(executingHooks)
}
for _, h := range executingHooks {
b := bytes.NewBufferString(h.Manifest) b := bytes.NewBufferString(h.Manifest)
if err := kubeCli.Create(namespace, b, timeout, false); err != nil { if err := kubeCli.Create(namespace, b, timeout, false); err != nil {
@ -937,6 +961,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
} }
h.LastRun = timeconv.Now() h.LastRun = timeconv.Now()
} }
log.Printf("Hooks complete for %s %s", hook, name) log.Printf("Hooks complete for %s %s", hook, name)
return nil return nil
} }

@ -717,7 +717,7 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected description %q, got %q", edesc, got) t.Errorf("Expected description %q, got %q", edesc, got)
} }
} }
func TestUpdateReleaseResetValues(t *testing.T) { func TestUpdateRelease_ResetValues(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
rel := releaseStub() rel := releaseStub()
@ -744,6 +744,71 @@ func TestUpdateReleaseResetValues(t *testing.T) {
} }
} }
func TestUpdateRelease_ReuseValues(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)},
},
// Since reuseValues is set, this should get ignored.
Values: &chart.Config{Raw: "foo: bar\n"},
},
Values: &chart.Config{Raw: "name2: val2"},
ReuseValues: true,
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Fatalf("Failed updated: %s", err)
}
// This should have been overwritten with the old value.
expect := "name: value\n"
if res.Release.Chart.Values != nil && res.Release.Chart.Values.Raw != expect {
t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Chart.Values.Raw)
}
// This should have the newly-passed overrides.
expect = "name2: val2"
if res.Release.Config != nil && res.Release.Config.Raw != expect {
t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw)
}
}
func TestUpdateRelease_ResetReuseValues(t *testing.T) {
// This verifies that when both reset and reuse are set, reset wins.
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
ResetValues: true,
ReuseValues: true,
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Fatalf("Failed updated: %s", err)
}
// This should have been unset. Config: &chart.Config{Raw: `name: value`},
if res.Release.Config != nil && res.Release.Config.Raw != "" {
t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw)
}
}
func TestUpdateReleaseFailure(t *testing.T) { func TestUpdateReleaseFailure(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()

@ -0,0 +1,47 @@
/*
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 version represents the current version of the project.
package version // import "k8s.io/helm/pkg/version"
import "testing"
import "k8s.io/helm/pkg/proto/hapi/version"
func TestGetVersionProto(t *testing.T) {
tests := []struct {
version string
buildMetadata string
gitCommit string
gitTreeState string
expected version.Version
}{
{"", "", "", "", version.Version{SemVer: "", GitCommit: "", GitTreeState: ""}},
{"v1.0.0", "", "", "", version.Version{SemVer: "v1.0.0", GitCommit: "", GitTreeState: ""}},
{"v1.0.0", "79d5c5f7", "", "", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "", GitTreeState: ""}},
{"v1.0.0", "79d5c5f7", "0d399baec2acda578a217d1aec8d7d707c71e44d", "", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "0d399baec2acda578a217d1aec8d7d707c71e44d", GitTreeState: ""}},
{"v1.0.0", "79d5c5f7", "0d399baec2acda578a217d1aec8d7d707c71e44d", "clean", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "0d399baec2acda578a217d1aec8d7d707c71e44d", GitTreeState: "clean"}},
}
for _, tt := range tests {
Version = tt.version
BuildMetadata = tt.buildMetadata
GitCommit = tt.gitCommit
GitTreeState = tt.gitTreeState
if versionProto := GetVersionProto(); *versionProto != tt.expected {
t.Errorf("expected Semver(%s), GitCommit(%s) and GitTreeState(%s) to be %v", tt.expected, tt.gitCommit, tt.gitTreeState, *versionProto)
}
}
}

@ -62,9 +62,8 @@ verifySupported() {
fi fi
} }
# downloadFile downloads the latest binary package and also the checksum # checkLatestVersion checks the latest available version.
# for that binary. checkLatestVersion() {
downloadFile() {
# Use the GitHub API to find the latest version for this project. # Use the GitHub API to find the latest version for this project.
local latest_url="https://api.github.com/repos/kubernetes/helm/releases/latest" local latest_url="https://api.github.com/repos/kubernetes/helm/releases/latest"
if type "curl" > /dev/null; then if type "curl" > /dev/null; then
@ -72,7 +71,28 @@ downloadFile() {
elif type "wget" > /dev/null; then elif type "wget" > /dev/null; then
TAG=$(wget -q -O - $latest_url | awk '/\"tag_name\":/{gsub( /[,\"]/,"", $2); print $2}') TAG=$(wget -q -O - $latest_url | awk '/\"tag_name\":/{gsub( /[,\"]/,"", $2); print $2}')
fi fi
}
# checkHelmInstalledVersion checks which version of helm is installed and
# if it needs to be updated.
checkHelmInstalledVersion() {
if [[ -f "${HELM_INSTALL_DIR}/${PROJECT_NAME}" ]]; then
local version=$(helm version | grep '^Client' | cut -d'"' -f2)
if [[ "$version" == "$TAG" ]]; then
echo "Helm ${version} is up-to-date."
return 0
else
echo "Helm ${TAG} is available. Upgrading from version ${version}."
return 1
fi
else
return 1
fi
}
# downloadFile downloads the latest binary package and also the checksum
# for that binary.
downloadFile() {
HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz" HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz"
DOWNLOAD_URL="https://kubernetes-helm.storage.googleapis.com/$HELM_DIST" DOWNLOAD_URL="https://kubernetes-helm.storage.googleapis.com/$HELM_DIST"
CHECKSUM_URL="$DOWNLOAD_URL.sha256" CHECKSUM_URL="$DOWNLOAD_URL.sha256"
@ -140,6 +160,9 @@ set -e
initArch initArch
initOS initOS
verifySupported verifySupported
downloadFile checkLatestVersion
installFile if ! checkHelmInstalledVersion; then
downloadFile
installFile
fi
testVersion testVersion

@ -30,7 +30,6 @@ gometalinter.v1 \
--enable deadcode \ --enable deadcode \
--severity deadcode:error \ --severity deadcode:error \
--enable gofmt \ --enable gofmt \
--enable gosimple \
--enable ineffassign \ --enable ineffassign \
--enable misspell \ --enable misspell \
--enable vet \ --enable vet \

@ -1,9 +1,9 @@
MUTABLE_VERSION ?= canary MUTABLE_VERSION ?= canary
GIT_COMMIT := $(shell git rev-parse HEAD) GIT_COMMIT ?= $(shell git rev-parse HEAD)
GIT_SHA := $(shell git rev-parse --short HEAD) GIT_SHA ?= $(shell git rev-parse --short HEAD)
GIT_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null) GIT_TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null)
GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") GIT_DIRTY ?= $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean")
ifdef VERSION ifdef VERSION
DOCKER_VERSION = $(VERSION) DOCKER_VERSION = $(VERSION)

Loading…
Cancel
Save