diff --git a/.circleci/config.yml b/.circleci/config.yml index 273643b7c..e7cf729fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,3 +23,11 @@ jobs: - deploy: name: deploy command: .circleci/deploy.sh +workflows: + version: 2 + build: + jobs: + - build: + filters: + tags: + only: /.*/ diff --git a/README.md b/README.md index 21a009e2c..022afd79e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Helm is a tool for managing Kubernetes charts. Charts are packages of pre-configured Kubernetes resources. -Use Helm to... +Use Helm to: - Find and use [popular software packaged as Kubernetes charts](https://github.com/kubernetes/charts) - Share your own applications as Kubernetes charts @@ -34,10 +34,10 @@ Think of it like apt/yum/homebrew for Kubernetes. Binary downloads of the Helm client can be found at the following links: -- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-darwin-amd64.tar.gz) -- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-amd64.tar.gz) -- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-386.tar.gz) -- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-windows-amd64.tar.gz) +- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-darwin-amd64.tar.gz) +- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz) +- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-386.tar.gz) +- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-windows-amd64.tar.gz) Unpack the `helm` binary and add it to your PATH and you are good to go! macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`. @@ -53,7 +53,7 @@ Get started with the [Quick Start guide](https://docs.helm.sh/using_helm/#quicks ## Roadmap -The [Helm roadmap is currently located on the wiki](https://github.com/kubernetes/helm/wiki/Roadmap). +The [Helm roadmap uses Github milestones](https://github.com/kubernetes/helm/milestones) to track the progress of the project. ## Community, discussion, contribution, and support diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go index 735f205ae..d6a998639 100644 --- a/cmd/helm/dependency_update.go +++ b/cmd/helm/dependency_update.go @@ -96,7 +96,7 @@ func (d *dependencyUpdateCmd) run() error { Getters: getter.All(settings), } if d.verify { - man.Verify = downloader.VerifyIfPossible + man.Verify = downloader.VerifyAlways } if settings.Debug { man.Debug = true diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index 70b99ae62..9c1f25614 100644 --- a/cmd/helm/fetch.go +++ b/cmd/helm/fetch.go @@ -79,8 +79,8 @@ func newFetchCmd(out io.Writer) *cobra.Command { } if fch.version == "" && fch.devel { - debug("setting version to >0.0.0-a") - fch.version = ">0.0.0-a" + debug("setting version to >0.0.0-0") + fch.version = ">0.0.0-0" } for i := 0; i < len(args); i++ { @@ -105,7 +105,7 @@ func newFetchCmd(out io.Writer) *cobra.Command { f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") + f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") return cmd } diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 6fe08b07e..4d3a9ae83 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -239,16 +239,16 @@ func getKubeClient(context string) (*rest.Config, kubernetes.Interface, error) { // getInternalKubeClient creates a Kubernetes config and an "internal" client for a given kubeconfig context. // // Prefer the similar getKubeClient if you don't need to use such an internal client. -func getInternalKubeClient(context string) (*rest.Config, internalclientset.Interface, error) { +func getInternalKubeClient(context string) (internalclientset.Interface, error) { config, err := configForContext(context) if err != nil { - return nil, nil, err + return nil, err } client, err := internalclientset.NewForConfig(config) if err != nil { - return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err) + return nil, fmt.Errorf("could not get Kubernetes client: %s", err) } - return config, client, nil + return client, nil } // ensureHelmClient returns a new helm client impl. if h is not nil. diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 163653692..21381a0f6 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "bytes" + "encoding/json" "errors" "fmt" "io" @@ -26,6 +28,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/helm/cmd/helm/installer" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/helm/helmpath" @@ -120,6 +123,10 @@ func newInitCmd(out io.Writer) *cobra.Command { f.StringVar(&i.serviceAccount, "service-account", "", "name of service account") f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") + f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") + f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)") + f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)") + return cmd } @@ -160,31 +167,66 @@ func (i *initCmd) run() error { i.opts.ServiceAccount = i.serviceAccount i.opts.MaxHistory = i.maxHistory - if settings.Debug { - writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { - w := i.out - if !first { - // YAML starting document boundary marker - if _, err := fmt.Fprintln(w, "---"); err != nil { - return err - } + writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { + 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 { + } + 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 + } + if len(i.opts.Output) > 0 { + var body string + var err error + const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",` + if body, err = installer.DeploymentManifest(&i.opts); err != nil { + return err + } + switch i.opts.Output.String() { + case "json": + var out bytes.Buffer + jsonb, err := yaml.ToJSON([]byte(body)) + if err != nil { return err } - if _, err := fmt.Fprintln(w, "kind:", kind); err != nil { + buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1)) + buf.WriteString(tm) + // Drop the opening object delimiter ('{'). + buf.Write(jsonb[1:]) + if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { return err } - if _, err := fmt.Fprint(w, body); err != nil { + if _, err = i.out.Write(out.Bytes()); err != nil { return err } - if !last { - return nil + + return nil + case "yaml": + if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { + return err } - // YAML ending document boundary marker - _, err := fmt.Fprintln(w, "...") - return err + return nil + default: + return fmt.Errorf("unknown output format: %q", i.opts.Output) } + } + if settings.Debug { var body string var err error diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 6547e2342..4513315b7 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -27,14 +27,16 @@ import ( "github.com/ghodss/yaml" + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/pkg/api/v1" - "k8s.io/client-go/pkg/apis/extensions/v1beta1" testcore "k8s.io/client-go/testing" + "encoding/json" + "k8s.io/helm/cmd/helm/installer" "k8s.io/helm/pkg/helm/helmpath" ) @@ -303,3 +305,53 @@ func TestInitCmd_tlsOptions(t *testing.T) { } } } + +// TestInitCmd_output tests that init -o formats are unmarshal-able +func TestInitCmd_output(t *testing.T) { + // This is purely defensive in this case. + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + dbg := settings.Debug + settings.Debug = true + defer func() { + os.Remove(home) + settings.Debug = dbg + }() + fc := fake.NewSimpleClientset() + tests := []struct { + expectF func([]byte, interface{}) error + expectName string + }{ + { + json.Unmarshal, + "json", + }, + { + yaml.Unmarshal, + "yaml", + }, + } + for _, s := range tests { + var buf bytes.Buffer + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fc, + opts: installer.Options{Output: installer.OutputFormat(s.expectName)}, + namespace: v1.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Fatal(err) + } + if got := len(fc.Actions()); got != 0 { + t.Errorf("expected no server calls, got %d", got) + } + d := &v1beta1.Deployment{} + if err = s.expectF(buf.Bytes(), &d); err != nil { + t.Errorf("error unmarshalling init %s output %s %s", s.expectName, err, buf.String()) + } + } + +} diff --git a/cmd/helm/install.go b/cmd/helm/install.go index a9d308908..d3b830029 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -41,13 +41,14 @@ import ( "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/strvals" + "net/url" ) const installDesc = ` This command installs a chart archive. -The install argument must be either a relative path to a chart directory or the -name of a chart in the current working directory. +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. To override values in a chart, use either the '--values' flag and pass in a file or use the '--set' flag and pass configuration from the command line. @@ -159,8 +160,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { debug("Original chart version: %q", inst.version) if inst.version == "" && inst.devel { - debug("setting version to >0.0.0-a") - inst.version = ">0.0.0-a" + debug("setting version to >0.0.0-0") + inst.version = ">0.0.0-0" } cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring, @@ -175,7 +176,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") + f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") @@ -192,7 +193,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") + f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") return cmd } @@ -316,8 +317,9 @@ func vals(valueFiles valueFiles, values []string) ([]byte, error) { if strings.TrimSpace(filePath) == "-" { bytes, err = ioutil.ReadAll(os.Stdin) } else { - bytes, err = ioutil.ReadFile(filePath) + bytes, err = readFile(filePath) } + if err != nil { return []byte{}, err } @@ -469,3 +471,23 @@ func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { } return nil } + +//readFile load a file from the local directory or a remote file with a url. +func readFile(filePath string) ([]byte, error) { + u, _ := url.Parse(filePath) + p := getter.All(settings) + + // FIXME: maybe someone handle other protocols like ftp. + getterConstructor, err := p.ByScheme(u.Scheme) + + if err != nil { + return ioutil.ReadFile(filePath) + } else { + getter, err := getterConstructor(filePath, "", "", "") + if err != nil { + return []byte{}, err + } + data, err := getter.Get(filePath) + return data.Bytes(), err + } +} diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index c3f9eb484..c3d970726 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -19,16 +19,18 @@ package installer // import "k8s.io/helm/cmd/helm/installer" import ( "fmt" "io/ioutil" + "strings" "github.com/ghodss/yaml" + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" - "k8s.io/client-go/pkg/api/v1" - "k8s.io/client-go/pkg/apis/extensions/v1beta1" + "k8s.io/helm/pkg/chartutil" ) // Install uses Kubernetes client to install Tiller. @@ -74,13 +76,17 @@ func Upgrade(client kubernetes.Interface, opts *Options) error { // createDeployment creates the Tiller Deployment resource. func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { - obj := deployment(opts) - _, err := client.Deployments(obj.Namespace).Create(obj) + obj, err := deployment(opts) + if err != nil { + return err + } + _, err = client.Deployments(obj.Namespace).Create(obj) return err + } // deployment gets the deployment object that installs Tiller. -func deployment(opts *Options) *v1beta1.Deployment { +func deployment(opts *Options) (*v1beta1.Deployment, error) { return generateDeployment(opts) } @@ -99,7 +105,10 @@ func service(namespace string) *v1.Service { // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment // resource. func DeploymentManifest(opts *Options) (string, error) { - obj := deployment(opts) + obj, err := deployment(opts) + if err != nil { + return "", err + } buf, err := yaml.Marshal(obj) return string(buf), err } @@ -117,8 +126,28 @@ func generateLabels(labels map[string]string) map[string]string { return labels } -func generateDeployment(opts *Options) *v1beta1.Deployment { +// parseNodeSelectors parses a comma delimited list of key=values pairs into a map. +func parseNodeSelectorsInto(labels string, m map[string]string) error { + kv := strings.Split(labels, ",") + for _, v := range kv { + el := strings.Split(v, "=") + if len(el) == 2 { + m[el[0]] = el[1] + } else { + return fmt.Errorf("invalid nodeSelector label: %q", kv) + } + } + return nil +} +func generateDeployment(opts *Options) (*v1beta1.Deployment, error) { labels := generateLabels(map[string]string{"name": "tiller"}) + nodeSelectors := map[string]string{} + if len(opts.NodeSelectors) > 0 { + err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors) + if err != nil { + return nil, err + } + } d := &v1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: opts.Namespace, @@ -166,10 +195,8 @@ func generateDeployment(opts *Options) *v1beta1.Deployment { }, }, }, - HostNetwork: opts.EnableHostNetwork, - NodeSelector: map[string]string{ - "beta.kubernetes.io/os": "linux", - }, + HostNetwork: opts.EnableHostNetwork, + NodeSelector: nodeSelectors, }, }, }, @@ -205,7 +232,40 @@ func generateDeployment(opts *Options) *v1beta1.Deployment { }, }) } - return d + // if --override values were specified, ultimately convert values and deployment to maps, + // merge them and convert back to Deployment + if len(opts.Values) > 0 { + // base deployment struct + var dd v1beta1.Deployment + // get YAML from original deployment + dy, err := yaml.Marshal(d) + if err != nil { + return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err) + } + // convert deployment YAML to values + dv, err := chartutil.ReadValues(dy) + if err != nil { + return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err) + } + dm := dv.AsMap() + // merge --set values into our map + sm, err := opts.valuesMap(dm) + if err != nil { + return nil, fmt.Errorf("Error merging --set values into Deployment manifest") + } + finalY, err := yaml.Marshal(sm) + if err != nil { + return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err) + } + // convert merged values back into deployment + err = yaml.Unmarshal(finalY, &dd) + if err != nil { + return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err) + } + d = &dd + } + + return d, nil } func generateService(namespace string) *v1.Service { diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index 431e72b5b..f0ee61853 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -23,13 +23,14 @@ import ( "testing" "github.com/ghodss/yaml" + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/pkg/api/v1" - "k8s.io/client-go/pkg/apis/extensions/v1beta1" testcore "k8s.io/client-go/testing" + "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/version" ) @@ -330,7 +331,7 @@ func TestInstall_canary(t *testing.T) { func TestUpgrade(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" serviceAccount := "newServiceAccount" - existingDeployment := deployment(&Options{ + existingDeployment, _ := deployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace", ServiceAccount: "serviceAccountToReplace", @@ -371,7 +372,7 @@ func TestUpgrade(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" - existingDeployment := deployment(&Options{ + existingDeployment, _ := deployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace", UseCanary: false, @@ -419,3 +420,107 @@ func tlsTestFile(t *testing.T, path string) string { } return path } +func TestDeploymentManifest_WithNodeSelectors(t *testing.T) { + tests := []struct { + opts Options + name string + expect map[string]interface{} + }{ + { + Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller"}, + "nodeSelector app=tiller", + map[string]interface{}{"app": "tiller"}, + }, + { + Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,helm=rocks"}, + "nodeSelector app=tiller, helm=rocks", + map[string]interface{}{"app": "tiller", "helm": "rocks"}, + }, + // note: nodeSelector key and value are strings + { + Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,minCoolness=1"}, + "nodeSelector app=tiller, helm=rocks", + map[string]interface{}{"app": "tiller", "minCoolness": "1"}, + }, + } + for _, tt := range tests { + o, err := DeploymentManifest(&tt.opts) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + + var d v1beta1.Deployment + if err := yaml.Unmarshal([]byte(o), &d); err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + // Verify that environment variables in Deployment reflect the use of TLS being enabled. + got := d.Spec.Template.Spec.NodeSelector + for k, v := range tt.expect { + if got[k] != v { + t.Errorf("%s: expected nodeSelector value %q, got %q", tt.name, tt.expect, got) + } + } + } +} +func TestDeploymentManifest_WithSetValues(t *testing.T) { + tests := []struct { + opts Options + name string + expectPath string + expect interface{} + }{ + { + Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.nodeselector.app=tiller"}}, + "setValues spec.template.spec.nodeSelector.app=tiller", + "spec.template.spec.nodeSelector.app", + "tiller", + }, + { + Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.replicas=2"}}, + "setValues spec.replicas=2", + "spec.replicas", + 2, + }, + { + Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.activedeadlineseconds=120"}}, + "setValues spec.template.spec.activedeadlineseconds=120", + "spec.template.spec.activeDeadlineSeconds", + 120, + }, + } + for _, tt := range tests { + o, err := DeploymentManifest(&tt.opts) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + values, err := chartutil.ReadValues([]byte(o)) + if err != nil { + t.Errorf("Error converting Deployment manifest to Values: %s", err) + } + // path value + pv, err := values.PathValue(tt.expectPath) + if err != nil { + t.Errorf("Error retrieving path value from Deployment Values: %s", err) + } + + // convert our expected value to match the result type for comparison + ev := tt.expect + switch pvt := pv.(type) { + case float64: + floatType := reflect.TypeOf(float64(0)) + v := reflect.ValueOf(ev) + v = reflect.Indirect(v) + if !v.Type().ConvertibleTo(floatType) { + t.Fatalf("Error converting expected value %v to float64", v.Type()) + } + fv := v.Convert(floatType) + if fv.Float() != pvt { + t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv) + } + default: + if pv != tt.expect { + t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv) + } + } + } +} diff --git a/cmd/helm/installer/options.go b/cmd/helm/installer/options.go index 7567c86e8..6ea60935a 100644 --- a/cmd/helm/installer/options.go +++ b/cmd/helm/installer/options.go @@ -19,7 +19,8 @@ package installer // import "k8s.io/helm/cmd/helm/installer" import ( "fmt" - "k8s.io/client-go/pkg/api/v1" + "k8s.io/api/core/v1" + "k8s.io/helm/pkg/strvals" "k8s.io/helm/pkg/version" ) @@ -76,6 +77,15 @@ type Options struct { // // Less than or equal to zero means no limit. MaxHistory int + + // NodeSelectors determine which nodes Tiller can land on. + NodeSelectors string + + // Output dumps the Tiller manifest in the specified format (e.g. JSON) but skips Helm/Tiller installation. + Output OutputFormat + + // Set merges additional values into the Tiller Deployment manifest. + Values []string } func (opts *Options) selectImage() string { @@ -97,3 +107,42 @@ func (opts *Options) pullPolicy() v1.PullPolicy { } func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS } + +// valuesMap returns user set values in map format +func (opts *Options) valuesMap(m map[string]interface{}) (map[string]interface{}, error) { + for _, skv := range opts.Values { + if err := strvals.ParseInto(skv, m); err != nil { + return nil, err + } + } + return m, nil +} + +// OutputFormat defines valid values for init output (json, yaml) +type OutputFormat string + +// String returns the string value of the OutputFormat +func (f *OutputFormat) String() string { + return string(*f) +} + +// Type returns the string value of the OutputFormat +func (f *OutputFormat) Type() string { + return "OutputFormat" +} + +const ( + fmtJSON OutputFormat = "json" + fmtYAML OutputFormat = "yaml" +) + +// Set validates and sets the value of the OutputFormat +func (f *OutputFormat) Set(s string) error { + for _, of := range []OutputFormat{fmtJSON, fmtYAML} { + if s == string(of) { + *f = of + return nil + } + } + return fmt.Errorf("unknown output format %q", s) +} diff --git a/cmd/helm/installer/uninstall.go b/cmd/helm/installer/uninstall.go index 3d0710969..818827ddb 100644 --- a/cmd/helm/installer/uninstall.go +++ b/cmd/helm/installer/uninstall.go @@ -19,7 +19,7 @@ package installer // import "k8s.io/helm/cmd/helm/installer" import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl" diff --git a/cmd/helm/reset.go b/cmd/helm/reset.go index cc0415061..707c3d0be 100644 --- a/cmd/helm/reset.go +++ b/cmd/helm/reset.go @@ -86,7 +86,7 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command { // runReset uninstalls tiller from Kubernetes Cluster and deletes local config func (d *resetCmd) run() error { if d.kubeClient == nil { - _, c, err := getInternalKubeClient(settings.KubeContext) + c, err := getInternalKubeClient(settings.KubeContext) if err != nil { return fmt.Errorf("could not get kubernetes client: %s", err) } diff --git a/cmd/helm/search/search.go b/cmd/helm/search/search.go index 828ebeeb5..a24208a4b 100644 --- a/cmd/helm/search/search.go +++ b/cmd/helm/search/search.go @@ -30,7 +30,6 @@ import ( "strings" "github.com/Masterminds/semver" - "k8s.io/helm/pkg/repo" ) @@ -147,6 +146,8 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result { term = strings.ToLower(term) buf := []*Result{} for k, v := range i.lines { + k = strings.ToLower(k) + v = strings.ToLower(v) res := strings.Index(v, term) if score := i.calcScore(res, v); res != -1 && score < threshold { parts := strings.Split(k, verSep) // Remove version, if it is there. @@ -231,5 +232,5 @@ func (s scoreSorter) Less(a, b int) bool { func indstr(name string, ref *repo.ChartVersion) string { i := ref.Name + sep + name + "/" + ref.Name + sep + ref.Description + sep + strings.Join(ref.Keywords, " ") - return strings.ToLower(i) + return i } diff --git a/cmd/helm/search/search_test.go b/cmd/helm/search/search_test.go index db1e83a74..949cf7be7 100644 --- a/cmd/helm/search/search_test.go +++ b/cmd/helm/search/search_test.go @@ -135,7 +135,7 @@ func TestAll(t *testing.T) { func TestAddRepo_Sort(t *testing.T) { i := loadTestIndex(t, true) - sr, err := i.Search("testing/santa-maria", 100, false) + sr, err := i.Search("TESTING/SANTA-MARIA", 100, false) if err != nil { t.Fatal(err) } @@ -202,6 +202,14 @@ func TestSearchByName(t *testing.T) { {Name: "ztesting/pinta"}, }, }, + { + name: "description upper search, two results", + query: "TWO", + expect: []*Result{ + {Name: "testing/pinta"}, + {Name: "ztesting/pinta"}, + }, + }, { name: "nothing found", query: "mayflower", @@ -209,7 +217,7 @@ func TestSearchByName(t *testing.T) { }, { name: "regexp, one result", - query: "th[ref]*", + query: "Th[ref]*", expect: []*Result{ {Name: "testing/santa-maria"}, }, diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 4b852198a..6ecd98608 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -98,8 +98,8 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { } if upgrade.version == "" && upgrade.devel { - debug("setting version to >0.0.0-a") - upgrade.version = ">0.0.0-a" + debug("setting version to >0.0.0-0") + upgrade.version = ">0.0.0-0" } upgrade.release = args[0] @@ -111,7 +111,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") + f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed") @@ -131,7 +131,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") + f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.MarkDeprecated("disable-hooks", "use --no-hooks instead") diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 2a4cf066e..f18ce6c3d 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -57,6 +57,7 @@ const ( storageMemory = "memory" storageConfigMap = "configmap" + storageSecret = "secret" probeAddr = ":44135" traceAddr = ":44136" @@ -68,7 +69,7 @@ const ( var ( grpcAddr = flag.String("listen", ":44134", "address:port to listen on") enableTracing = flag.Bool("trace", false, "enable rpc tracing") - store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap' or 'memory'") + store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap', 'memory', or 'secret'") remoteReleaseModules = flag.Bool("experimental-release", false, "enable experimental release modules") tlsEnable = flag.Bool("tls", tlsEnableEnvVarDefault(), "enable TLS") tlsVerify = flag.Bool("tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate") @@ -117,6 +118,12 @@ func start() { env.Releases = storage.Init(cfgmaps) env.Releases.Log = newLogger("storage").Printf + case storageSecret: + secrets := driver.NewSecrets(clientset.Core().Secrets(namespace())) + secrets.Log = newLogger("storage/driver").Printf + + env.Releases = storage.Init(secrets) + env.Releases.Log = newLogger("storage").Printf } if *maxHistory > 0 { diff --git a/docs/chart_best_practices/templates.md b/docs/chart_best_practices/templates.md index db1259246..c9995ea0a 100644 --- a/docs/chart_best_practices/templates.md +++ b/docs/chart_best_practices/templates.md @@ -36,6 +36,7 @@ Incorrect: {{/* ... */}} {{ end -}} ``` +It is highly recommended that new charts are created via `helm create` command as the template names are automatically defined as per this best practice. ## Formatting Templates diff --git a/docs/chart_template_guide/variables.md b/docs/chart_template_guide/variables.md index 34b6fa891..b55e6e422 100644 --- a/docs/chart_template_guide/variables.md +++ b/docs/chart_template_guide/variables.md @@ -96,6 +96,34 @@ data: food: "pizza" ``` -Variables are not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block. +Variables are normally not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block. + +However, there is one variable that is always global - `$` - this +variable will always point to the root context. This can be very +useful when you are looping in a range need to know the chart's release +name. + +An example illustrating this: +```yaml +{{- range .Values.tlsSecrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .name }} + labels: + # Many helm templates would use `.` below, but that will not work, + # however `$` will work here + app: {{ template "fullname" $ }} + # I cannot reference .Chart.Name, but I can do $.Chart.Name + chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}" + release: "{{ $.Release.Name }}" + heritage: "{{ $.Release.Service }}" +type: kubernetes.io/tls +data: + tls.crt: {{ .certificate }} + tls.key: {{ .key }} +--- +{{- end }} +``` So far we have looked at just one template declared in just one file. But one of the powerful features of the Helm template language is its ability to declare multiple templates and use them together. We'll turn to that in the next section. diff --git a/docs/chart_template_guide/yaml_techniques.md b/docs/chart_template_guide/yaml_techniques.md index ccada54c0..44c41f903 100644 --- a/docs/chart_template_guide/yaml_techniques.md +++ b/docs/chart_template_guide/yaml_techniques.md @@ -319,7 +319,7 @@ refer to that value by reference. YAML refers to this as "anchoring": ```yaml coffee: "yes, please" favorite: &favoriteCoffee "Cappucino" -coffess: +coffees: - Latte - *favoriteCoffee - Espresso @@ -339,7 +339,7 @@ YAML would be: ```YAML coffee: yes, please favorite: Cappucino -coffess: +coffees: - Latte - Cappucino - Espresso diff --git a/docs/charts_tips_and_tricks.md b/docs/charts_tips_and_tricks.md index 0ed1ab83e..885c30085 100644 --- a/docs/charts_tips_and_tricks.md +++ b/docs/charts_tips_and_tricks.md @@ -96,6 +96,35 @@ For example: The above will render the template when .Values.foo is defined, but will fail to render and exit when .Values.foo is undefined. +## Creating Image Pull Secrets +Image pull secrets are essentially a combination of _registry_, _username_, and _password_. You may need them in an application you are deploying, but to create them requires running _base64_ a couple of times. We can write a helper template to compose the Docker configuration file for use as the Secret's payload. Here is an example: + +First, assume that the credentials are defined in the `values.yaml` file like so: +``` +imageCredentials: + registry: quay.io + username: someone + password: sillyness +``` + +We then define our helper template as follows: +``` +{{- define "imagePullSecret" }} +{{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.imageCredentials.registry (printf "%s:%s" .Values.imageCredentials.username .Values.imageCredentials.password | b64enc) | b64enc }} +{{- end }} +``` + +Finally, we use the helper template in a larger template to create the Secret manifest: +``` +apiVersion: v1 +kind: Secret +metadata: + name: myregistrykey +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ template "imagePullSecret" . }} +``` + ## Automatically Roll Deployments When ConfigMaps or Secrets change Often times configmaps or secrets are injected as configuration diff --git a/docs/developers.md b/docs/developers.md index e0aeb374a..9720b99f0 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -21,7 +21,9 @@ We use Make to build our programs. The simplest way to get started is: $ make bootstrap build ``` -NOTE: This will fail if not run from the path: `$GOPATH/src/k8s.io/helm`. +NOTE: This will fail if not running from the path `$GOPATH/src/k8s.io/helm`. The +directory `k8s.io` should not be a symlink or `build` will not find the relevant +packages. This will build both Helm and Tiller. `make bootstrap` will attempt to install certain tools if they are missing. diff --git a/docs/examples/alpine/templates/_helpers.tpl b/docs/examples/alpine/templates/_helpers.tpl index f0d83d2ed..3e9c25bed 100644 --- a/docs/examples/alpine/templates/_helpers.tpl +++ b/docs/examples/alpine/templates/_helpers.tpl @@ -2,7 +2,7 @@ {{/* Expand the name of the chart. */}} -{{- define "name" -}} +{{- define "alpine.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} @@ -10,7 +10,7 @@ Expand the name of the chart. Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} -{{- define "fullname" -}} +{{- define "alpine.fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} diff --git a/docs/examples/alpine/templates/alpine-pod.yaml b/docs/examples/alpine/templates/alpine-pod.yaml index 14995675e..da9caef78 100644 --- a/docs/examples/alpine/templates/alpine-pod.yaml +++ b/docs/examples/alpine/templates/alpine-pod.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: {{ template "fullname" . }} + name: {{ template "alpine.fullname" . }} labels: # The "heritage" label is used to track which tool deployed a given chart. # It is useful for admins who want to see what releases a particular tool @@ -12,7 +12,7 @@ metadata: release: {{ .Release.Name }} # This makes it easy to audit chart usage. chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "name" . }} + app: {{ template "alpine.name" . }} spec: # This shows how to use a simple value. This will look for a passed-in value called restartPolicy. restartPolicy: {{ .Values.restartPolicy }} diff --git a/docs/examples/nginx/templates/_helpers.tpl b/docs/examples/nginx/templates/_helpers.tpl index f0d83d2ed..2ec6ba757 100644 --- a/docs/examples/nginx/templates/_helpers.tpl +++ b/docs/examples/nginx/templates/_helpers.tpl @@ -2,7 +2,7 @@ {{/* Expand the name of the chart. */}} -{{- define "name" -}} +{{- define "nginx.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} @@ -10,7 +10,7 @@ Expand the name of the chart. Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} -{{- define "fullname" -}} +{{- define "nginx.fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} diff --git a/docs/examples/nginx/templates/configmap.yaml b/docs/examples/nginx/templates/configmap.yaml index 641e62ea4..b90d6c0c7 100644 --- a/docs/examples/nginx/templates/configmap.yaml +++ b/docs/examples/nginx/templates/configmap.yaml @@ -2,12 +2,12 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} labels: heritage: {{ .Release.Service }} release: {{ .Release.Name }} chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} data: # When the config map is mounted as a volume, these will be created as files. index.html: {{ .Values.index | quote }} diff --git a/docs/examples/nginx/templates/deployment.yaml b/docs/examples/nginx/templates/deployment.yaml index ca929c278..5fa2633ea 100644 --- a/docs/examples/nginx/templates/deployment.yaml +++ b/docs/examples/nginx/templates/deployment.yaml @@ -4,7 +4,7 @@ metadata: # This uses a "fullname" template (see _helpers) # Basing names on .Release.Name means that the same chart can be installed # multiple times into the same namespace. - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} labels: # The "heritage" label is used to track which tool deployed a given chart. # It is useful for admins who want to see what releases a particular tool @@ -15,7 +15,7 @@ metadata: release: {{ .Release.Name }} # This makes it easy to audit chart usage. chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} spec: replicas: {{ .Values.replicaCount }} template: @@ -26,11 +26,11 @@ spec: {{ toYaml .Values.podAnnotations | indent 8 }} {{- end }} labels: - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} release: {{ .Release.Name }} spec: containers: - - name: {{ template "name" . }} + - name: {{ template "nginx.name" . }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: @@ -54,4 +54,4 @@ spec: volumes: - name: wwwdata-volume configMap: - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} diff --git a/docs/examples/nginx/templates/post-install-job.yaml b/docs/examples/nginx/templates/post-install-job.yaml index 06e7024f2..9ec90cd0a 100644 --- a/docs/examples/nginx/templates/post-install-job.yaml +++ b/docs/examples/nginx/templates/post-install-job.yaml @@ -1,7 +1,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} labels: # The "heritage" label is used to track which tool deployed a given chart. # It is useful for admins who want to see what releases a particular tool @@ -12,7 +12,7 @@ metadata: release: {{ .Release.Name }} # This makes it easy to audit chart usage. chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} annotations: # This is what defines this resource as a hook. Without this line, the # job is considered part of the release. @@ -20,10 +20,10 @@ metadata: spec: template: metadata: - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} labels: release: {{ .Release.Name }} - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} spec: # This shows how to use a simple value. This will look for a passed-in value # called restartPolicy. If it is not found, it will use the default value. diff --git a/docs/examples/nginx/templates/pre-install-secret.yaml b/docs/examples/nginx/templates/pre-install-secret.yaml index 405f4e531..6392f9684 100644 --- a/docs/examples/nginx/templates/pre-install-secret.yaml +++ b/docs/examples/nginx/templates/pre-install-secret.yaml @@ -3,12 +3,12 @@ apiVersion: v1 kind: Secret metadata: - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} labels: heritage: {{ .Release.Service }} release: {{ .Release.Name }} chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} # This declares the resource to be a hook. By convention, we also name the # file "pre-install-XXX.yaml", but Helm itself doesn't care about file names. annotations: diff --git a/docs/examples/nginx/templates/service-test.yaml b/docs/examples/nginx/templates/service-test.yaml index 107b19a79..3913ead9c 100644 --- a/docs/examples/nginx/templates/service-test.yaml +++ b/docs/examples/nginx/templates/service-test.yaml @@ -1,12 +1,12 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ template "fullname" . }}-service-test" + name: "{{ template "nginx.fullname" . }}-service-test" labels: heritage: {{ .Release.Service }} release: {{ .Release.Name }} chart: {{ .Chart.Name }}-{{ .Chart.Version }} - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} annotations: "helm.sh/hook": test-success spec: @@ -14,5 +14,5 @@ spec: - name: curl image: radial/busyboxplus:curl command: ['curl'] - args: ['{{ template "fullname" . }}:{{ .Values.service.port }}'] + args: ['{{ template "nginx.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never diff --git a/docs/examples/nginx/templates/service.yaml b/docs/examples/nginx/templates/service.yaml index bad29b14e..1481e34f0 100644 --- a/docs/examples/nginx/templates/service.yaml +++ b/docs/examples/nginx/templates/service.yaml @@ -6,11 +6,11 @@ metadata: {{ toYaml .Values.service.annotations | indent 4 }} {{- end }} labels: - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} chart: {{ .Chart.Name }}-{{ .Chart.Version }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} - name: {{ template "fullname" . }} + name: {{ template "nginx.fullname" . }} spec: # Provides options for the service so chart users have the full choice type: "{{ .Values.service.type }}" @@ -35,5 +35,5 @@ spec: nodePort: {{ .Values.service.nodePort }} {{- end }} selector: - app: {{ template "name" . }} + app: {{ template "nginx.name" . }} release: {{ .Release.Name }} diff --git a/docs/helm/helm_fetch.md b/docs/helm/helm_fetch.md index 56996774e..6bb3279c4 100644 --- a/docs/helm/helm_fetch.md +++ b/docs/helm/helm_fetch.md @@ -30,7 +30,7 @@ helm fetch [flags] [chart URL | repo/chartname] [...] --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --cert-file string identify HTTPS client using this SSL certificate file -d, --destination string location to write the chart. If this and tardir are specified, tardir is appended to this (default ".") - --devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. --key-file string identify HTTPS client using this SSL key file --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") --prov fetch the provenance file, but don't perform verification diff --git a/docs/helm/helm_init.md b/docs/helm/helm_init.md index 2d224c7f1..0085f6cde 100644 --- a/docs/helm/helm_init.md +++ b/docs/helm/helm_init.md @@ -39,6 +39,9 @@ helm init --history-max int limit the maximum number of revisions saved per release. Use 0 for no limit. --local-repo-url string URL for local repository (default "http://127.0.0.1:8879/charts") --net-host install Tiller with net=host + --node-selectors string labels to specify the node on which Tiller is installed (app=tiller,helm=rocks) + -o, --output OutputFormat skip installation and output Tiller's manifest in specified format (json or yaml) + --override stringArray override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2) --service-account string name of service account --skip-refresh do not refresh (download) the local repository cache --stable-repo-url string URL for stable repository (default "https://kubernetes-charts.storage.googleapis.com") @@ -64,4 +67,4 @@ helm init ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 10-Aug-2017 +###### Auto generated by spf13/cobra on 10-Oct-2017 diff --git a/docs/helm/helm_install.md b/docs/helm/helm_install.md index 8858f534d..74fe19759 100644 --- a/docs/helm/helm_install.md +++ b/docs/helm/helm_install.md @@ -8,8 +8,8 @@ install a chart archive This command installs a chart archive. -The install argument must be either a relative path to a chart directory or the -name of a chart in the current working directory. +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. To override values in a chart, use either the '--values' flag and pass in a file or use the '--set' flag and pass configuration from the command line. @@ -70,7 +70,7 @@ helm install [CHART] ``` --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --cert-file string identify HTTPS client using this SSL certificate file - --devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. --dry-run simulate an install --key-file string identify HTTPS client using this SSL key file --keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg") @@ -87,7 +87,7 @@ helm install [CHART] --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") --tls-verify enable TLS for request and verify remote - -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) + -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default []) --verify verify the package before installing it --version string specify the exact chart version to install. If this is not specified, the latest version is installed --wait 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 diff --git a/docs/helm/helm_upgrade.md b/docs/helm/helm_upgrade.md index fdf95854f..07927523f 100644 --- a/docs/helm/helm_upgrade.md +++ b/docs/helm/helm_upgrade.md @@ -38,7 +38,7 @@ helm upgrade [RELEASE] [CHART] ``` --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --cert-file string identify HTTPS client using this SSL certificate file - --devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. --dry-run simulate an upgrade --force force resource update through delete/recreate if needed -i, --install if a release by this name doesn't already exist, run an install @@ -57,7 +57,7 @@ helm upgrade [RELEASE] [CHART] --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") --tls-verify enable TLS for request and verify remote - -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) + -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default []) --verify verify the provenance of the chart before upgrading --version string specify the exact chart version to use. If this is not specified, the latest version is used --wait 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 diff --git a/docs/install.md b/docs/install.md index af5d57a7b..ea51afd89 100755 --- a/docs/install.md +++ b/docs/install.md @@ -197,6 +197,144 @@ Tiller can then be re-installed from the client with: $ helm init ``` +## Advanced Usage + +`helm init` provides additional flags for modifying Tiller's deployment +manifest before it is installed. + +### Using `--node-selectors` + +The `--node-selectors` flag allows us to specify the node labels required +for scheduling the Tiller pod. + +The example below will create the specified label under the nodeSelector +property. + +``` +helm init --node-selectors "beta.kubernetes.io/os"="linux" +``` + +The installed deployment manifest will contain our node selector label. + +``` +... +spec: + template: + spec: + nodeSelector: + beta.kubernetes.io/os: linux +... +``` + + +### Using `--override` + +`--override` allows you to specify properties of Tiller's +deployment manifest. Unlike the `--set` command used elsewhere in Helm, +`helm init --override` manipulates the specified properties of the final +manifest (there is no "values" file). Therefore you may specify any valid +value for any valid property in the deployment manifest. + +#### Override annotation + +In the example below we use `--override` to add the revision property and set +its value to 1. + +``` +helm init --set metadata.annotations."deployment\.kubernetes\.io/revision"="1" +``` +Output: + +``` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" +... +``` + +#### Override affinity + +In the example below we set properties for node affinity. Multiple +`--override` commands may be combined to modify different properties of the +same list item. + +``` +helm init --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight"="1" --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].key"="e2e-az-name" +``` + +The specified properties are combined into the +"preferredDuringSchedulingIgnoredDuringExecution" property's first +list item. + +``` +... +spec: + strategy: {} + template: + ... + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: e2e-az-name + operator: "" + weight: 1 +... +``` + +### Using `--output` + +The `--output` flag allows us skip the installation of Tiller's deployment +manifest and simply output the deployment manifest to stdout in either +JSON or YAML format. The output may then be modified with tools like `jq` +and installed manually with `kubectl`. + +In the example below we execute `helm init` with the `--output json` flag. + +``` +helm init --output json +``` + +The Tiller installation is skipped and the manifest is output to stdout +in JSON format. + +``` +"apiVersion": "extensions/v1beta1", +"kind": "Deployment", +"metadata": { + "creationTimestamp": null, + "labels": { + "app": "helm", + "name": "tiller" + }, + "name": "tiller-deploy", + "namespace": "kube-system" +}, +... +``` + +### Storage backends +By default, `tiller` stores release information in `ConfigMaps` in the namespace +where it is running. As of Helm 2.7.0, there is now a beta storage backend that +uses `Secrets` for storing release information. This was added for additional +security in protecting charts in conjunction with the release of `Secret` +encryption in Kubernetes. + +To enable the secrets backend, you'll need to init Tiller with the following +options: + +```shell +helm init --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}' +``` + +Currently, if you want to switch from the default backend to the secrets +backend, you'll have to do the migration for this on your own. When this backend +graduates from beta, there will be a more official path of migration + ## Conclusion In most cases, installation is as simple as getting a pre-built `helm` binary diff --git a/docs/kubernetes_distros.md b/docs/kubernetes_distros.md index bea1e8075..620ef72ac 100644 --- a/docs/kubernetes_distros.md +++ b/docs/kubernetes_distros.md @@ -27,7 +27,7 @@ Kubernetes bootstrapped with `kubeadm` is known to work on the following Linux distributions: - Ubuntu 16.04 -- CAN SOMEONE CONFIRM ON FEDORA? +- Fedora release 25 Some versions of Helm (v2.0.0-beta2) require you to `export KUBECONFIG=/etc/kubernetes/admin.conf` or create a `~/.kube/config`. @@ -35,3 +35,7 @@ or create a `~/.kube/config`. ## Container Linux by CoreOS Helm requires that kubelet have access to a copy of the `socat` program to proxy connections to the Tiller API. On Container Linux the Kubelet runs inside of a [hyperkube](https://github.com/kubernetes/kubernetes/tree/master/cluster/images/hyperkube) container image that has socat. So, even though Container Linux doesn't ship `socat` the container filesystem running kubelet does have socat. To learn more read the [Kubelet Wrapper](https://coreos.com/kubernetes/docs/latest/kubelet-wrapper.html) docs. + +## Openshift + +Helm works straightforward on OpenShift Online, OpenShift Dedicated, OpenShift Container Platform (version >= 3.6) or OpenShift Origin (version >= 3.6). To learn more read [this blog](https://blog.openshift.com/getting-started-helm-openshift/) post. diff --git a/docs/man/man1/helm_install.1 b/docs/man/man1/helm_install.1 index 8fe99acb3..a19b3adf4 100644 --- a/docs/man/man1/helm_install.1 +++ b/docs/man/man1/helm_install.1 @@ -18,8 +18,8 @@ helm\-install \- install a chart archive This command installs a chart archive. .PP -The install argument must be either a relative path to a chart directory or the -name of a chart in the current working directory. +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. .PP To override values in a chart, use either the '\-\-values' flag and pass in a file @@ -192,7 +192,7 @@ charts in a repository, use 'helm search'. .PP \fB\-f\fP, \fB\-\-values\fP=[] - specify values in a YAML file (can specify multiple) + specify values in a YAML file or a URL(can specify multiple) .PP \fB\-\-verify\fP[=false] diff --git a/docs/man/man1/helm_upgrade.1 b/docs/man/man1/helm_upgrade.1 index 5d5e919f6..24bba7c85 100644 --- a/docs/man/man1/helm_upgrade.1 +++ b/docs/man/man1/helm_upgrade.1 @@ -143,7 +143,7 @@ $ helm upgrade \-\-set foo=bar \-\-set foo=newbar redis ./redis .PP \fB\-f\fP, \fB\-\-values\fP=[] - specify values in a YAML file (can specify multiple) + specify values in a YAML file or a URL(can specify multiple) .PP \fB\-\-verify\fP[=false] diff --git a/glide.lock b/glide.lock index 3db9919b2..cb17642e4 100644 --- a/glide.lock +++ b/glide.lock @@ -1,14 +1,22 @@ -hash: 91eba16992e639203a273b247807bb990901cc0ef7e744991722106fc9db0956 -updated: 2017-09-18T15:49:23.687863166-04:00 +hash: 650f1d4cd9e9dc5ba76480a5465923ce1bbd11b8fa956b644aaf975e8f7e1f33 +updated: 2017-10-12T13:08:50.435765-07:00 imports: - name: cloud.google.com/go version: 3b1ae45394a234c385be014e9a488f2bb6eef821 + subpackages: + - compute/metadata + - internal - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 - name: github.com/asaskevich/govalidator version: 7664702784775e51966f0885f5cd27435916517b - name: github.com/Azure/go-autorest - version: d7c034a8af24eda120dd6460bfcd6d9ed14e43ca + version: 58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d + subpackages: + - autorest + - autorest/adal + - autorest/azure + - autorest/date - name: github.com/beorn7/perks version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 subpackages: @@ -22,42 +30,59 @@ imports: subpackages: - md2man - name: github.com/davecgh/go-spew - version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + version: 782f4967f2dc4564575ca782fe2d04090b5faca8 subpackages: - spew - name: github.com/dgrijalva/jwt-go version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 - name: github.com/docker/distribution - version: 03efb43768979f4d2ea5187bef995656441829e5 + version: edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c subpackages: - - digest + - digestset - reference -- name: github.com/docker/engine-api - version: dea108d3aa0c67d7162a3fd8aa65f38a430019fd - subpackages: +- name: github.com/docker/docker + version: 4f3616fb1c112e206b88cb7a9922bf49067a7756 + subpackages: + - api + - api/types + - api/types/blkiodev + - api/types/container + - api/types/events + - api/types/filters + - api/types/image + - api/types/mount + - api/types/network + - api/types/registry + - api/types/strslice + - api/types/swarm + - api/types/swarm/runtime + - api/types/time + - api/types/versions + - api/types/volume - client - - client/transport - - client/transport/cancellable - - types - - types/blkiodev - - types/container - - types/filters - - types/network - - types/reference - - types/registry - - types/strslice - - types/time - - types/versions + - pkg/ioutils + - pkg/jsonlog + - pkg/jsonmessage + - pkg/longpath + - pkg/mount + - pkg/stdcopy + - pkg/symlink + - pkg/system + - pkg/term + - pkg/term/windows + - pkg/tlsconfig - name: github.com/docker/go-connections - version: f549a9393d05688dff0992ef3efd8bbe6c628aeb + version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d subpackages: - nat - sockets - tlsconfig - name: github.com/docker/go-units - version: e30f1e79f3cd72542f2026ceec18d3bd67ab859c + version: 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 - name: github.com/docker/spdystream version: 449fdfce4d962303d702fec724ef0ad181c92528 + subpackages: + - spdy - name: github.com/emicklei/go-restful version: ff4f55a206334ef123e4f79bbf348980da81ca46 subpackages: @@ -65,31 +90,19 @@ imports: - name: github.com/emicklei/go-restful-swagger12 version: dcef7f55730566d41eae5db10e7d6981829720f6 - name: github.com/evanphx/json-patch - version: ba18e35c5c1b36ef6334cad706eb681153d2d379 + version: 944e07253867aacae43c04b2e6a239005443f33a - name: github.com/exponent-io/jsonpath version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5 -- name: github.com/facebookgo/atomicfile - version: 2de1f203e7d5e386a6833233882782932729f27e -- name: github.com/facebookgo/symwalk - version: 42004b9f322246749dd73ad71008b1f3160c0052 - name: github.com/fatih/camelcase version: f6a740d52f961c60348ebb109adde9f4635d7540 - name: github.com/ghodss/yaml version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee -- name: github.com/go-openapi/analysis - version: b44dc874b601d9e4e2f6e19140e794ba24bead3b -- name: github.com/go-openapi/errors - version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc - name: github.com/go-openapi/jsonpointer version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 - name: github.com/go-openapi/jsonreference version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 -- name: github.com/go-openapi/loads - version: 18441dfa706d924a39a030ee2c3b1d8d81917b38 - name: github.com/go-openapi/spec version: 6aced65f8501fe1217321abf0749d354824ba2ff -- name: github.com/go-openapi/strfmt - version: d65c7fdb29eca313476e529628176fe17e58c488 - name: github.com/go-openapi/swag version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 - name: github.com/gobwas/glob @@ -105,31 +118,8 @@ imports: - name: github.com/gogo/protobuf version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 subpackages: - - gogoproto - - plugin/compare - - plugin/defaultcheck - - plugin/description - - plugin/embedcheck - - plugin/enumstringer - - plugin/equal - - plugin/face - - plugin/gostring - - plugin/marshalto - - plugin/oneofcheck - - plugin/populate - - plugin/size - - plugin/stringer - - plugin/testgen - - plugin/union - - plugin/unmarshal - proto - - protoc-gen-gogo/descriptor - - protoc-gen-gogo/generator - - protoc-gen-gogo/grpc - - protoc-gen-gogo/plugin - sortkeys - - vanity - - vanity/command - name: github.com/golang/glog version: 44145f04b68cf362d9c4df2182967c2275eaefed - name: github.com/golang/groupcache @@ -137,30 +127,57 @@ imports: subpackages: - lru - name: github.com/golang/protobuf - version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef + version: 4bd1920723d7b7c925de087aa32e2187708897f7 subpackages: - proto + - ptypes - ptypes/any + - ptypes/duration - ptypes/timestamp +- name: github.com/google/btree + version: 7d79101e329e5a3adf994758c578dab82b90c017 - name: github.com/google/gofuzz - version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5 + version: 44d81051d367757e1c7c6a5a86423ece9afcf63c +- name: github.com/googleapis/gnostic + version: 0c5108395e2debce0d731cf0287ddf7242066aba + subpackages: + - OpenAPIv2 + - compiler + - extensions +- name: github.com/gophercloud/gophercloud + version: 2bf16b94fdd9b01557c4d076e567fe5cbbe5a961 + subpackages: + - openstack + - openstack/identity/v2/tenants + - openstack/identity/v2/tokens + - openstack/identity/v3/tokens + - openstack/utils + - pagination - name: github.com/gosuri/uitable version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 subpackages: - util/strutil - util/wordwrap +- name: github.com/gregjones/httpcache + version: 787624de3eb7bd915c329cba748687a3b22666a6 + subpackages: + - diskcache - name: github.com/grpc-ecosystem/go-grpc-prometheus version: 0c1b191dbfe51efdabe3c14b9f6f3b96429e0722 - name: github.com/hashicorp/golang-lru version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 + subpackages: + - simplelru - name: github.com/howeyc/gopass - version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d + version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 - name: github.com/huandu/xstrings version: 3959339b333561bf62a38b424fd41517c2c90f40 - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/json-iterator/go + version: 36b14963da70d11297d313183d7e6388c8510e1e - name: github.com/juju/ratelimit version: 5b9ff866471762aa2ab2dced63c9fb6f53921342 - name: github.com/mailru/easyjson @@ -172,7 +189,7 @@ imports: - name: github.com/Masterminds/semver version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd - name: github.com/Masterminds/sprig - version: 4c164950cd0a8d3724ddb78982e2c56dc7f47112 + version: efda631a76d70875162cdc25ffa0d0164bf69758 - name: github.com/Masterminds/vcs version: 3084677c2c188840777bff30054f2b553729d329 - name: github.com/mattn/go-runewidth @@ -181,12 +198,19 @@ imports: version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a subpackages: - pbutil -- name: github.com/mitchellh/mapstructure - version: 740c764bc6149d3f1806231418adb9f52c11bcbf - name: github.com/naoina/go-stringutil version: 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b +- name: github.com/opencontainers/go-digest + version: a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb +- name: github.com/opencontainers/image-spec + version: 372ad780f63454fbbbbcc7cf80e5b90245c13e13 + subpackages: + - specs-go + - specs-go/v1 - name: github.com/pborman/uuid version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 +- name: github.com/peterbourgon/diskv + version: 5f041e8faa004a95c88a202771f4cc3e991971e6 - name: github.com/prometheus/client_golang version: c5b7fccd204277076155f10851dad72b76a49317 subpackages: @@ -228,9 +252,8 @@ imports: version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74 subpackages: - codec - - codec/codecgen - name: golang.org/x/crypto - version: d172538b2cfce0c13cee31e647d0367aa8cd2486 + version: 81e90905daefcd6fd217b62423c0908922eadb30 subpackages: - cast5 - openpgp @@ -244,9 +267,10 @@ imports: - scrypt - ssh/terminal - name: golang.org/x/net - version: f2499483f923065a842d38eb4c7f1927e6fc6e6d + version: 1c05540f6879653db88113bc4a2b70aec4bd491f subpackages: - context + - context/ctxhttp - http2 - http2/hpack - idna @@ -254,19 +278,26 @@ imports: - lex/httplex - trace - name: golang.org/x/oauth2 - version: 3c3a985cb79f52a3190fbc056984415ca6763d01 + version: a6bd8cefa1811bd24b86f8902872e4e8225f74c4 + subpackages: + - google + - internal + - jws + - jwt - name: golang.org/x/sys - version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 + version: 43eea11bc92608addb41b8a406b0407495c106f6 subpackages: - unix + - windows - name: golang.org/x/text - version: 2910a502d2bf9e43193af9d68ca516529614eed3 + version: b19bf474d317b857955b12035d2c5acb57ce8b01 subpackages: - cases - encoding - encoding/internal - encoding/internal/identifier - encoding/unicode + - internal - internal/tag - internal/utf8internal - language @@ -277,6 +308,18 @@ imports: - unicode/bidi - unicode/norm - width +- name: google.golang.org/appengine + version: 12d5545dc1cfa6047a286d5e853841b6471f4c19 + subpackages: + - internal + - internal/app_identity + - internal/base + - internal/datastore + - internal/log + - internal/modules + - internal/remote_api + - internal/urlfetch + - urlfetch - name: google.golang.org/grpc version: 8050b9cbc271307e5a716a9d782803d09b0d6f2d subpackages: @@ -293,37 +336,99 @@ imports: - transport - name: gopkg.in/inf.v0 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 -- name: gopkg.in/mgo.v2 - version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 - subpackages: - - bson - name: gopkg.in/yaml.v2 version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 - name: k8s.io/api - version: 4fe9229aaa9d704f8a2a21cdcd50de2bbb6e1b57 + version: cadaf100c0a3dd6b254f320d6d651df079ec8e0a subpackages: + - admission/v1alpha1 - admissionregistration/v1alpha1 - apps/v1beta1 + - apps/v1beta2 - authentication/v1 - authentication/v1beta1 - authorization/v1 - authorization/v1beta1 - autoscaling/v1 - - autoscaling/v2alpha1 + - autoscaling/v2beta1 - batch/v1 + - batch/v1beta1 - batch/v2alpha1 - certificates/v1beta1 - core/v1 - extensions/v1beta1 + - imagepolicy/v1alpha1 - networking/v1 - policy/v1beta1 + - rbac/v1 - rbac/v1alpha1 - rbac/v1beta1 + - scheduling/v1alpha1 - settings/v1alpha1 - storage/v1 - storage/v1beta1 +- name: k8s.io/apiextensions-apiserver + version: a5bbfd114a9b122acd741c61d88c84812375d9e1 + subpackages: + - pkg/features +- name: k8s.io/apimachinery + version: 3b05bbfa0a45413bfa184edbf9af617e277962fb + subpackages: + - pkg/api/equality + - pkg/api/errors + - pkg/api/meta + - pkg/api/resource + - pkg/api/validation + - pkg/apimachinery + - pkg/apimachinery/announced + - pkg/apimachinery/registered + - pkg/apis/meta/internalversion + - pkg/apis/meta/v1 + - pkg/apis/meta/v1/unstructured + - pkg/apis/meta/v1/validation + - pkg/apis/meta/v1alpha1 + - pkg/conversion + - pkg/conversion/queryparams + - pkg/conversion/unstructured + - pkg/fields + - pkg/labels + - pkg/runtime + - pkg/runtime/schema + - pkg/runtime/serializer + - pkg/runtime/serializer/json + - pkg/runtime/serializer/protobuf + - pkg/runtime/serializer/recognizer + - pkg/runtime/serializer/streaming + - pkg/runtime/serializer/versioning + - pkg/selection + - pkg/types + - pkg/util/cache + - pkg/util/clock + - pkg/util/diff + - pkg/util/errors + - pkg/util/framer + - pkg/util/httpstream + - pkg/util/httpstream/spdy + - pkg/util/intstr + - pkg/util/json + - pkg/util/mergepatch + - pkg/util/net + - pkg/util/rand + - pkg/util/runtime + - pkg/util/sets + - pkg/util/strategicpatch + - pkg/util/uuid + - pkg/util/validation + - pkg/util/validation/field + - pkg/util/wait + - pkg/util/yaml + - pkg/version + - pkg/watch + - third_party/forked/golang/json + - third_party/forked/golang/netutil + - third_party/forked/golang/reflect - name: k8s.io/apiserver - version: 087d1a2efeb6296f04bb0f54e7af9890052aa6d7 + version: c1e53d745d0fe45bf7d5d44697e6eface25fceca subpackages: - pkg/admission - pkg/apis/apiserver @@ -337,20 +442,118 @@ imports: - pkg/features - pkg/util/feature - pkg/util/flag +- name: k8s.io/client-go + version: 82aa063804cf055e16e8911250f888bc216e8b61 + subpackages: + - discovery + - discovery/fake + - dynamic + - informers/apps/v1beta1 + - informers/core/v1 + - informers/extensions/v1beta1 + - informers/internalinterfaces + - kubernetes + - kubernetes/fake + - kubernetes/scheme + - kubernetes/typed/admissionregistration/v1alpha1 + - kubernetes/typed/admissionregistration/v1alpha1/fake + - kubernetes/typed/apps/v1beta1 + - kubernetes/typed/apps/v1beta1/fake + - kubernetes/typed/apps/v1beta2 + - kubernetes/typed/apps/v1beta2/fake + - kubernetes/typed/authentication/v1 + - kubernetes/typed/authentication/v1/fake + - kubernetes/typed/authentication/v1beta1 + - kubernetes/typed/authentication/v1beta1/fake + - kubernetes/typed/authorization/v1 + - kubernetes/typed/authorization/v1/fake + - kubernetes/typed/authorization/v1beta1 + - kubernetes/typed/authorization/v1beta1/fake + - kubernetes/typed/autoscaling/v1 + - kubernetes/typed/autoscaling/v1/fake + - kubernetes/typed/autoscaling/v2beta1 + - kubernetes/typed/autoscaling/v2beta1/fake + - kubernetes/typed/batch/v1 + - kubernetes/typed/batch/v1/fake + - kubernetes/typed/batch/v1beta1 + - kubernetes/typed/batch/v1beta1/fake + - kubernetes/typed/batch/v2alpha1 + - kubernetes/typed/batch/v2alpha1/fake + - kubernetes/typed/certificates/v1beta1 + - kubernetes/typed/certificates/v1beta1/fake + - kubernetes/typed/core/v1 + - kubernetes/typed/core/v1/fake + - kubernetes/typed/extensions/v1beta1 + - kubernetes/typed/extensions/v1beta1/fake + - kubernetes/typed/networking/v1 + - kubernetes/typed/networking/v1/fake + - kubernetes/typed/policy/v1beta1 + - kubernetes/typed/policy/v1beta1/fake + - kubernetes/typed/rbac/v1 + - kubernetes/typed/rbac/v1/fake + - kubernetes/typed/rbac/v1alpha1 + - kubernetes/typed/rbac/v1alpha1/fake + - kubernetes/typed/rbac/v1beta1 + - kubernetes/typed/rbac/v1beta1/fake + - kubernetes/typed/scheduling/v1alpha1 + - kubernetes/typed/scheduling/v1alpha1/fake + - kubernetes/typed/settings/v1alpha1 + - kubernetes/typed/settings/v1alpha1/fake + - kubernetes/typed/storage/v1 + - kubernetes/typed/storage/v1/fake + - kubernetes/typed/storage/v1beta1 + - kubernetes/typed/storage/v1beta1/fake + - listers/apps/v1beta1 + - listers/core/v1 + - listers/extensions/v1beta1 + - pkg/version + - plugin/pkg/client/auth + - plugin/pkg/client/auth/azure + - plugin/pkg/client/auth/gcp + - plugin/pkg/client/auth/oidc + - plugin/pkg/client/auth/openstack + - rest + - rest/fake + - rest/watch + - testing + - third_party/forked/golang/template + - tools/auth + - tools/cache + - tools/clientcmd + - tools/clientcmd/api + - tools/clientcmd/api/latest + - tools/clientcmd/api/v1 + - tools/metrics + - tools/pager + - tools/portforward + - tools/record + - tools/reference + - transport + - transport/spdy + - util/cert + - util/flowcontrol + - util/homedir + - util/integer + - util/jsonpath + - util/retry + - util/workqueue +- name: k8s.io/kube-openapi + version: 868f2f29720b192240e18284659231b440f9cda5 + subpackages: + - pkg/common - name: k8s.io/kubernetes - version: d3ada0119e776222f11ec7945e6d860061339aad + version: 0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4 subpackages: - - cmd/kubeadm/app/apis/kubeadm - federation/apis/federation - federation/apis/federation/install - federation/apis/federation/v1beta1 - - federation/client/clientset_generated/federation_internalclientset - - federation/client/clientset_generated/federation_internalclientset/scheme - - federation/client/clientset_generated/federation_internalclientset/typed/autoscaling/internalversion - - federation/client/clientset_generated/federation_internalclientset/typed/batch/internalversion - - federation/client/clientset_generated/federation_internalclientset/typed/core/internalversion - - federation/client/clientset_generated/federation_internalclientset/typed/extensions/internalversion - - federation/client/clientset_generated/federation_internalclientset/typed/federation/internalversion + - federation/client/clientset_generated/federation_clientset + - federation/client/clientset_generated/federation_clientset/scheme + - federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1 + - federation/client/clientset_generated/federation_clientset/typed/batch/v1 + - federation/client/clientset_generated/federation_clientset/typed/core/v1 + - federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1 + - federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1 - pkg/api - pkg/api/events - pkg/api/helper @@ -366,7 +569,6 @@ imports: - pkg/api/v1/helper - pkg/api/v1/helper/qos - pkg/api/v1/pod - - pkg/api/v1/ref - pkg/api/validation - pkg/apis/admission - pkg/apis/admission/install @@ -377,6 +579,7 @@ imports: - pkg/apis/apps - pkg/apis/apps/install - pkg/apis/apps/v1beta1 + - pkg/apis/apps/v1beta2 - pkg/apis/authentication - pkg/apis/authentication/install - pkg/apis/authentication/v1 @@ -388,10 +591,11 @@ imports: - pkg/apis/autoscaling - pkg/apis/autoscaling/install - pkg/apis/autoscaling/v1 - - pkg/apis/autoscaling/v2alpha1 + - pkg/apis/autoscaling/v2beta1 - pkg/apis/batch - pkg/apis/batch/install - pkg/apis/batch/v1 + - pkg/apis/batch/v1beta1 - pkg/apis/batch/v2alpha1 - pkg/apis/certificates - pkg/apis/certificates/install @@ -413,8 +617,12 @@ imports: - pkg/apis/policy/v1beta1 - pkg/apis/rbac - pkg/apis/rbac/install + - pkg/apis/rbac/v1 - pkg/apis/rbac/v1alpha1 - pkg/apis/rbac/v1beta1 + - pkg/apis/scheduling + - pkg/apis/scheduling/install + - pkg/apis/scheduling/v1alpha1 - pkg/apis/settings - pkg/apis/settings/install - pkg/apis/settings/v1alpha1 @@ -424,28 +632,6 @@ imports: - pkg/apis/storage/v1 - pkg/apis/storage/v1beta1 - pkg/capabilities - - pkg/client/clientset_generated/clientset - - pkg/client/clientset_generated/clientset/scheme - - pkg/client/clientset_generated/clientset/typed/admissionregistration/v1alpha1 - - pkg/client/clientset_generated/clientset/typed/apps/v1beta1 - - pkg/client/clientset_generated/clientset/typed/authentication/v1 - - pkg/client/clientset_generated/clientset/typed/authentication/v1beta1 - - pkg/client/clientset_generated/clientset/typed/authorization/v1 - - pkg/client/clientset_generated/clientset/typed/authorization/v1beta1 - - pkg/client/clientset_generated/clientset/typed/autoscaling/v1 - - pkg/client/clientset_generated/clientset/typed/autoscaling/v2alpha1 - - pkg/client/clientset_generated/clientset/typed/batch/v1 - - pkg/client/clientset_generated/clientset/typed/batch/v2alpha1 - - pkg/client/clientset_generated/clientset/typed/certificates/v1beta1 - - pkg/client/clientset_generated/clientset/typed/core/v1 - - pkg/client/clientset_generated/clientset/typed/extensions/v1beta1 - - pkg/client/clientset_generated/clientset/typed/networking/v1 - - pkg/client/clientset_generated/clientset/typed/policy/v1beta1 - - pkg/client/clientset_generated/clientset/typed/rbac/v1alpha1 - - pkg/client/clientset_generated/clientset/typed/rbac/v1beta1 - - pkg/client/clientset_generated/clientset/typed/settings/v1alpha1 - - pkg/client/clientset_generated/clientset/typed/storage/v1 - - pkg/client/clientset_generated/clientset/typed/storage/v1beta1 - pkg/client/clientset_generated/internalclientset - pkg/client/clientset_generated/internalclientset/fake - pkg/client/clientset_generated/internalclientset/scheme @@ -473,24 +659,19 @@ imports: - pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion - pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion + - pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/settings/internalversion - pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/storage/internalversion - pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake - - pkg/client/informers/informers_generated/externalversions/apps/v1beta1 - - pkg/client/informers/informers_generated/externalversions/core/v1 - - pkg/client/informers/informers_generated/externalversions/extensions/v1beta1 - - pkg/client/informers/informers_generated/externalversions/internalinterfaces - - pkg/client/leaderelection/resourcelock - - pkg/client/listers/apps/v1beta1 - - pkg/client/listers/core/v1 - - pkg/client/listers/extensions/v1beta1 - - pkg/client/retry - pkg/client/unversioned - pkg/controller - pkg/controller/daemon - pkg/controller/daemon/util - pkg/controller/deployment/util + - pkg/controller/history + - pkg/controller/statefulset - pkg/credentialprovider - pkg/features - pkg/fieldpath @@ -498,9 +679,13 @@ imports: - pkg/kubectl/cmd/testing - pkg/kubectl/cmd/util - pkg/kubectl/cmd/util/openapi + - pkg/kubectl/cmd/util/openapi/validation - pkg/kubectl/plugins - pkg/kubectl/resource - pkg/kubectl/util + - pkg/kubectl/util/hash + - pkg/kubectl/util/slice + - pkg/kubectl/validation - pkg/kubelet/apis - pkg/kubelet/qos - pkg/kubelet/types @@ -510,16 +695,18 @@ imports: - pkg/registry/rbac/validation - pkg/security/apparmor - pkg/serviceaccount - - pkg/util - - pkg/util/exec + - pkg/util/file - pkg/util/hash + - pkg/util/io - pkg/util/labels - pkg/util/metrics - pkg/util/mount - pkg/util/net/sets - pkg/util/node - pkg/util/parsers + - pkg/util/pointer - pkg/util/slice + - pkg/util/taints - pkg/version - pkg/volume/util - pkg/watch/json @@ -529,6 +716,7 @@ imports: - plugin/pkg/scheduler/api - plugin/pkg/scheduler/schedulercache - plugin/pkg/scheduler/util + - staging/src/k8s.io/apimachinery/pkg/util/rand - name: k8s.io/metrics version: 8efbc8e22d00b9c600afec5f1c14073fd2412fce subpackages: @@ -537,6 +725,11 @@ imports: - pkg/client/clientset_generated/clientset - pkg/client/clientset_generated/clientset/scheme - pkg/client/clientset_generated/clientset/typed/metrics/v1alpha1 +- name: k8s.io/utils + version: 9fdc871a36f37980dd85f96d576b20d564cc0784 + subpackages: + - exec + - exec/testing - name: vbom.ml/util version: db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394 repo: https://github.com/fvbommel/util.git diff --git a/glide.yaml b/glide.yaml index 864eda8f7..a91094d22 100644 --- a/glide.yaml +++ b/glide.yaml @@ -14,32 +14,38 @@ import: - package: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - package: github.com/Masterminds/sprig - version: ^2.13 + version: ^2.14 - package: github.com/ghodss/yaml - package: github.com/Masterminds/semver version: ~1.3.1 - package: github.com/technosophos/moniker - package: github.com/golang/protobuf - version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef + version: 4bd1920723d7b7c925de087aa32e2187708897f7 subpackages: - proto - ptypes/any - ptypes/timestamp - package: google.golang.org/grpc version: 1.2.1 + # 1.8.1 libs are hosed and need some manual intervention, so pinning to 1.8.0 for now + # so others aren't getting errors when running `glide up` - package: k8s.io/kubernetes - version: ~1.7.0 + version: 1.8.0 - package: github.com/gosuri/uitable - package: github.com/asaskevich/govalidator version: ^4.0.0 - package: golang.org/x/crypto subpackages: - openpgp + # pin version of golang.org/x/sys that is compatible with golang.org/x/crypto +- package: golang.org/x/sys + version: 43eea11 + subpackages: + - unix + - windows - package: github.com/gobwas/glob version: ^0.2.1 - package: github.com/evanphx/json-patch -- package: github.com/facebookgo/atomicfile -- package: github.com/facebookgo/symwalk - package: github.com/BurntSushi/toml version: ~0.3.0 - package: github.com/naoina/go-stringutil @@ -50,37 +56,6 @@ import: - package: vbom.ml/util repo: https://github.com/fvbommel/util.git vcs: git -- package: github.com/docker/distribution - version: ~2.4.0 - -# hacks for kubernetes v1.7 -- package: cloud.google.com/go -- package: github.com/Azure/go-autorest - version: d7c034a8af24eda120dd6460bfcd6d9ed14e43ca -- package: github.com/dgrijalva/jwt-go -- package: github.com/docker/spdystream -- package: github.com/go-openapi/analysis - version: b44dc874b601d9e4e2f6e19140e794ba24bead3b -- package: github.com/go-openapi/errors - version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc -- package: github.com/go-openapi/loads - version: 18441dfa706d924a39a030ee2c3b1d8d81917b38 -- package: github.com/go-openapi/spec - version: 6aced65f8501fe1217321abf0749d354824ba2ff -- package: github.com/google/gofuzz -- package: github.com/hashicorp/golang-lru -- package: github.com/howeyc/gopass -- package: github.com/juju/ratelimit - version: 5b9ff866471762aa2ab2dced63c9fb6f53921342 -- package: github.com/pborman/uuid -- package: golang.org/x/oauth2 -- package: gopkg.in/inf.v0 -- package: github.com/go-openapi/strfmt -- package: github.com/mitchellh/mapstructure -- package: gopkg.in/mgo.v2/bson -ignore: - - k8s.io/client-go - - k8s.io/apimachinery testImports: - package: github.com/stretchr/testify diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index fe06e14e0..5fb3834ef 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "k8s.io/helm/pkg/proto/hapi/chart" ) @@ -111,14 +112,14 @@ const defaultIgnore = `# Patterns to ignore when building packages. ` const defaultIngress = `{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "fullname" . -}} +{{- $serviceName := include ".fullname" . -}} {{- $servicePort := .Values.service.externalPort -}} apiVersion: extensions/v1beta1 kind: Ingress metadata: - name: {{ template "fullname" . }} + name: {{ template ".fullname" . }} labels: - app: {{ template "name" . }} + app: {{ template ".name" . }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} @@ -147,9 +148,9 @@ spec: const defaultDeployment = `apiVersion: extensions/v1beta1 kind: Deployment metadata: - name: {{ template "fullname" . }} + name: {{ template ".fullname" . }} labels: - app: {{ template "name" . }} + app: {{ template ".name" . }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} @@ -158,7 +159,7 @@ spec: template: metadata: labels: - app: {{ template "name" . }} + app: {{ template ".name" . }} release: {{ .Release.Name }} spec: containers: @@ -186,9 +187,9 @@ spec: const defaultService = `apiVersion: v1 kind: Service metadata: - name: {{ template "fullname" . }} + name: {{ template ".fullname" . }} labels: - app: {{ template "name" . }} + app: {{ template ".name" . }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} @@ -200,7 +201,7 @@ spec: protocol: TCP name: {{ .Values.service.name }} selector: - app: {{ template "name" . }} + app: {{ template ".name" . }} release: {{ .Release.Name }} ` @@ -210,16 +211,16 @@ const defaultNotes = `1. Get the application URL by running these commands: http://{{ . }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template ".fullname" . }}) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + You can watch the status of by running 'kubectl get svc -w {{ template ".fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template ".fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo http://$SERVICE_IP:{{ .Values.service.externalPort }} {{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template ".name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl port-forward $POD_NAME 8080:{{ .Values.service.internalPort }} {{- end }} @@ -229,7 +230,7 @@ const defaultHelpers = `{{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} -{{- define "name" -}} +{{- define ".name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} @@ -237,7 +238,7 @@ Expand the name of the chart. Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} -{{- define "fullname" -}} +{{- define ".fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} @@ -318,27 +319,27 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) { { // ingress.yaml path: filepath.Join(cdir, TemplatesDir, IngressFileName), - content: []byte(defaultIngress), + content: []byte(strings.Replace(defaultIngress, "", chartfile.Name, -1)), }, { // deployment.yaml path: filepath.Join(cdir, TemplatesDir, DeploymentName), - content: []byte(defaultDeployment), + content: []byte(strings.Replace(defaultDeployment, "", chartfile.Name, -1)), }, { // service.yaml path: filepath.Join(cdir, TemplatesDir, ServiceName), - content: []byte(defaultService), + content: []byte(strings.Replace(defaultService, "", chartfile.Name, -1)), }, { // NOTES.txt path: filepath.Join(cdir, TemplatesDir, NotesName), - content: []byte(defaultNotes), + content: []byte(strings.Replace(defaultNotes, "", chartfile.Name, -1)), }, { // _helpers.tpl path: filepath.Join(cdir, TemplatesDir, HelpersName), - content: []byte(defaultHelpers), + content: []byte(strings.Replace(defaultHelpers, "", chartfile.Name, -1)), }, } diff --git a/pkg/chartutil/load.go b/pkg/chartutil/load.go index 03ba20e12..cf11ae2e4 100644 --- a/pkg/chartutil/load.go +++ b/pkg/chartutil/load.go @@ -28,7 +28,6 @@ import ( "path/filepath" "strings" - "github.com/facebookgo/symwalk" "github.com/golang/protobuf/ptypes/any" "k8s.io/helm/pkg/ignore" @@ -244,7 +243,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files := []*BufferedFile{} topdir += string(filepath.Separator) - err = symwalk.Walk(topdir, func(name string, fi os.FileInfo, err error) error { + err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) // Normalize to / since it will also work on Windows diff --git a/pkg/chartutil/requirements.go b/pkg/chartutil/requirements.go index 606b5db88..ce761a6fc 100644 --- a/pkg/chartutil/requirements.go +++ b/pkg/chartutil/requirements.go @@ -23,6 +23,7 @@ import ( "github.com/ghodss/yaml" "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/version" ) const ( @@ -230,7 +231,7 @@ func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Ch if existingChart.Metadata.Name != aliasChart.Name { continue } - if existingChart.Metadata.Version != aliasChart.Version { + if !version.IsCompatibleRange(aliasChart.Version, existingChart.Metadata.Version) { continue } chartFound = *existingChart @@ -266,7 +267,7 @@ func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error { for _, existingDependency := range c.Dependencies { var dependencyFound bool for _, req := range reqs.Dependencies { - if existingDependency.Metadata.Name == req.Name && existingDependency.Metadata.Version == req.Version { + if existingDependency.Metadata.Name == req.Name && version.IsCompatibleRange(req.Version, existingDependency.Metadata.Version) { dependencyFound = true break } diff --git a/pkg/chartutil/requirements_test.go b/pkg/chartutil/requirements_test.go index 502d8ad8d..ef09bcd2e 100644 --- a/pkg/chartutil/requirements_test.go +++ b/pkg/chartutil/requirements_test.go @@ -21,6 +21,7 @@ import ( "strconv" "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/version" ) func TestLoadRequirements(t *testing.T) { @@ -347,11 +348,24 @@ func TestGetAliasDependency(t *testing.T) { t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.Name) } + if req.Dependencies[0].Version != "" { + if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { + t.Fatalf("Dependency chart version is not in the compatible range") + } + + } + // Failure case req.Dependencies[0].Name = "something-else" if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) } + + req.Dependencies[0].Version = "something else which is not in the compatible range" + if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { + t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ") + } + } func TestDependentChartAliases(t *testing.T) { diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 15e97f6a1..a8a1b5a57 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -217,7 +217,12 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, ge // If the URL is relative (no scheme), prepend the chart repo's base URL if !u.IsAbs() { - u, err = url.Parse(rc.URL + "/" + u.Path) + path := u.Path + u, err = url.Parse(rc.URL) + if err != nil { + return u, r.Client, err + } + u.Path = u.Path + path return u, r.Client, err } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 4049b7979..73f9191c9 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -43,6 +43,7 @@ func TestResolveChartRef(t *testing.T) { {name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"}, {name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"}, {name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"}, + {name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"}, {name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true}, {name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true}, {name: "invalid", ref: "invalid-1.2.3", fail: true}, diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-querystring-index.yaml b/pkg/downloader/testdata/helmhome/repository/cache/testing-querystring-index.yaml new file mode 100644 index 000000000..1956e9f83 --- /dev/null +++ b/pkg/downloader/testdata/helmhome/repository/cache/testing-querystring-index.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +entries: + alpine: + - name: alpine + urls: + - alpine-1.2.3.tgz + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d + home: https://k8s.io/helm + sources: + - https://github.com/kubernetes/helm + version: 1.2.3 + description: Deploy a basic Alpine Linux pod + keywords: [] + maintainers: [] + engine: "" + icon: "" diff --git a/pkg/downloader/testdata/helmhome/repository/repositories.yaml b/pkg/downloader/testdata/helmhome/repository/repositories.yaml index 9b0cfe972..68efb461a 100644 --- a/pkg/downloader/testdata/helmhome/repository/repositories.yaml +++ b/pkg/downloader/testdata/helmhome/repository/repositories.yaml @@ -10,3 +10,5 @@ repositories: url: "http://example.com/charts" - name: malformed url: "http://dl.example.com" + - name: testing-querystring + url: "http://example.com?key=value" \ No newline at end of file diff --git a/pkg/helm/portforwarder/pod.go b/pkg/helm/portforwarder/pod.go index e20d644ea..7c2355204 100644 --- a/pkg/helm/portforwarder/pod.go +++ b/pkg/helm/portforwarder/pod.go @@ -17,7 +17,7 @@ limitations under the License. package portforwarder import ( - "k8s.io/client-go/pkg/api/v1" + "k8s.io/api/core/v1" ) // These functions are adapted from the "kubernetes" repository's file diff --git a/pkg/helm/portforwarder/portforwarder.go b/pkg/helm/portforwarder/portforwarder.go index 617da7a13..87f697a74 100644 --- a/pkg/helm/portforwarder/portforwarder.go +++ b/pkg/helm/portforwarder/portforwarder.go @@ -19,11 +19,11 @@ package portforwarder import ( "fmt" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/rest" "k8s.io/helm/pkg/kube" diff --git a/pkg/helm/portforwarder/portforwarder_test.go b/pkg/helm/portforwarder/portforwarder_test.go index f98e1f011..b9e90afa8 100644 --- a/pkg/helm/portforwarder/portforwarder_test.go +++ b/pkg/helm/portforwarder/portforwarder_test.go @@ -19,9 +19,9 @@ package portforwarder import ( "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/pkg/api/v1" ) func mockTillerPod() v1.Pod { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index c0a9a42bb..798434da4 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -27,6 +27,10 @@ import ( "time" jsonpatch "github.com/evanphx/json-patch" + apps "k8s.io/api/apps/v1beta2" + batch "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -40,15 +44,12 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/helper" - "k8s.io/kubernetes/pkg/api/v1" - apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" batchinternal "k8s.io/kubernetes/pkg/apis/batch" - batch "k8s.io/kubernetes/pkg/apis/batch/v1" - "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" conditions "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/printers" ) @@ -103,13 +104,9 @@ func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shoul } func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { - schema, err := c.Validator(true, c.SchemaCacheDir) - if err != nil { - c.Log("warning: failed to load schema: %s", err) - } return c.NewBuilder(true). ContinueOnError(). - Schema(schema). + Schema(c.validator()). NamespaceParam(namespace). DefaultNamespace(). Stream(reader, ""). @@ -117,20 +114,25 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result Do() } -// BuildUnstructured validates for Kubernetes objects and returns unstructured infos. -func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { - schema, err := c.Validator(true, c.SchemaCacheDir) +func (c *Client) validator() validation.Schema { + const openapi = false // only works on v1.8 clusters + schema, err := c.Validator(true, openapi, c.SchemaCacheDir) if err != nil { c.Log("warning: failed to load schema: %s", err) } + return schema +} +// BuildUnstructured validates for Kubernetes objects and returns unstructured infos. +func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { var result Result + b, err := c.NewUnstructuredBuilder(true) if err != nil { return result, err } result, err = b.ContinueOnError(). - Schema(schema). + Schema(c.validator()). NamespaceParam(namespace). DefaultNamespace(). Stream(reader, ""). @@ -157,6 +159,9 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { if err != nil { return "", err } + + var objPods = make(map[string][]api.Pod) + missing := []string{} err = perform(infos, func(info *resource.Info) error { c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) @@ -171,12 +176,26 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { gvk := info.ResourceMapping().GroupVersionKind vk := gvk.Version + "/" + gvk.Kind objs[vk] = append(objs[vk], info.Object) + + //Get the relation pods + objPods, err = c.getSelectRelationPod(info, objPods) + if err != nil { + c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) + } + return nil }) if err != nil { return "", err } + //here, we will add the objPods to the objs + for key, podItems := range objPods { + for i := range podItems { + objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i]) + } + } + // Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so // spin through them and print them. Printer is cool since it prints the header only when // an object type changes, so we can just rely on that. Problem is it doesn't seem to keep @@ -628,3 +647,67 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf return err } + +//get an kubernetes resources's relation pods +// kubernetes resource used select labels to relate pods +func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]api.Pod) (map[string][]api.Pod, error) { + if info == nil { + return objPods, nil + } + + c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name) + + versioned, err := c.AsVersionedObject(info.Object) + if runtime.IsNotRegisteredError(err) { + return objPods, nil + } + if err != nil { + return objPods, err + } + + // We can ignore this error because it will only error if it isn't a type that doesn't + // have pods. In that case, we don't care + selector, _ := getSelectorFromObject(versioned) + + selectorString := labels.Set(selector).AsSelector().String() + + // If we have an empty selector, this likely is a service or config map, so bail out now + if selectorString == "" { + return objPods, nil + } + + client, _ := c.ClientSet() + + pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{ + FieldSelector: fields.Everything().String(), + LabelSelector: labels.Set(selector).AsSelector().String(), + }) + if err != nil { + return objPods, err + } + + for _, pod := range pods.Items { + if pod.APIVersion == "" { + pod.APIVersion = "v1" + } + + if pod.Kind == "" { + pod.Kind = "Pod" + } + vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind + + if !isFoundPod(objPods[vk], pod) { + objPods[vk] = append(objPods[vk], pod) + } + } + return objPods, nil +} + +func isFoundPod(podItem []api.Pod, pod api.Pod) bool { + for _, value := range podItem { + if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) { + return true + } + } + return false +} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 889a12f72..f8432f102 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -35,11 +35,11 @@ import ( "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/kubectl" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/printers" watchjson "k8s.io/kubernetes/pkg/watch/json" ) diff --git a/pkg/kube/result.go b/pkg/kube/result.go index 9f143feb5..87c7e6ac1 100644 --- a/pkg/kube/result.go +++ b/pkg/kube/result.go @@ -83,5 +83,5 @@ func (r Result) Intersect(rs Result) Result { // isMatchingInfo returns true if infos match on Name and GroupVersionKind. func isMatchingInfo(a, b *resource.Info) bool { - return a.Name == b.Name && a.Mapping.GroupVersionKind == b.Mapping.GroupVersionKind + return a.Name == b.Name && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind } diff --git a/pkg/kube/tunnel.go b/pkg/kube/tunnel.go index 9c8f31e60..6da0c4594 100644 --- a/pkg/kube/tunnel.go +++ b/pkg/kube/tunnel.go @@ -21,11 +21,12 @@ import ( "io" "io/ioutil" "net" + "net/http" "strconv" "k8s.io/client-go/rest" "k8s.io/client-go/tools/portforward" - "k8s.io/client-go/tools/remotecommand" + "k8s.io/client-go/transport/spdy" ) // Tunnel describes a ssh-like tunnel to a kubernetes pod @@ -71,10 +72,11 @@ func (t *Tunnel) ForwardPort() error { Name(t.PodName). SubResource("portforward").URL() - dialer, err := remotecommand.NewExecutor(t.config, "POST", u) + transport, upgrader, err := spdy.RoundTripperFor(t.config) if err != nil { return err } + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", u) local, err := getAvailablePort() if err != nil { diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 30173167e..63e7c4a65 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -19,20 +19,18 @@ package kube // import "k8s.io/helm/pkg/kube" import ( "time" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/api/v1/helper" podutil "k8s.io/kubernetes/pkg/api/v1/pod" - apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" - extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" - "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" - core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1" - extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1" - internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" ) @@ -47,11 +45,10 @@ type deployment struct { func (c *Client) waitForResources(timeout time.Duration, created Result) error { c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout) - cs, err := c.ClientSet() + kcs, err := c.KubernetesClientSet() if err != nil { return err } - client := versionedClientsetForDeployment(cs) return wait.Poll(2*time.Second, timeout, func() (bool, error) { pods := []v1.Pod{} services := []v1.Service{} @@ -64,24 +61,24 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { } switch value := obj.(type) { case (*v1.ReplicationController): - list, err := getPods(client, value.Namespace, value.Spec.Selector) + list, err := getPods(kcs, value.Namespace, value.Spec.Selector) if err != nil { return false, err } pods = append(pods, list...) case (*v1.Pod): - pod, err := client.Core().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{}) + pod, err := kcs.Core().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{}) if err != nil { return false, err } pods = append(pods, *pod) case (*extensions.Deployment): - currentDeployment, err := client.Extensions().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) + currentDeployment, err := kcs.Extensions().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) if err != nil { return false, err } // Find RS associated with deployment - newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, client) + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1()) if err != nil || newReplicaSet == nil { return false, err } @@ -91,53 +88,59 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { } deployments = append(deployments, newDeployment) case (*extensions.DaemonSet): - list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) if err != nil { return false, err } pods = append(pods, list...) - case (*apps.StatefulSet): - list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) + case (*appsv1beta1.StatefulSet): + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case (*appsv1beta2.StatefulSet): + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) if err != nil { return false, err } pods = append(pods, list...) case (*extensions.ReplicaSet): - list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) if err != nil { return false, err } pods = append(pods, list...) case (*v1.PersistentVolumeClaim): - claim, err := client.Core().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{}) + claim, err := kcs.Core().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{}) if err != nil { return false, err } pvc = append(pvc, *claim) case (*v1.Service): - svc, err := client.Core().Services(value.Namespace).Get(value.Name, metav1.GetOptions{}) + svc, err := kcs.Core().Services(value.Namespace).Get(value.Name, metav1.GetOptions{}) if err != nil { return false, err } services = append(services, *svc) } } - isReady := podsReady(pods) && servicesReady(services) && volumesReady(pvc) && deploymentsReady(deployments) - c.Log("resources ready: %v", isReady) + isReady := c.podsReady(pods) && c.servicesReady(services) && c.volumesReady(pvc) && c.deploymentsReady(deployments) return isReady, nil }) } -func podsReady(pods []v1.Pod) bool { +func (c *Client) podsReady(pods []v1.Pod) bool { for _, pod := range pods { if !podutil.IsPodReady(&pod) { + c.Log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName()) return false } } return true } -func servicesReady(svc []v1.Service) bool { +func (c *Client) servicesReady(svc []v1.Service) bool { for _, s := range svc { // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) if s.Spec.Type == v1.ServiceTypeExternalName { @@ -146,48 +149,42 @@ func servicesReady(svc []v1.Service) bool { // Make sure the service is not explicitly set to "None" before checking the IP if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) { + c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName()) return false } // This checks if the service has a LoadBalancer and that balancer has an Ingress defined if s.Spec.Type == v1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil { + c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName()) return false } } return true } -func volumesReady(vols []v1.PersistentVolumeClaim) bool { +func (c *Client) volumesReady(vols []v1.PersistentVolumeClaim) bool { for _, v := range vols { if v.Status.Phase != v1.ClaimBound { + c.Log("PersistentVolumeClaim is not ready: %s/%s", v.GetNamespace(), v.GetName()) return false } } return true } -func deploymentsReady(deployments []deployment) bool { +func (c *Client) deploymentsReady(deployments []deployment) bool { for _, v := range deployments { if !(v.replicaSets.Status.ReadyReplicas >= *v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) { + c.Log("Deployment is not ready: %s/%s", v.deployment.GetNamespace(), v.deployment.GetName()) return false } } return true } -func getPods(client clientset.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) { +func getPods(client kubernetes.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) { list, err := client.Core().Pods(namespace).List(metav1.ListOptions{ FieldSelector: fields.Everything().String(), LabelSelector: labels.Set(selector).AsSelector().String(), }) return list.Items, err } - -func versionedClientsetForDeployment(internalClient internalclientset.Interface) clientset.Interface { - if internalClient == nil { - return &clientset.Clientset{} - } - return &clientset.Clientset{ - CoreV1Client: core.New(internalClient.Core().RESTClient()), - ExtensionsV1beta1Client: extensionsclient.New(internalClient.Extensions().RESTClient()), - } -} diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 1162ee972..02040f079 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -68,7 +68,7 @@ func TestBadChart(t *testing.T) { func TestInvalidYaml(t *testing.T) { m := All(badYamlFileDir).Messages if len(m) != 1 { - t.Errorf("All didn't fail with expected errors, got %#v", m) + t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { t.Errorf("All didn't have the error for deliberateSyntaxError") @@ -78,7 +78,7 @@ func TestInvalidYaml(t *testing.T) { func TestBadValues(t *testing.T) { m := All(badValuesFileDir).Messages if len(m) != 1 { - t.Errorf("All didn't fail with expected errors, got %#v", m) + t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "cannot unmarshal") { t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 97506e607..236032eef 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -62,7 +62,7 @@ func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, } client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile) if err != nil { - return nil, fmt.Errorf("Could not construct protocol handler for: %s", u.Scheme) + return nil, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err) } return &ChartRepository{ @@ -110,8 +110,13 @@ func (r *ChartRepository) Load() error { // is for pre-2.2.0 repo files. func (r *ChartRepository) DownloadIndexFile(cachePath string) error { var indexURL string + parsedURL, err := url.Parse(r.Config.URL) + if err != nil { + return err + } + parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" - indexURL = strings.TrimSuffix(r.Config.URL, "/") + "/index.yaml" + indexURL = parsedURL.String() resp, err := r.Client.Get(indexURL) if err != nil { return err diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index 5e1b5c6cd..cbf54c572 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -23,7 +23,6 @@ import ( "os" "time" - "github.com/facebookgo/atomicfile" "github.com/ghodss/yaml" ) @@ -135,20 +134,9 @@ func (r *RepoFile) Remove(name string) bool { // WriteFile writes a repositories file to the given path. func (r *RepoFile) WriteFile(path string, perm os.FileMode) error { - f, err := atomicfile.New(path, perm) - if err != nil { - return err - } - data, err := yaml.Marshal(r) if err != nil { return err } - - _, err = f.File.Write(data) - if err != nil { - return err - } - - return f.Close() + return ioutil.WriteFile(path, data, perm) } diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go index d4500c9e2..6aee41faf 100644 --- a/pkg/repo/repo_test.go +++ b/pkg/repo/repo_test.go @@ -201,18 +201,10 @@ func TestWriteFile(t *testing.T) { t.Errorf("failed to create test-file (%v)", err) } defer os.Remove(repoFile.Name()) - - fileMode := os.FileMode(0744) - if err := sampleRepository.WriteFile(repoFile.Name(), fileMode); err != nil { + if err := sampleRepository.WriteFile(repoFile.Name(), 744); err != nil { t.Errorf("failed to write file (%v)", err) } - info, _ := os.Stat(repoFile.Name()) - mode := info.Mode() - if mode != fileMode { - t.Errorf("incorrect file mode: %s (expected %s)", mode, fileMode) - } - repos, err := LoadRepositoriesFile(repoFile.Name()) if err != nil { t.Errorf("failed to load file (%v)", err) diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 6d1c8f222..63c03a1d2 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -17,16 +17,11 @@ limitations under the License. package driver // import "k8s.io/helm/pkg/storage/driver" import ( - "bytes" - "compress/gzip" - "encoding/base64" "fmt" - "io/ioutil" "strconv" "strings" "time" - "github.com/golang/protobuf/proto" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kblabels "k8s.io/apimachinery/pkg/labels" @@ -42,10 +37,6 @@ var _ Driver = (*ConfigMaps)(nil) // ConfigMapsDriverName is the string name of the driver. const ConfigMapsDriverName = "ConfigMap" -var b64 = base64.StdEncoding - -var magicGzip = []byte{0x1f, 0x8b, 0x08} - // ConfigMaps is a wrapper around an implementation of a kubernetes // ConfigMapsInterface. type ConfigMaps struct { @@ -265,57 +256,3 @@ func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.Config Data: map[string]string{"release": s}, }, nil } - -// encodeRelease encodes a release returning a base64 encoded -// gzipped binary protobuf encoding representation, or error. -func encodeRelease(rls *rspb.Release) (string, error) { - b, err := proto.Marshal(rls) - if err != nil { - return "", err - } - var buf bytes.Buffer - w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) - if err != nil { - return "", err - } - if _, err = w.Write(b); err != nil { - return "", err - } - w.Close() - - return b64.EncodeToString(buf.Bytes()), nil -} - -// decodeRelease decodes the bytes in data into a release -// type. Data must contain a base64 encoded string of a -// valid protobuf encoding of a release, otherwise -// an error is returned. -func decodeRelease(data string) (*rspb.Release, error) { - // base64 decode string - b, err := b64.DecodeString(data) - if err != nil { - return nil, err - } - - // For backwards compatibility with releases that were stored before - // compression was introduced we skip decompression if the - // gzip magic header is not found - if bytes.Equal(b[0:3], magicGzip) { - r, err := gzip.NewReader(bytes.NewReader(b)) - if err != nil { - return nil, err - } - b2, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - b = b2 - } - - var rls rspb.Release - // unmarshal protobuf bytes - if err := proto.Unmarshal(b, &rls); err != nil { - return nil, err - } - return &rls, nil -} diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 40174106d..e9f00a40a 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -142,3 +142,81 @@ func (mock *MockConfigMapsInterface) Delete(name string, opts *metav1.DeleteOpti delete(mock.objects, name) return nil } + +// newTestFixture initializes a MockSecretsInterface. +// Secrets are created for each release provided. +func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets { + var mock MockSecretsInterface + mock.Init(t, releases...) + + return NewSecrets(&mock) +} + +// MockSecretsInterface mocks a kubernetes SecretsInterface +type MockSecretsInterface struct { + internalversion.SecretInterface + + objects map[string]*api.Secret +} + +// Init initializes the MockSecretsInterface with the set of releases. +func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) { + mock.objects = map[string]*api.Secret{} + + for _, rls := range releases { + objkey := testKey(rls.Name, rls.Version) + + secret, err := newSecretsObject(objkey, rls, nil) + if err != nil { + t.Fatalf("Failed to create secret: %s", err) + } + mock.objects[objkey] = secret + } +} + +// Get returns the Secret by name. +func (mock *MockSecretsInterface) Get(name string, options metav1.GetOptions) (*api.Secret, error) { + object, ok := mock.objects[name] + if !ok { + return nil, apierrors.NewNotFound(api.Resource("tests"), name) + } + return object, nil +} + +// List returns the a of Secret. +func (mock *MockSecretsInterface) List(opts metav1.ListOptions) (*api.SecretList, error) { + var list api.SecretList + for _, secret := range mock.objects { + list.Items = append(list.Items, *secret) + } + return &list, nil +} + +// Create creates a new Secret. +func (mock *MockSecretsInterface) Create(secret *api.Secret) (*api.Secret, error) { + name := secret.ObjectMeta.Name + if object, ok := mock.objects[name]; ok { + return object, apierrors.NewAlreadyExists(api.Resource("tests"), name) + } + mock.objects[name] = secret + return secret, nil +} + +// Update updates a Secret. +func (mock *MockSecretsInterface) Update(secret *api.Secret) (*api.Secret, error) { + name := secret.ObjectMeta.Name + if _, ok := mock.objects[name]; !ok { + return nil, apierrors.NewNotFound(api.Resource("tests"), name) + } + mock.objects[name] = secret + return secret, nil +} + +// Delete deletes a Secret by name. +func (mock *MockSecretsInterface) Delete(name string, opts *metav1.DeleteOptions) error { + if _, ok := mock.objects[name]; !ok { + return apierrors.NewNotFound(api.Resource("tests"), name) + } + delete(mock.objects, name) + return nil +} diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go index e6875fb3d..ce72308a8 100644 --- a/pkg/storage/driver/records.go +++ b/pkg/storage/driver/records.go @@ -20,6 +20,8 @@ import ( "sort" "strconv" + "github.com/golang/protobuf/proto" + rspb "k8s.io/helm/pkg/proto/hapi/release" ) @@ -129,5 +131,5 @@ func newRecord(key string, rls *rspb.Release) *record { lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) lbs.set("VERSION", strconv.Itoa(int(rls.Version))) - return &record{key: key, lbs: lbs, rls: rls} + return &record{key: key, lbs: lbs, rls: proto.Clone(rls).(*rspb.Release)} } diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go new file mode 100644 index 000000000..f81b475c0 --- /dev/null +++ b/pkg/storage/driver/secrets.go @@ -0,0 +1,258 @@ +/* +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 driver // import "k8s.io/helm/pkg/storage/driver" + +import ( + "fmt" + "strconv" + "strings" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +var _ Driver = (*Secrets)(nil) + +// SecretsDriverName is the string name of the driver. +const SecretsDriverName = "Secret" + +// Secrets is a wrapper around an implementation of a kubernetes +// SecretsInterface. +type Secrets struct { + impl internalversion.SecretInterface + Log func(string, ...interface{}) +} + +// NewSecrets initializes a new Secrets wrapping an implmenetation of +// the kubernetes SecretsInterface. +func NewSecrets(impl internalversion.SecretInterface) *Secrets { + return &Secrets{ + impl: impl, + Log: func(_ string, _ ...interface{}) {}, + } +} + +// Name returns the name of the driver. +func (secrets *Secrets) Name() string { + return SecretsDriverName +} + +// Get fetches the release named by key. The corresponding release is returned +// or error if not found. +func (secrets *Secrets) Get(key string) (*rspb.Release, error) { + // fetch the secret holding the release named by key + obj, err := secrets.impl.Get(key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound(key) + } + + secrets.Log("get: failed to get %q: %s", key, err) + return nil, err + } + // found the secret, decode the base64 data string + r, err := decodeRelease(string(obj.Data["release"])) + if err != nil { + secrets.Log("get: failed to decode data %q: %s", key, err) + return nil, err + } + // return the release object + return r, nil +} + +// List fetches all releases and returns the list releases such +// that filter(release) == true. An error is returned if the +// secret fails to retrieve the releases. +func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { + lsel := kblabels.Set{"OWNER": "TILLER"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := secrets.impl.List(opts) + if err != nil { + secrets.Log("list: failed to list: %s", err) + return nil, err + } + + var results []*rspb.Release + + // iterate over the secrets object list + // and decode each release + for _, item := range list.Items { + rls, err := decodeRelease(string(item.Data["release"])) + if err != nil { + secrets.Log("list: failed to decode release: %v: %s", item, err) + continue + } + if filter(rls) { + results = append(results, rls) + } + } + return results, nil +} + +// Query fetches all releases that match the provided map of labels. +// An error is returned if the secret fails to retrieve the releases. +func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) { + ls := kblabels.Set{} + for k, v := range labels { + if errs := validation.IsValidLabelValue(v); len(errs) != 0 { + return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + } + ls[k] = v + } + + opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} + + list, err := secrets.impl.List(opts) + if err != nil { + secrets.Log("query: failed to query with labels: %s", err) + return nil, err + } + + if len(list.Items) == 0 { + return nil, ErrReleaseNotFound(labels["NAME"]) + } + + var results []*rspb.Release + for _, item := range list.Items { + rls, err := decodeRelease(string(item.Data["release"])) + if err != nil { + secrets.Log("query: failed to decode release: %s", err) + continue + } + results = append(results, rls) + } + return results, nil +} + +// Create creates a new Secret holding the release. If the +// Secret already exists, ErrReleaseExists is returned. +func (secrets *Secrets) Create(key string, rls *rspb.Release) error { + // set labels for secrets object meta data + var lbs labels + + lbs.init() + lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix()))) + + // create a new secret to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + secrets.Log("create: failed to encode release %q: %s", rls.Name, err) + return err + } + // push the secret object out into the kubiverse + if _, err := secrets.impl.Create(obj); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists(rls.Name) + } + + secrets.Log("create: failed to create: %s", err) + return err + } + return nil +} + +// Update updates the Secret holding the release. If not found +// the Secret is created to hold the release. +func (secrets *Secrets) Update(key string, rls *rspb.Release) error { + // set labels for secrets object meta data + var lbs labels + + lbs.init() + lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix()))) + + // create a new secret object to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + secrets.Log("update: failed to encode release %q: %s", rls.Name, err) + return err + } + // push the secret object out into the kubiverse + _, err = secrets.impl.Update(obj) + if err != nil { + secrets.Log("update: failed to update: %s", err) + return err + } + return nil +} + +// Delete deletes the Secret holding the release named by key. +func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) { + // fetch the release to check existence + if rls, err = secrets.Get(key); err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseExists(rls.Name) + } + + secrets.Log("delete: failed to get release %q: %s", key, err) + return nil, err + } + // delete the release + if err = secrets.impl.Delete(key, &metav1.DeleteOptions{}); err != nil { + return rls, err + } + return rls, nil +} + +// newSecretsObject constructs a kubernetes Secret object +// to store a release. Each secret data entry is the base64 +// encoded string of a release's binary protobuf encoding. +// +// The following labels are used within each secret: +// +// "MODIFIED_AT" - timestamp indicating when this secret was last modified. (set in Update) +// "CREATED_AT" - timestamp indicating when this secret was created. (set in Create) +// "VERSION" - version of the release. +// "STATUS" - status of the release (see proto/hapi/release.status.pb.go for variants) +// "OWNER" - owner of the secret, currently "TILLER". +// "NAME" - name of the release. +// +func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*api.Secret, error) { + const owner = "TILLER" + + // encode the release + s, err := encodeRelease(rls) + if err != nil { + return nil, err + } + + if lbs == nil { + lbs.init() + } + + // apply labels + lbs.set("NAME", rls.Name) + lbs.set("OWNER", owner) + lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) + lbs.set("VERSION", strconv.Itoa(int(rls.Version))) + + // create and return secret object + return &api.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Data: map[string][]byte{"release": []byte(s)}, + }, nil +} diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go new file mode 100644 index 000000000..2441560c3 --- /dev/null +++ b/pkg/storage/driver/secrets_test.go @@ -0,0 +1,186 @@ +/* +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 driver + +import ( + "encoding/base64" + "reflect" + "testing" + + "github.com/gogo/protobuf/proto" + "k8s.io/kubernetes/pkg/api" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestSecretName(t *testing.T) { + c := newTestFixtureSecrets(t) + if c.Name() != SecretsDriverName { + t.Errorf("Expected name to be %q, got %q", SecretsDriverName, c.Name()) + } +} + +func TestSecretGet(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // get release with key + got, err := secrets.Get(key) + if err != nil { + t.Fatalf("Failed to get release: %s", err) + } + // compare fetched release with original + if !reflect.DeepEqual(rel, got) { + t.Errorf("Expected {%q}, got {%q}", rel, got) + } +} + +func TestUNcompressedSecretGet(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + // Create a test fixture which contains an uncompressed release + secret, err := newSecretsObject(key, rel, nil) + if err != nil { + t.Fatalf("Failed to create secret: %s", err) + } + b, err := proto.Marshal(rel) + if err != nil { + t.Fatalf("Failed to marshal release: %s", err) + } + secret.Data["release"] = []byte(base64.StdEncoding.EncodeToString(b)) + var mock MockSecretsInterface + mock.objects = map[string]*api.Secret{key: secret} + secrets := NewSecrets(&mock) + + // get release with key + got, err := secrets.Get(key) + if err != nil { + t.Fatalf("Failed to get release: %s", err) + } + // compare fetched release with original + if !reflect.DeepEqual(rel, got) { + t.Errorf("Expected {%q}, got {%q}", rel, got) + } +} + +func TestSecretList(t *testing.T) { + secrets := newTestFixtureSecrets(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", rspb.Status_DELETED), + releaseStub("key-2", 1, "default", rspb.Status_DELETED), + releaseStub("key-3", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-4", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-5", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("key-6", 1, "default", rspb.Status_SUPERSEDED), + }...) + + // list all deleted releases + del, err := secrets.List(func(rel *rspb.Release) bool { + return rel.Info.Status.Code == rspb.Status_DELETED + }) + // check + if err != nil { + t.Errorf("Failed to list deleted: %s", err) + } + if len(del) != 2 { + t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) + } + + // list all deployed releases + dpl, err := secrets.List(func(rel *rspb.Release) bool { + return rel.Info.Status.Code == rspb.Status_DEPLOYED + }) + // check + if err != nil { + t.Errorf("Failed to list deployed: %s", err) + } + if len(dpl) != 2 { + t.Errorf("Expected 2 deployed, got %d", len(dpl)) + } + + // list all superseded releases + ssd, err := secrets.List(func(rel *rspb.Release) bool { + return rel.Info.Status.Code == rspb.Status_SUPERSEDED + }) + // check + if err != nil { + t.Errorf("Failed to list superseded: %s", err) + } + if len(ssd) != 2 { + t.Errorf("Expected 2 superseded, got %d", len(ssd)) + } +} + +func TestSecretCreate(t *testing.T) { + secrets := newTestFixtureSecrets(t) + + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + // store the release in a secret + if err := secrets.Create(key, rel); err != nil { + t.Fatalf("Failed to create release with key %q: %s", key, err) + } + + // get the release back + got, err := secrets.Get(key) + if err != nil { + t.Fatalf("Failed to get release with key %q: %s", key, err) + } + + // compare created release with original + if !reflect.DeepEqual(rel, got) { + t.Errorf("Expected {%q}, got {%q}", rel, got) + } +} + +func TestSecretUpdate(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // modify release status code + rel.Info.Status.Code = rspb.Status_SUPERSEDED + + // perform the update + if err := secrets.Update(key, rel); err != nil { + t.Fatalf("Failed to update release: %s", err) + } + + // fetch the updated release + got, err := secrets.Get(key) + if err != nil { + t.Fatalf("Failed to get release with key %q: %s", key, err) + } + + // check release has actually been updated by comparing modified fields + if rel.Info.Status.Code != got.Info.Status.Code { + t.Errorf("Expected status %s, got status %s", rel.Info.Status.Code, got.Info.Status.Code) + } +} diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go new file mode 100644 index 000000000..65fb17e7c --- /dev/null +++ b/pkg/storage/driver/util.go @@ -0,0 +1,85 @@ +/* +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 driver // import "k8s.io/helm/pkg/storage/driver" + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io/ioutil" + + "github.com/golang/protobuf/proto" + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +var b64 = base64.StdEncoding + +var magicGzip = []byte{0x1f, 0x8b, 0x08} + +// encodeRelease encodes a release returning a base64 encoded +// gzipped binary protobuf encoding representation, or error. +func encodeRelease(rls *rspb.Release) (string, error) { + b, err := proto.Marshal(rls) + if err != nil { + return "", err + } + var buf bytes.Buffer + w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) + if err != nil { + return "", err + } + if _, err = w.Write(b); err != nil { + return "", err + } + w.Close() + + return b64.EncodeToString(buf.Bytes()), nil +} + +// decodeRelease decodes the bytes in data into a release +// type. Data must contain a base64 encoded string of a +// valid protobuf encoding of a release, otherwise +// an error is returned. +func decodeRelease(data string) (*rspb.Release, error) { + // base64 decode string + b, err := b64.DecodeString(data) + if err != nil { + return nil, err + } + + // For backwards compatibility with releases that were stored before + // compression was introduced we skip decompression if the + // gzip magic header is not found + if bytes.Equal(b[0:3], magicGzip) { + r, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + b2, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + b = b2 + } + + var rls rspb.Release + // unmarshal protobuf bytes + if err := proto.Unmarshal(b, &rls); err != nil { + return nil, err + } + return &rls, nil +} diff --git a/pkg/tiller/kind_sorter.go b/pkg/tiller/kind_sorter.go index f2447143b..f367e65c8 100644 --- a/pkg/tiller/kind_sorter.go +++ b/pkg/tiller/kind_sorter.go @@ -32,9 +32,11 @@ var InstallOrder SortOrder = []string{ "LimitRange", "Secret", "ConfigMap", + "StorageClass", "PersistentVolume", "PersistentVolumeClaim", "ServiceAccount", + "CustomResourceDefinition", "ClusterRole", "ClusterRoleBinding", "Role", @@ -71,9 +73,11 @@ var UninstallOrder SortOrder = []string{ "Role", "ClusterRoleBinding", "ClusterRole", + "CustomResourceDefinition", "ServiceAccount", "PersistentVolumeClaim", "PersistentVolume", + "StorageClass", "ConfigMap", "Secret", "LimitRange", @@ -116,8 +120,12 @@ func (k *kindSorter) Less(i, j int) bool { b := k.manifests[j] first, aok := k.ordering[a.Head.Kind] second, bok := k.ordering[b.Head.Kind] + // if same kind (including unknown) sub sort alphanumeric if first == second { - // same kind (including unknown) so sub sort alphanumeric + // if both are unknown and of different kind sort by kind alphabetically + if !aok && !bok && a.Head.Kind != b.Head.Kind { + return a.Head.Kind < b.Head.Kind + } return a.Name < b.Name } // unknown kind is last diff --git a/pkg/tiller/kind_sorter_test.go b/pkg/tiller/kind_sorter_test.go index 6996731ca..ef7296e89 100644 --- a/pkg/tiller/kind_sorter_test.go +++ b/pkg/tiller/kind_sorter_test.go @@ -41,6 +41,10 @@ func TestKindSorter(t *testing.T) { Name: "u", Head: &util.SimpleHead{Kind: "CronJob"}, }, + { + Name: "2", + Head: &util.SimpleHead{Kind: "CustomResourceDefinition"}, + }, { Name: "n", Head: &util.SimpleHead{Kind: "DaemonSet"}, @@ -117,6 +121,10 @@ func TestKindSorter(t *testing.T) { Name: "s", Head: &util.SimpleHead{Kind: "StatefulSet"}, }, + { + Name: "1", + Head: &util.SimpleHead{Kind: "StorageClass"}, + }, { Name: "w", Head: &util.SimpleHead{Kind: "APIService"}, @@ -128,8 +136,8 @@ func TestKindSorter(t *testing.T) { order SortOrder expected string }{ - {"install", InstallOrder, "abcdefghijklmnopqrstuvw!"}, - {"uninstall", UninstallOrder, "wvmutsrqponlkjihgfedcba!"}, + {"install", InstallOrder, "abcde1fgh2ijklmnopqrstuvw!"}, + {"uninstall", UninstallOrder, "wvmutsrqponlkji2hgf1edcba!"}, } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { @@ -175,7 +183,7 @@ func TestKindSorterSubSort(t *testing.T) { Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, }, { - Name: "u3", + Name: "u2", Head: &util.SimpleHead{Kind: "Unknown"}, }, { @@ -183,8 +191,8 @@ func TestKindSorterSubSort(t *testing.T) { Head: &util.SimpleHead{Kind: "Unknown"}, }, { - Name: "u2", - Head: &util.SimpleHead{Kind: "Unknown"}, + Name: "t3", + Head: &util.SimpleHead{Kind: "Unknown2"}, }, } for _, test := range []struct { @@ -193,7 +201,7 @@ func TestKindSorterSubSort(t *testing.T) { expected string }{ // expectation is sorted by kind (unknown is last) and then sub sorted alphabetically within each group - {"cm,clusterRole,clusterRoleBinding,Unknown", InstallOrder, "01Aa!zu1u2u3"}, + {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01Aa!zu1u2t3"}, } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go index 58a8a18c0..18cf56737 100644 --- a/pkg/tiller/release_update.go +++ b/pkg/tiller/release_update.go @@ -155,7 +155,7 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R updatedRelease.Info.Status.Code = release.Status_FAILED updatedRelease.Info.Description = msg s.recordRelease(originalRelease, true) - s.recordRelease(updatedRelease, false) + s.recordRelease(updatedRelease, true) return res, err } diff --git a/pkg/tiller/release_update_test.go b/pkg/tiller/release_update_test.go index 7b1618b20..a3eb37f4a 100644 --- a/pkg/tiller/release_update_test.go +++ b/pkg/tiller/release_update_test.go @@ -20,6 +20,8 @@ import ( "strings" "testing" + "github.com/golang/protobuf/proto" + "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" @@ -59,10 +61,7 @@ func TestUpdateRelease(t *testing.T) { t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) } - updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } + updated := compareStoredAndReturnedRelease(t, *rs, *res) if len(updated.Hooks) != 1 { t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) @@ -79,8 +78,8 @@ func TestUpdateRelease(t *testing.T) { t.Errorf("Expected event 0 to be pre upgrade") } - if len(res.Release.Manifest) == 0 { - t.Errorf("No manifest returned: %v", res.Release) + if len(updated.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) } if res.Release.Config == nil { @@ -89,12 +88,8 @@ func TestUpdateRelease(t *testing.T) { t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) } - if len(updated.Manifest) == 0 { - t.Errorf("Expected manifest in %v", res) - } - if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { - t.Errorf("unexpected output: %s", rel.Manifest) + t.Errorf("unexpected output: %s", updated.Manifest) } if res.Release.Version != 2 { @@ -167,6 +162,7 @@ func TestUpdateRelease_ReuseValues(t *testing.T) { 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) } + compareStoredAndReturnedRelease(t, *rs, *res) } func TestUpdateRelease_ResetReuseValues(t *testing.T) { @@ -196,6 +192,7 @@ func TestUpdateRelease_ResetReuseValues(t *testing.T) { if res.Release.Config != nil && res.Release.Config.Raw != "" { t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) } + compareStoredAndReturnedRelease(t, *rs, *res) } func TestUpdateReleaseFailure(t *testing.T) { @@ -204,6 +201,7 @@ func TestUpdateReleaseFailure(t *testing.T) { rel := releaseStub() rs.env.Releases.Create(rel) rs.env.KubeClient = newUpdateFailingKubeClient() + rs.Log = t.Logf req := &services.UpdateReleaseRequest{ Name: rel.Name, @@ -225,6 +223,8 @@ func TestUpdateReleaseFailure(t *testing.T) { t.Errorf("Expected FAILED release. Got %d", updatedStatus) } + compareStoredAndReturnedRelease(t, *rs, *res) + edesc := "Upgrade \"angry-panda\" failed: Failed update in kube client" if got := res.Release.Info.Description; got != edesc { t.Errorf("Expected description %q, got %q", edesc, got) @@ -285,3 +285,16 @@ func TestUpdateReleaseNoChanges(t *testing.T) { t.Fatalf("Failed updated: %s", err) } } + +func compareStoredAndReturnedRelease(t *testing.T, rs ReleaseServer, res services.UpdateReleaseResponse) *release.Release { + storedRelease, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Fatalf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if !proto.Equal(storedRelease, res.Release) { + t.Errorf("Stored release doesn't match returned Release") + } + + return storedRelease +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 71b6af465..2109a0ae0 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -26,7 +26,7 @@ var ( // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. // Increment patch number for critical fixes to existing releases. - Version = "v2.6" + Version = "v2.7" // BuildMetadata is extra build time data BuildMetadata = "unreleased"