diff --git a/.circleci/config.yml b/.circleci/config.yml index 273643b7c..73e734825 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,16 +10,28 @@ jobs: steps: - checkout - setup_remote_docker + - restore_cache: + keys: + - glide-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }} + - glide- # used as a fall through if the checksum fails to find exact entry - run: name: install dependencies command: make bootstrap - save_cache: - key: vendor-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }} + key: glide-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }} paths: - - vendor + - /root/.glide # Where the glide cache is stored - run: name: test command: .circleci/test.sh - deploy: name: deploy command: .circleci/deploy.sh +workflows: + version: 2 + build: + jobs: + - build: + filters: + tags: + only: /.*/ diff --git a/Makefile b/Makefile index 737be298c..a5bdf1b8f 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ DOCKER_REGISTRY ?= gcr.io IMAGE_PREFIX ?= kubernetes-helm SHORT_NAME ?= tiller SHORT_NAME_RUDDER ?= rudder -TARGETS = darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le windows/amd64 +TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le windows/amd64 DIST_DIRS = find * -type d -exec APP = helm diff --git a/README.md b/README.md index 66cb8579a..d1787ef1e 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.2-darwin-amd64.tar.gz) -- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.2-linux-amd64.tar.gz) -- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.2-linux-386.tar.gz) -- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.2-windows-amd64.tar.gz) +- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-darwin-amd64.tar.gz) +- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-linux-amd64.tar.gz) +- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-linux-386.tar.gz) +- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-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`. diff --git a/_proto/hapi/chart/metadata.proto b/_proto/hapi/chart/metadata.proto index 44ab82635..37d3ac348 100644 --- a/_proto/hapi/chart/metadata.proto +++ b/_proto/hapi/chart/metadata.proto @@ -25,6 +25,9 @@ message Maintainer { // Email is an optional email address to contact the named maintainer string email = 2; + + // Url is an optional URL to an address for the named maintainer + string url = 3; } // Metadata for a Chart file. This models the structure of a Chart.yaml file. diff --git a/cmd/helm/delete_test.go b/cmd/helm/delete_test.go index 4f1bd29a6..829d17906 100644 --- a/cmd/helm/delete_test.go +++ b/cmd/helm/delete_test.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" ) func TestDelete(t *testing.T) { @@ -33,28 +34,32 @@ func TestDelete(t *testing.T) { args: []string{"aeneas"}, flags: []string{}, expected: "", // Output of a delete is an empty string and exit 0. - resp: releaseMock(&releaseOptions{name: "aeneas"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, }, { name: "delete with timeout", args: []string{"aeneas"}, flags: []string{"--timeout", "120"}, expected: "", - resp: releaseMock(&releaseOptions{name: "aeneas"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, }, { name: "delete without hooks", args: []string{"aeneas"}, flags: []string{"--no-hooks"}, expected: "", - resp: releaseMock(&releaseOptions{name: "aeneas"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, }, { name: "purge", args: []string{"aeneas"}, flags: []string{"--purge"}, expected: "", - resp: releaseMock(&releaseOptions{name: "aeneas"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, }, { name: "delete without release", 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/get.go b/cmd/helm/get.go index fc5871f46..477f730d5 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -64,7 +64,7 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command { } get.release = args[0] if get.client == nil { - get.client = helm.NewClient(helm.Host(settings.TillerHost)) + get.client = newClient() } return get.run() }, @@ -72,9 +72,9 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command { cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") - cmd.AddCommand(newGetValuesCmd(nil, out)) - cmd.AddCommand(newGetManifestCmd(nil, out)) - cmd.AddCommand(newGetHooksCmd(nil, out)) + cmd.AddCommand(addFlagsTLS(newGetValuesCmd(nil, out))) + cmd.AddCommand(addFlagsTLS(newGetManifestCmd(nil, out))) + cmd.AddCommand(addFlagsTLS(newGetHooksCmd(nil, out))) return cmd } diff --git a/cmd/helm/get_hooks_test.go b/cmd/helm/get_hooks_test.go index 3e6132fbf..e578c2533 100644 --- a/cmd/helm/get_hooks_test.go +++ b/cmd/helm/get_hooks_test.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" ) func TestGetHooks(t *testing.T) { @@ -30,8 +31,9 @@ func TestGetHooks(t *testing.T) { { name: "get hooks with release", args: []string{"aeneas"}, - expected: mockHookTemplate, - resp: releaseMock(&releaseOptions{name: "aeneas"}), + expected: helm.MockHookTemplate, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, }, { name: "get hooks without args", diff --git a/cmd/helm/get_manifest_test.go b/cmd/helm/get_manifest_test.go index 4ba80e2ad..286b5628a 100644 --- a/cmd/helm/get_manifest_test.go +++ b/cmd/helm/get_manifest_test.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" ) func TestGetManifest(t *testing.T) { @@ -30,8 +31,9 @@ func TestGetManifest(t *testing.T) { { name: "get manifest with release", args: []string{"juno"}, - expected: mockManifest, - resp: releaseMock(&releaseOptions{name: "juno"}), + expected: helm.MockManifest, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"})}, }, { name: "get manifest without args", diff --git a/cmd/helm/get_test.go b/cmd/helm/get_test.go index 23b82c04d..a6e72987a 100644 --- a/cmd/helm/get_test.go +++ b/cmd/helm/get_test.go @@ -23,15 +23,17 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" ) func TestGetCmd(t *testing.T) { tests := []releaseCase{ { name: "get with a release", - resp: releaseMock(&releaseOptions{name: "thomas-guide"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), args: []string{"thomas-guide"}, - expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + mockHookTemplate + "\nMANIFEST:", + expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + helm.MockHookTemplate + "\nMANIFEST:", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, }, { name: "get requires release name arg", diff --git a/cmd/helm/get_values_test.go b/cmd/helm/get_values_test.go index 4032253fe..30b2ba4b8 100644 --- a/cmd/helm/get_values_test.go +++ b/cmd/helm/get_values_test.go @@ -23,15 +23,17 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" ) func TestGetValuesCmd(t *testing.T) { tests := []releaseCase{ { name: "get values with a release", - resp: releaseMock(&releaseOptions{name: "thomas-guide"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), args: []string{"thomas-guide"}, expected: "name: \"value\"", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, }, { name: "get values requires release name arg", diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 6fe08b07e..3810cfb8e 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -45,6 +45,10 @@ var ( tlsVerify bool // enable TLS and verify remote certificates tlsEnable bool // enable TLS + tlsCaCertDefault = "$HELM_HOME/ca.pem" + tlsCertDefault = "$HELM_HOME/cert.pem" + tlsKeyDefault = "$HELM_HOME/key.pem" + tillerTunnel *kube.Tunnel settings helm_env.EnvSettings ) @@ -174,7 +178,7 @@ func setupConnection(c *cobra.Command, args []string) error { return err } - settings.TillerHost = fmt.Sprintf("localhost:%d", tunnel.Local) + settings.TillerHost = fmt.Sprintf("127.0.0.1:%d", tunnel.Local) debug("Created tunnel using local port: '%d'\n", tunnel.Local) } @@ -216,7 +220,7 @@ func prettyError(err error) error { // configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. func configForContext(context string) (*rest.Config, error) { - config, err := kube.GetConfig(context).ClientConfig() + config, err := kube.GetConfig(context, settings.KubeConfig).ClientConfig() if err != nil { return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err) } @@ -239,16 +243,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. @@ -263,6 +267,16 @@ func newClient() helm.Interface { options := []helm.Option{helm.Host(settings.TillerHost)} if tlsVerify || tlsEnable { + if tlsCaCertFile == "" { + tlsCaCertFile = settings.Home.TLSCaCert() + } + if tlsCertFile == "" { + tlsCertFile = settings.Home.TLSCert() + } + if tlsKeyFile == "" { + tlsKeyFile = settings.Home.TLSKey() + } + debug("Key=%q, Cert=%q, CA=%q\n", tlsKeyFile, tlsCertFile, tlsCaCertFile) tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true} if tlsVerify { tlsopts.CaCertFile = tlsCaCertFile @@ -281,12 +295,6 @@ func newClient() helm.Interface { // addFlagsTLS adds the flags for supporting client side TLS to the // helm command (only those that invoke communicate to Tiller.) func addFlagsTLS(cmd *cobra.Command) *cobra.Command { - // defaults - var ( - tlsCaCertDefault = "$HELM_HOME/ca.pem" - tlsCertDefault = "$HELM_HOME/cert.pem" - tlsKeyDefault = "$HELM_HOME/key.pem" - ) // add flags cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file") diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 2a2af1359..7d74d66b7 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -21,115 +21,30 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" "os" "path/filepath" "regexp" "strings" "testing" - "github.com/golang/protobuf/ptypes/timestamp" "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm/helmpath" - "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/repo" ) -var mockHookTemplate = `apiVersion: v1 -kind: Job -metadata: - annotations: - "helm.sh/hooks": pre-install -` - -var mockManifest = `apiVersion: v1 -kind: Secret -metadata: - name: fixture -` - -type releaseOptions struct { - name string - version int32 - chart *chart.Chart - statusCode release.Status_Code - namespace string -} - -func releaseMock(opts *releaseOptions) *release.Release { - date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} - - name := opts.name - if name == "" { - name = "testrelease-" + string(rand.Intn(100)) - } - - var version int32 = 1 - if opts.version != 0 { - version = opts.version - } - - namespace := opts.namespace - if namespace == "" { - namespace = "default" - } - - ch := opts.chart - if opts.chart == nil { - ch = &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "foo", - Version: "0.1.0-beta.1", - }, - Templates: []*chart.Template{ - {Name: "templates/foo.tpl", Data: []byte(mockManifest)}, - }, - } - } - - scode := release.Status_DEPLOYED - if opts.statusCode > 0 { - scode = opts.statusCode - } - - return &release.Release{ - Name: name, - Info: &release.Info{ - FirstDeployed: &date, - LastDeployed: &date, - Status: &release.Status{Code: scode}, - Description: "Release mock", - }, - Chart: ch, - Config: &chart.Config{Raw: `name: "value"`}, - Version: version, - Namespace: namespace, - Hooks: []*release.Hook{ - { - Name: "pre-install-hook", - Kind: "Job", - Path: "pre-install-hook.yaml", - Manifest: mockHookTemplate, - LastRun: &date, - Events: []release.Hook_Event{release.Hook_PRE_INSTALL}, - }, - }, - Manifest: mockManifest, - } -} - // releaseCmd is a command that works with a FakeClient type releaseCmd func(c *helm.FakeClient, out io.Writer) *cobra.Command // runReleaseCases runs a set of release cases through the given releaseCmd. func runReleaseCases(t *testing.T, tests []releaseCase, rcmd releaseCmd) { + var buf bytes.Buffer for _, tt := range tests { c := &helm.FakeClient{ - Rels: []*release.Release{tt.resp}, + Rels: tt.rels, } cmd := rcmd(c, &buf) cmd.ParseFlags(tt.flags) @@ -154,6 +69,8 @@ type releaseCase struct { expected string err bool resp *release.Release + // Rels are the available releases at the start of the test. + rels []*release.Release } // tempHelmHome sets up a Helm Home in a temp dir. @@ -230,6 +147,7 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error { t.Logf("$HELM_HOME has been configured at %s.\n", settings.Home.String()) return nil + } func TestRootCmd(t *testing.T) { diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 08f1656f5..27c47ad3e 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -66,7 +66,7 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command { case len(args) == 0: return errReleaseRequired case his.helmc == nil: - his.helmc = helm.NewClient(helm.Host(settings.TillerHost)) + his.helmc = newClient() } his.rls = args[0] return his.run() diff --git a/cmd/helm/history_test.go b/cmd/helm/history_test.go index 3ff4b4a89..f193f6314 100644 --- a/cmd/helm/history_test.go +++ b/cmd/helm/history_test.go @@ -27,10 +27,10 @@ import ( func TestHistoryCmd(t *testing.T) { mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release { - return releaseMock(&releaseOptions{ - name: name, - version: vers, - statusCode: code, + return helm.ReleaseMock(&helm.MockReleaseOptions{ + Name: name, + Version: vers, + StatusCode: code, }) } diff --git a/cmd/helm/init.go b/cmd/helm/init.go index e331fb5da..2b1b9ddf6 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" @@ -27,6 +29,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" @@ -121,6 +124,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 } @@ -161,31 +168,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 0c21050b1..e2f1ada66 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -41,6 +41,7 @@ import ( "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/strvals" + "net/url" ) const installDesc = ` @@ -75,21 +76,22 @@ To check the generated manifests of a release without installing the chart, the '--debug' and '--dry-run' flags can be combined. This will still require a round-trip to the Tiller server. -If --verify is set, the chart MUST have a provenance file, and the provenenace -fall MUST pass all verification steps. +If --verify is set, the chart MUST have a provenance file, and the provenance +file MUST pass all verification steps. -There are four different ways you can express the chart you want to install: +There are five different ways you can express the chart you want to install: 1. By chart reference: helm install stable/mariadb 2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz 3. By path to an unpacked chart directory: helm install ./nginx 4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz +5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx CHART REFERENCES A chart reference is a convenient way of reference a chart in a chart repository. -When you use a chart reference ('stable/mariadb'), Helm will look in the local +When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local configuration for a chart repository named 'stable', and will then look for a chart in that repository whose name is 'mariadb'. It will install the latest version of that chart unless you also supply a version number with the @@ -118,6 +120,7 @@ type installCmd struct { wait bool repoURL string devel bool + depUp bool certFile string keyFile string @@ -159,8 +162,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 +178,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 +195,8 @@ 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.") + f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart") return cmd } @@ -230,7 +234,22 @@ func (i *installCmd) run() error { // As of Helm 2.4.0, this is treated as a stopping condition: // https://github.com/kubernetes/helm/issues/2209 if err := checkDependencies(chartRequested, req); err != nil { - return prettyError(err) + if i.depUp { + man := &downloader.Manager{ + Out: i.out, + ChartPath: i.chartPath, + HelmHome: settings.Home, + Keyring: defaultKeyring(), + SkipUpdate: false, + Getters: getter.All(settings), + } + if err := man.Update(); err != nil { + return prettyError(err) + } + } else { + return prettyError(err) + } + } } else if err != chartutil.ErrRequirementsNotFound { return fmt.Errorf("cannot load requirements: %v", err) @@ -316,8 +335,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 } @@ -424,7 +444,7 @@ func locateChartPath(repoURL, name, version string, verify bool, keyring, return filename, err } - return filename, fmt.Errorf("file %q not found", name) + return filename, fmt.Errorf("failed to download %q", name) } func generateName(nameTemplate string) (string, error) { @@ -441,7 +461,7 @@ func generateName(nameTemplate string) (string, error) { } func defaultNamespace() string { - if ns, _, err := kube.GetConfig(settings.KubeContext).Namespace(); err == nil { + if ns, _, err := kube.GetConfig(settings.KubeContext, settings.KubeConfig).Namespace(); err == nil { return ns } return "default" @@ -469,3 +489,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/install_test.go b/cmd/helm/install_test.go index c68deb482..aa828c6ce 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -24,7 +24,6 @@ import ( "testing" "github.com/spf13/cobra" - "k8s.io/helm/pkg/helm" ) @@ -36,46 +35,46 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/alpine"}, flags: strings.Split("--name aeneas", " "), expected: "aeneas", - resp: releaseMock(&releaseOptions{name: "aeneas"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), }, // Install, no hooks { name: "install without hooks", args: []string{"testdata/testcharts/alpine"}, flags: strings.Split("--name aeneas --no-hooks", " "), - expected: "juno", - resp: releaseMock(&releaseOptions{name: "juno"}), + expected: "aeneas", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), }, // Install, values from cli { name: "install with values", args: []string{"testdata/testcharts/alpine"}, - flags: strings.Split("--set foo=bar", " "), - resp: releaseMock(&releaseOptions{name: "virgil"}), + flags: strings.Split("--name virgil --set foo=bar", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), expected: "virgil", }, // Install, values from cli via multiple --set { name: "install with multiple values", args: []string{"testdata/testcharts/alpine"}, - flags: strings.Split("--set foo=bar", "--set bar=foo"), - resp: releaseMock(&releaseOptions{name: "virgil"}), + flags: strings.Split("--name virgil --set foo=bar --set bar=foo", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), expected: "virgil", }, // Install, values from yaml { name: "install with values", args: []string{"testdata/testcharts/alpine"}, - flags: strings.Split("-f testdata/testcharts/alpine/extra_values.yaml", " "), - resp: releaseMock(&releaseOptions{name: "virgil"}), + flags: strings.Split("--name virgil -f testdata/testcharts/alpine/extra_values.yaml", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), expected: "virgil", }, // Install, values from multiple yaml { name: "install with values", args: []string{"testdata/testcharts/alpine"}, - flags: strings.Split("-f testdata/testcharts/alpine/extra_values.yaml -f testdata/testcharts/alpine/more_values.yaml", " "), - resp: releaseMock(&releaseOptions{name: "virgil"}), + flags: strings.Split("--name virgil -f testdata/testcharts/alpine/extra_values.yaml -f testdata/testcharts/alpine/more_values.yaml", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), expected: "virgil", }, // Install, no charts @@ -90,23 +89,23 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/alpine"}, flags: strings.Split("--name aeneas --replace", " "), expected: "aeneas", - resp: releaseMock(&releaseOptions{name: "aeneas"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), }, // Install, with timeout { name: "install with a timeout", args: []string{"testdata/testcharts/alpine"}, - flags: strings.Split("--timeout 120", " "), + flags: strings.Split("--name foobar --timeout 120", " "), expected: "foobar", - resp: releaseMock(&releaseOptions{name: "foobar"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "foobar"}), }, // Install, with wait { name: "install with a wait", args: []string{"testdata/testcharts/alpine"}, - flags: strings.Split("--wait", " "), + flags: strings.Split("--name apollo --wait", " "), expected: "apollo", - resp: releaseMock(&releaseOptions{name: "apollo"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}), }, // Install, using the name-template { @@ -114,7 +113,7 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/alpine"}, flags: []string{"--name-template", "{{upper \"foobar\"}}"}, expected: "FOOBAR", - resp: releaseMock(&releaseOptions{name: "FOOBAR"}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "FOOBAR"}), }, // Install, perform chart verification along the way. { diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index c3f9eb484..9fa365a51 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, @@ -139,6 +168,7 @@ func generateDeployment(opts *Options) *v1beta1.Deployment { ImagePullPolicy: opts.pullPolicy(), Ports: []v1.ContainerPort{ {ContainerPort: 44134, Name: "tiller"}, + {ContainerPort: 44135, Name: "http"}, }, Env: []v1.EnvVar{ {Name: "TILLER_NAMESPACE", Value: opts.Namespace}, @@ -166,10 +196,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 +233,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..ad30557e2 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" ) @@ -206,6 +207,10 @@ func TestInstall(t *testing.T) { if i != image { t.Errorf("expected image = '%s', got '%s'", image, i) } + ports := len(obj.Spec.Template.Spec.Containers[0].Ports) + if ports != 2 { + t.Errorf("expected ports = 2, got '%d'", ports) + } return true, obj, nil }) fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { @@ -330,7 +335,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 +376,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 +424,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/list.go b/cmd/helm/list.go index f6cdaacfe..7c312a365 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -93,7 +93,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { list.filter = strings.Join(args, " ") } if list.client == nil { - list.client = helm.NewClient(helm.Host(settings.TillerHost)) + list.client = newClient() } return list.run() }, diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index 5660eb26a..587a7c39c 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -36,7 +36,7 @@ func TestListCmd(t *testing.T) { { name: "with a release", resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), }, expected: "thomas-guide", }, @@ -44,7 +44,7 @@ func TestListCmd(t *testing.T) { name: "list", args: []string{}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "atlas"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}), }, expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n", }, @@ -52,8 +52,8 @@ func TestListCmd(t *testing.T) { name: "list, one deployed, one failed", args: []string{"-q"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_FAILED}), - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_FAILED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), }, expected: "thomas-guide\natlas-guide", }, @@ -61,8 +61,8 @@ func TestListCmd(t *testing.T) { name: "with a release, multiple flags", args: []string{"--deleted", "--deployed", "--failed", "-q"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETED}), - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), }, // Note: We're really only testing that the flags parsed correctly. Which results are returned // depends on the backend. And until pkg/helm is done, we can't mock this. @@ -72,8 +72,8 @@ func TestListCmd(t *testing.T) { name: "with a release, multiple flags", args: []string{"--all", "-q"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETED}), - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), }, // See note on previous test. expected: "thomas-guide\natlas-guide", @@ -82,8 +82,8 @@ func TestListCmd(t *testing.T) { name: "with a release, multiple flags, deleting", args: []string{"--all", "-q"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETING}), - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETING}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), }, // See note on previous test. expected: "thomas-guide\natlas-guide", @@ -92,8 +92,8 @@ func TestListCmd(t *testing.T) { name: "namespace defined, multiple flags", args: []string{"--all", "-q", "--namespace test123"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", namespace: "test123"}), - releaseMock(&releaseOptions{name: "atlas-guide", namespace: "test321"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Namespace: "test123"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Namespace: "test321"}), }, // See note on previous test. expected: "thomas-guide", @@ -102,8 +102,8 @@ func TestListCmd(t *testing.T) { name: "with a pending release, multiple flags", args: []string{"--all", "-q"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_PENDING_INSTALL}), - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), }, expected: "thomas-guide\natlas-guide", }, @@ -111,10 +111,10 @@ func TestListCmd(t *testing.T) { name: "with a pending release, pending flag", args: []string{"--pending", "-q"}, resp: []*release.Release{ - releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_PENDING_INSTALL}), - releaseMock(&releaseOptions{name: "wild-idea", statusCode: release.Status_PENDING_UPGRADE}), - releaseMock(&releaseOptions{name: "crazy-maps", statusCode: release.Status_PENDING_ROLLBACK}), - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "wild-idea", StatusCode: release.Status_PENDING_UPGRADE}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-maps", StatusCode: release.Status_PENDING_ROLLBACK}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), }, expected: "thomas-guide\nwild-idea\ncrazy-maps", }, diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 44c34a315..ed44382c7 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -56,6 +56,7 @@ type packageCmd struct { key string keyring string version string + appVersion string destination string dependencyUpdate bool @@ -99,6 +100,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring") f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version") + f.StringVar(&pkg.appVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.") f.BoolVarP(&pkg.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "requirements.yaml" to dir "charts/" before packaging`) @@ -139,6 +141,11 @@ func (p *packageCmd) run() error { debug("Setting version to %s", p.version) } + if p.appVersion != "" { + ch.Metadata.AppVersion = p.appVersion + debug("Setting appVersion to %s", p.appVersion) + } + if filepath.Base(path) != ch.Metadata.Name { return fmt.Errorf("directory name (%s) and Chart.yaml name (%s) must match", filepath.Base(path), ch.Metadata.Name) } diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index eb6e02830..d338c29cd 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -25,6 +25,7 @@ import ( "github.com/spf13/cobra" + "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/proto/hapi/chart" ) @@ -200,6 +201,38 @@ func TestPackage(t *testing.T) { } } +func TestSetAppVersion(t *testing.T) { + var ch *chart.Chart + expectedAppVersion := "app-version-foo" + tmp, _ := ioutil.TempDir("", "helm-package-app-version-") + defer os.RemoveAll(tmp) + + c := newPackageCmd(&bytes.Buffer{}) + flags := map[string]string{ + "destination": tmp, + "app-version": expectedAppVersion, + } + setFlags(c, flags) + err := c.RunE(c, []string{"testdata/testcharts/alpine"}) + if err != nil { + t.Errorf("unexpected error %q", err) + } + + chartPath := filepath.Join(tmp, "alpine-0.1.0.tgz") + if fi, err := os.Stat(chartPath); err != nil { + t.Errorf("expected file %q, got err %q", chartPath, err) + } else if fi.Size() == 0 { + t.Errorf("file %q has zero bytes.", chartPath) + } + ch, err = chartutil.Load(chartPath) + if err != nil { + t.Errorf("unexpected error loading packaged chart: %v", err) + } + if ch.Metadata.AppVersion != expectedAppVersion { + t.Errorf("expected app-version %q, found %q", expectedAppVersion, ch.Metadata.AppVersion) + } +} + func setFlags(cmd *cobra.Command, flags map[string]string) { dest := cmd.Flags() for f, v := range flags { diff --git a/cmd/helm/plugin_install.go b/cmd/helm/plugin_install.go index aa75770fc..f36178808 100644 --- a/cmd/helm/plugin_install.go +++ b/cmd/helm/plugin_install.go @@ -33,11 +33,19 @@ type pluginInstallCmd struct { out io.Writer } +const pluginInstallDesc = ` +This command allows you to install a plugin from a url to a VCS repo or a local path. + +Example usage: + $ helm plugin install https://github.com/technosophos/helm-template +` + func newPluginInstallCmd(out io.Writer) *cobra.Command { pcmd := &pluginInstallCmd{out: out} cmd := &cobra.Command{ Use: "install [options] ...", Short: "install one or more Helm plugins", + Long: pluginInstallDesc, PreRunE: func(cmd *cobra.Command, args []string) error { return pcmd.complete(args) }, 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/reset_test.go b/cmd/helm/reset_test.go index d6febd132..b036e25c9 100644 --- a/cmd/helm/reset_test.go +++ b/cmd/helm/reset_test.go @@ -107,7 +107,7 @@ func TestReset_deployedReleases(t *testing.T) { var buf bytes.Buffer resp := []*release.Release{ - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), } c := &helm.FakeClient{ Rels: resp, @@ -139,7 +139,7 @@ func TestReset_forceFlag(t *testing.T) { var buf bytes.Buffer resp := []*release.Release{ - releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), } c := &helm.FakeClient{ Rels: resp, diff --git a/cmd/helm/search.go b/cmd/helm/search.go index 9f1398de3..cf924dcb0 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -109,10 +109,17 @@ func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, err } data := res[:0] + foundNames := map[string]bool{} for _, r := range res { + if _, found := foundNames[r.Name]; found { + continue + } v, err := semver.NewVersion(r.Chart.Version) if err != nil || constraint.Check(v) { data = append(data, r) + if !s.versions { + foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches + } } } @@ -125,9 +132,9 @@ func (s *searchCmd) formatSearchResults(res []*search.Result) string { } table := uitable.New() table.MaxColWidth = 50 - table.AddRow("NAME", "VERSION", "DESCRIPTION") + table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION") for _, r := range res { - table.AddRow(r.Name, r.Chart.Version, r.Chart.Description) + table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description) } return table.String() } @@ -149,7 +156,7 @@ func (s *searchCmd) buildIndex() (*search.Index, error) { continue } - i.AddRepo(n, ind, s.versions) + i.AddRepo(n, ind, s.versions || len(s.version) > 0) } return i, nil } diff --git a/cmd/helm/search_test.go b/cmd/helm/search_test.go index 26878fd0f..0fb54b5f9 100644 --- a/cmd/helm/search_test.go +++ b/cmd/helm/search_test.go @@ -34,18 +34,42 @@ func TestSearchCmd(t *testing.T) { { name: "search for 'maria', expect one match", args: []string{"maria"}, - expect: "NAME \tVERSION\tDESCRIPTION \ntesting/mariadb\t0.3.0 \tChart for MariaDB", + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/mariadb\t0.3.0 \t \tChart for MariaDB", }, { name: "search for 'alpine', expect two matches", args: []string{"alpine"}, - expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \tDeploy a basic Alpine Linux pod", + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod", }, { name: "search for 'alpine' with versions, expect three matches", args: []string{"alpine"}, flags: []string{"--versions"}, - expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \tDeploy a basic Alpine Linux pod", + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint, expect one match with version 0.1.0", + args: []string{"alpine"}, + flags: []string{"--version", ">= 0.1, < 0.2"}, + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint, expect one match with version 0.1.0", + args: []string{"alpine"}, + flags: []string{"--versions", "--version", ">= 0.1, < 0.2"}, + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint, expect one match with version 0.2.0", + args: []string{"alpine"}, + flags: []string{"--version", ">= 0.1"}, + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint and --versions, expect two matches", + args: []string{"alpine"}, + flags: []string{"--versions", "--version", ">= 0.1"}, + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", }, { name: "search for 'syzygy', expect no matches", @@ -56,7 +80,7 @@ func TestSearchCmd(t *testing.T) { name: "search for 'alp[a-z]+', expect two matches", args: []string{"alp[a-z]+"}, flags: []string{"--regexp"}, - expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \tDeploy a basic Alpine Linux pod", + expect: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod", regexp: true, }, { diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 36269c4b1..e5e9aa44c 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -67,7 +67,7 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command { } status.release = args[0] if status.client == nil { - status.client = helm.NewClient(helm.Host(settings.TillerHost)) + status.client = newClient() } return status.run() }, diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 92f6ed738..3ca364cfb 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "os" + "path" "path/filepath" "regexp" "strings" @@ -39,6 +40,10 @@ import ( tversion "k8s.io/helm/pkg/version" ) +const defaultDirectoryPermission = 0755 + +var whitespaceRegex = regexp.MustCompile(`^\s*$`) + const templateDesc = ` Render chart templates locally and display the output. @@ -64,6 +69,7 @@ type templateCmd struct { releaseName string renderFiles []string kubeVersion string + outputDir string } func newTemplateCmd(out io.Writer) *cobra.Command { @@ -88,6 +94,7 @@ func newTemplateCmd(out io.Writer) *cobra.Command { f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&t.kubeVersion, "kube-version", "", "override the Kubernetes version used as Capabilities.KubeVersion.Major/Minor (e.g. 1.7)") + f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") return cmd } @@ -126,6 +133,14 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error { } } + // verify that output-dir exists if provided + if t.outputDir != "" { + _, err = os.Stat(t.outputDir) + if os.IsNotExist(err) { + return fmt.Errorf("output-dir '%s' does not exist", t.outputDir) + } + } + if t.namespace == "" { t.namespace = defaultNamespace() } @@ -248,8 +263,61 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error { if strings.HasPrefix(b, "_") { continue } + + if t.outputDir != "" { + // blank template after execution + if whitespaceRegex.MatchString(data) { + continue + } + err = writeToFile(t.outputDir, m.Name, data) + if err != nil { + return err + } + continue + } fmt.Printf("---\n# Source: %s\n", m.Name) fmt.Println(data) } return nil } + +// write the to / +func writeToFile(outputDir string, name string, data string) error { + outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + + err := ensureDirectoryForFile(outfileName) + if err != nil { + return err + } + + f, err := os.Create(outfileName) + if err != nil { + return err + } + + defer f.Close() + + _, err = f.WriteString(fmt.Sprintf("##---\n# Source: %s\n%s", name, data)) + + if err != nil { + return err + } + + fmt.Printf("wrote %s\n", outfileName) + return nil +} + +// check if the directory exists to create file. creates if don't exists +func ensureDirectoryForFile(file string) error { + baseDir := path.Dir(file) + _, err := os.Stat(baseDir) + if err != nil && !os.IsNotExist(err) { + return err + } + + err = os.MkdirAll(baseDir, defaultDirectoryPermission) + if err != nil { + return err + } + return nil +} diff --git a/cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml b/cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml index df75878c1..96c98c38c 100644 --- a/cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml +++ b/cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml @@ -8,6 +8,7 @@ entries: sources: - https://github.com/kubernetes/helm version: 0.1.0 + appVersion: 1.2.3 description: Deploy a basic Alpine Linux pod keywords: [] maintainers: [] @@ -20,6 +21,7 @@ entries: sources: - https://github.com/kubernetes/helm version: 0.2.0 + appVersion: 2.3.4 description: Deploy a basic Alpine Linux pod keywords: [] maintainers: [] diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 4b852198a..7a7dfb0f6 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") @@ -151,7 +151,15 @@ func (u *upgradeCmd) run() error { // The returned error is a grpc.rpcError that wraps the message from the original error. // So we're stuck doing string matching against the wrapped error, which is nested somewhere // inside of the grpc.rpcError message. - _, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1)) + releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1)) + + if err == nil { + previousReleaseNamespace := releaseHistory.Releases[0].Namespace + if previousReleaseNamespace != u.namespace { + fmt.Fprintf(u.out, "WARNING: Namespace doesn't match with previous. Release will be deployed to %s\n", previousReleaseNamespace) + } + } + if err != nil && strings.Contains(err.Error(), driver.ErrReleaseNotFound(u.release).Error()) { fmt.Fprintf(u.out, "Release %q does not exist. Installing it now.\n", u.release) ic := &installCmd{ diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 1c5d1c856..187d3593e 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" ) func TestUpgradeCmd(t *testing.T) { @@ -43,9 +44,9 @@ func TestUpgradeCmd(t *testing.T) { t.Errorf("Error creating chart for upgrade: %v", err) } ch, _ := chartutil.Load(chartPath) - _ = releaseMock(&releaseOptions{ - name: "funny-bunny", - chart: ch, + _ = helm.ReleaseMock(&helm.MockReleaseOptions{ + Name: "funny-bunny", + Chart: ch, }) // update chart version @@ -94,61 +95,68 @@ func TestUpgradeCmd(t *testing.T) { { name: "upgrade a release", args: []string{"funny-bunny", chartPath}, - resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch}), expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch})}, }, { name: "upgrade a release with timeout", args: []string{"funny-bunny", chartPath}, flags: []string{"--timeout", "120"}, - resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 3, chart: ch2}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2}), expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2})}, }, { name: "upgrade a release with --reset-values", args: []string{"funny-bunny", chartPath}, flags: []string{"--reset-values", "true"}, - resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 4, chart: ch2}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2}), expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2})}, }, { name: "upgrade a release with --reuse-values", args: []string{"funny-bunny", chartPath}, flags: []string{"--reuse-values", "true"}, - resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 5, chart: ch2}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2}), expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2})}, }, { name: "install a release with 'upgrade --install'", args: []string{"zany-bunny", chartPath}, flags: []string{"-i"}, - resp: releaseMock(&releaseOptions{name: "zany-bunny", version: 1, chart: ch}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch}), expected: "Release \"zany-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch})}, }, { name: "install a release with 'upgrade --install' and timeout", args: []string{"crazy-bunny", chartPath}, flags: []string{"-i", "--timeout", "120"}, - resp: releaseMock(&releaseOptions{name: "crazy-bunny", version: 1, chart: ch}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch}), expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch})}, }, { name: "upgrade a release with wait", args: []string{"crazy-bunny", chartPath}, flags: []string{"--wait"}, - resp: releaseMock(&releaseOptions{name: "crazy-bunny", version: 2, chart: ch2}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2}), expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2})}, }, { name: "upgrade a release with missing dependencies", args: []string{"bonkers-bunny", missingDepsPath}, - resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "bonkers-bunny", Version: 1, Chart: ch3}), err: true, }, { name: "upgrade a release with bad dependencies", args: []string{"bonkers-bunny", badDepsPath}, - resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "bonkers-bunny", Version: 1, Chart: ch3}), err: true, }, } diff --git a/cmd/rudder/rudder.go b/cmd/rudder/rudder.go index e1ba4736a..30ece3998 100644 --- a/cmd/rudder/rudder.go +++ b/cmd/rudder/rudder.go @@ -26,9 +26,9 @@ import ( "google.golang.org/grpc/grpclog" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "github.com/spf13/pflag" "k8s.io/helm/pkg/kube" rudderAPI "k8s.io/helm/pkg/proto/hapi/rudder" - "k8s.io/helm/pkg/rudder" "k8s.io/helm/pkg/tiller" "k8s.io/helm/pkg/version" ) @@ -36,24 +36,43 @@ import ( var kubeClient *kube.Client var clientset internalclientset.Interface +type options struct { + listen string +} + +func (opts *options) registerFlags() { + pflag.StringVarP(&opts.listen, "listen", "l", "127.0.0.1:10001", + "Socket for rudder grpc server (default: 127.0.0.1:10001).") +} + +func (opts *options) parseFlags() { + pflag.Parse() +} + +func (opts *options) regAndParseFlags() { + opts.registerFlags() + opts.parseFlags() +} + func main() { + opts := new(options) + opts.regAndParseFlags() var err error kubeClient = kube.New(nil) clientset, err = kubeClient.ClientSet() if err != nil { grpclog.Fatalf("Cannot initialize Kubernetes connection: %s", err) } - - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", rudder.GrpcPort)) + grpclog.Printf("Creating tcp socket on %s\n", opts.listen) + lis, err := net.Listen("tcp", opts.listen) if err != nil { grpclog.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() rudderAPI.RegisterReleaseModuleServiceServer(grpcServer, &ReleaseModuleServiceServer{}) - grpclog.Print("Server starting") + grpclog.Printf("Starting server on %s\n", opts.listen) grpcServer.Serve(lis) - grpclog.Print("Server started") } // ReleaseModuleServiceServer provides implementation for rudderAPI.ReleaseModuleServiceServer diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 2a4cf066e..96eeddacb 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -28,10 +28,12 @@ import ( "path/filepath" "strconv" "strings" + "time" goprom "github.com/grpc-ecosystem/go-grpc-prometheus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" "k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/proto/hapi/services" @@ -57,6 +59,7 @@ const ( storageMemory = "memory" storageConfigMap = "configmap" + storageSecret = "secret" probeAddr = ":44135" traceAddr = ":44136" @@ -68,7 +71,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") @@ -76,6 +79,7 @@ var ( certFile = flag.String("tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file") caCertFile = flag.String("tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA") maxHistory = flag.Int("history-max", historyMaxFromEnv(), "maximum number of releases kept in release history, with 0 meaning no limit") + printVersion = flag.Bool("version", false, "print the version number") // rootServer is the root gRPC server. // @@ -91,8 +95,14 @@ var ( ) func main() { + // TODO: use spf13/cobra for tiller instead of flags flag.Parse() + if *printVersion { + fmt.Println(version.GetVersion()) + os.Exit(0) + } + if *enableTracing { log.SetFlags(log.Lshortfile) } @@ -117,6 +127,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 { @@ -141,6 +157,10 @@ func start() { logger.Fatalf("Could not create server TLS configuration: %v", err) } opts = append(opts, grpc.Creds(credentials.NewTLS(cfg))) + opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionIdle: 10 * time.Minute, + // If needed, we can configure the max connection age + })) } rootServer = tiller.NewServer(opts...) @@ -218,7 +238,11 @@ func tlsOptions() tlsutil.Options { opts := tlsutil.Options{CertFile: *certFile, KeyFile: *keyFile} if *tlsVerify { opts.CaCertFile = *caCertFile - opts.ClientAuth = tls.VerifyClientCertIfGiven + + // We want to force the client to not only provide a cert, but to + // provide a cert that we can validate. + // http://www.bite-code.com/2015/06/25/tls-mutual-auth-in-golang/ + opts.ClientAuth = tls.RequireAndVerifyClientCert } return opts } diff --git a/docs/chart_best_practices/README.md b/docs/chart_best_practices/README.md index 22908871d..58cc65407 100644 --- a/docs/chart_best_practices/README.md +++ b/docs/chart_best_practices/README.md @@ -16,5 +16,6 @@ may find that their internal interests override our suggestions here. - [Labels and Annotations](labels.md): Helm has a _heritage_ of labeling and annotating. - Kubernetes Resources: - [Pods and Pod Specs](pods.md): See the best practices for working with pod specifications. + - [Role-Based Access Control](rbac.md): Guidance on creating and using service accounts, roles, and role bindings. - [Third Party Resources](third_party_resources.md): Third Party Resources (TPRs) have their own associated best practices. diff --git a/docs/chart_best_practices/pods.md b/docs/chart_best_practices/pods.md index de679f9f5..3f26b0253 100644 --- a/docs/chart_best_practices/pods.md +++ b/docs/chart_best_practices/pods.md @@ -30,12 +30,21 @@ image: "{{ .Values.redisImage }}:{{ .Values.redisTag }}" ## ImagePullPolicy -The `imagePullPolicy` should default to an empty value, but allow users to override it: +`helm create` sets the `imagePullPolicy` to `IfNotPresent` by default by doing the following in your `deployment.yaml`: ```yaml -imagePullPolicy: {{ default "" .Values.imagePullPolicy | quote }} +imagePullPolicy: {{ .Values.image.pullPolicy }} ``` +And `values.yaml`: + +```yaml +pullPolicy: IfNotPresent +``` + +Similarly, Kubernetes defaults the `imagePullPolicy` to `IfNotPresent` if it is not defined at all. If you want a value other than `IfNotPresent`, simply update the value in `values.yaml` to your desired value. + + ## PodTemplates Should Declare Selectors All PodTemplate sections should specify a selector. For example: diff --git a/docs/chart_best_practices/rbac.md b/docs/chart_best_practices/rbac.md new file mode 100644 index 000000000..4b84b3d42 --- /dev/null +++ b/docs/chart_best_practices/rbac.md @@ -0,0 +1,28 @@ +# Role-Based Access Control + +This part of the Best Practices Guide discusses the creation and formatting of RBAC resources in chart manifests. + +RBAC resources are: + +- ServiceAccount (namespaced) +- Role (namespaced) +- ClusterRole +- RoleBinding (namespaced) +- ClusterRoleBinding + +## RBAC-related values + +RBAC-related values in a chart should be namespaced under an `rbac` top-level key. At a minimum this key should contain these sub-keys (explained below): + +- `create` +- `serviceAccountName` + +Other keys under `rbac` may also be listed and used as well. + +## RBAC Resources Should be Created by Default + +`rbac.create` should be a boolean value controlling whether RBAC resources are created. The default should be `true`. Users who wish to manage RBAC access controls themselves can set this value to `false` (in which case see below). + +## Using RBAC Resources + +`rbac.serviceAccountName` should set the name of the ServiceAccount to be used by access-controlled resources created by the chart. If `rbac.create` is true, then a ServiceAccount with this name should be created. If `rbac.create` is false, then it should not be created, but it should still be associated with the same resources so that manually-created RBAC resources created later that reference it will function correctly. (Note that this effectively makes `rbac.serviceAccountName` a required value in these charts.) 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/accessing_files.md b/docs/chart_template_guide/accessing_files.md index 3911af830..c547babdd 100644 --- a/docs/chart_template_guide/accessing_files.md +++ b/docs/chart_template_guide/accessing_files.md @@ -7,6 +7,7 @@ Helm provides access to files through the `.Files` object. Before we get going w - It is okay to add extra files to your Helm chart. These files will be bundled and sent to Tiller. Be careful, though. Charts must be smaller than 1M because of the storage limitations of Kubernetes objects. - Some files cannot be accessed through the `.Files` object, usually for security reasons. - Files in `templates/` cannot be accessed. + - Files excluded using `.helmignore` cannot be accessed. - Charts do not preserve UNIX mode information, so file-level permissions will have no impact on the availability of a file when it comes to the `.Files` object. diff --git a/docs/chart_template_guide/builtin_objects.md b/docs/chart_template_guide/builtin_objects.md index 2e1a5d8a3..11982229b 100644 --- a/docs/chart_template_guide/builtin_objects.md +++ b/docs/chart_template_guide/builtin_objects.md @@ -27,7 +27,7 @@ In the previous section, we use `{{.Release.Name}}` to insert the name of a rele - `Capabilities.TillerVersion` provides a way to look up the Tiller version. It has the following values: `SemVer`, `GitCommit`, and `GitTreeState`. - `Template`: Contains information about the current template that is being executed - `Name`: A namespaced filepath to the current template (e.g. `mychart/templates/mytemplate.yaml`) - - `BasePath`: The namespaced path to the templates directory of the current chart (e.g. `mychart/templates`). This can be used to [include template files](https://github.com/kubernetes/helm/blob/master/docs/charts_tips_and_tricks.md#automatically-roll-deployments-when-configmaps-or-secrets-change) + - `BasePath`: The namespaced path to the templates directory of the current chart (e.g. `mychart/templates`). The values are available to any top-level template. As we will see later, this does not necessarily mean that they will be available _everywhere_. 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.md b/docs/charts.md index 81c3d44f5..1bcafb64b 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -36,6 +36,8 @@ wordpress/ Helm reserves use of the `charts/` and `templates/` directories, and of the listed file names. Other files will be left as they are. +While the `charts` and `template` directories are optional there must be at least one chart dependency or template file for the chart to be valid. + ## The Chart.yaml File The `Chart.yaml` file is required for a chart. It contains the following fields: @@ -52,6 +54,7 @@ sources: maintainers: # (optional) - name: The maintainer's name (required for each maintainer) email: The maintainer's email (optional for each maintainer) + url: A URL for the maintainer (optional for each maintainer) engine: gotpl # The name of the template engine (optional, defaults to gotpl) icon: A URL to an SVG or PNG image to be used as an icon (optional). appVersion: The version of the app that this contains (optional). This needn't be SemVer. @@ -449,6 +452,45 @@ directory. **TIP:** _To drop a dependency into your `charts/` directory, use the `helm fetch` command_ +### Operational aspects of using dependencies + +The above sections explain how to specify chart dependencies, but how does this affect +chart installation using `helm install` and `helm upgrade`? + +Suppose that a chart named "A" creates the following Kubernetes objects + +- namespace "A-Namespace" +- statefulset "A-StatefulSet" +- service "A-Service" + +Furthermore, A is dependent on chart B that creates objects + +- namespace "B-Namespace" +- replicaset "B-ReplicaSet" +- service "B-Service" + +After installation/upgrade of chart A a single Helm release is created/modified. The release will +create/update all of the above Kubernetes objects in the following order: + +- A-Namespace +- B-Namespace +- A-StatefulSet +- B-ReplicaSet +- A-Service +- B-Service + +This is because when Helm installs/upgrades charts, +the Kubernetes objects from the charts and all its dependencies are + +- aggregrated into a single set; then +- sorted by type followed by name; and then +- created/updated in that order. + +Hence a single release is created with all the objects for the chart and its dependencies. + +The install order of Kubernetes types is given by the enumeration InstallOrder in kind_sorter.go +(see [the Helm source file](https://github.com/kubernetes/helm/blob/master/pkg/tiller/kind_sorter.go#L26). + ## Templates and Values Helm Chart templates are written in the @@ -548,9 +590,10 @@ sensitive_. `Chart.Maintainers`. - `Files`: A map-like object containing all non-special files in the chart. This will not give you access to templates, but will give you access to additional - files that are present. Files can be accessed using `{{index .Files "file.name"}}` - or using the `{{.Files.Get name}}` or `{{.Files.GetString name}}` functions. You can - also access the contents of the file as `[]byte` using `{{.Files.GetBytes}}` + files that are present (unless they are excluded using `.helmignore`). Files can be + accessed using `{{index .Files "file.name"}}` or using the `{{.Files.Get name}}` or + `{{.Files.GetString name}}` functions. You can also access the contents of the file + as `[]byte` using `{{.Files.GetBytes}}` - `Capabilities`: A map-like object that contains information about the versions of Kubernetes (`{{.Capabilities.KubeVersion}}`, Tiller (`{{.Capabilities.TillerVersion}}`, and the supported Kubernetes API versions diff --git a/docs/charts_tips_and_tricks.md b/docs/charts_tips_and_tricks.md index 0ed1ab83e..b1df146db 100644 --- a/docs/charts_tips_and_tricks.md +++ b/docs/charts_tips_and_tricks.md @@ -18,11 +18,11 @@ We also added two special template functions: `include` and `required`. The `inc function allows you to bring in another template, and then pass the results to other template functions. -For example, this template snippet includes a template called `mytpl.tpl`, then +For example, this template snippet includes a template called `mytpl`, then lowercases the result, then wraps that in double quotes. ```yaml -value: {{include "mytpl.tpl" . | lower | quote}} +value: {{include "mytpl" . | lower | quote}} ``` The `required` function allows you to declare a particular @@ -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 @@ -105,9 +134,8 @@ be updated with a subsequent `helm upgrade`, but if the deployment spec itself didn't change the application keeps running with the old configuration resulting in an inconsistent deployment. -The `sha256sum` function can be used together with the `include` -function to ensure a deployments template section is updated if another -spec changes: +The `sha256sum` function can be used to ensure a deployment's +annotation section is updated if another file changes: ```yaml kind: Deployment @@ -115,10 +143,13 @@ spec: template: metadata: annotations: - checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} [...] ``` +See also the `helm upgrade --recreate-pods` flag for a slightly +different way of addressing this issue. + ## Tell Tiller Not To Delete a Resource Sometimes there are resources that should not be deleted when Helm runs a @@ -200,3 +231,10 @@ cryptographic keys, and so on. These are fine to use. But be aware that during upgrades, templates are re-executed. When a template run generates data that differs from the last run, that will trigger an update of that resource. + +## Upgrade a release idempotently + +In order to use the same command when installing and upgrading a release, use the following comand: +```shell +helm upgrade --install --values +``` diff --git a/docs/developers.md b/docs/developers.md index 9720b99f0..ec0b9f91b 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -5,10 +5,9 @@ Helm and Tiller. ## Prerequisites -- Go 1.6.0 or later -- Glide 0.12.0 or later -- kubectl 1.2 or later -- A Kubernetes cluster (optional) +- The latest version of Go +- The latest version of Glide +- A Kubernetes cluster w/ kubectl (optional) - The gRPC toolchain - Git - Mercurial 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.md b/docs/helm/helm.md index 36e41835e..caa4301a0 100644 --- a/docs/helm/helm.md +++ b/docs/helm/helm.md @@ -33,9 +33,10 @@ Environment: ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` @@ -67,4 +68,4 @@ Environment: * [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid * [helm version](helm_version.md) - print the client/server version information -###### Auto generated by spf13/cobra on 8-Aug-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_completion.md b/docs/helm/helm_completion.md index 60cc7497c..9fe91d52a 100644 --- a/docs/helm/helm_completion.md +++ b/docs/helm/helm_completion.md @@ -25,13 +25,14 @@ helm completion SHELL ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_create.md b/docs/helm/helm_create.md index 73d435d6c..fa047ee23 100644 --- a/docs/helm/helm_create.md +++ b/docs/helm/helm_create.md @@ -44,13 +44,14 @@ helm create NAME ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_delete.md b/docs/helm/helm_delete.md index 91fc51c22..bf3adb6b2 100644 --- a/docs/helm/helm_delete.md +++ b/docs/helm/helm_delete.md @@ -35,13 +35,14 @@ helm delete [flags] RELEASE_NAME [...] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_dependency.md b/docs/helm/helm_dependency.md index d7259c2f6..673c2c384 100644 --- a/docs/helm/helm_dependency.md +++ b/docs/helm/helm_dependency.md @@ -58,9 +58,10 @@ for this case. ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` @@ -70,4 +71,4 @@ for this case. * [helm dependency list](helm_dependency_list.md) - list the dependencies for the given chart * [helm dependency update](helm_dependency_update.md) - update charts/ based on the contents of requirements.yaml -###### Auto generated by spf13/cobra on 11-Jul-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_dependency_build.md b/docs/helm/helm_dependency_build.md index d99eb0ea3..0c9d1f97b 100644 --- a/docs/helm/helm_dependency_build.md +++ b/docs/helm/helm_dependency_build.md @@ -31,13 +31,14 @@ helm dependency build [flags] CHART ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm dependency](helm_dependency.md) - manage a chart's dependencies -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_dependency_list.md b/docs/helm/helm_dependency_list.md index 74e4fb80e..a558356be 100644 --- a/docs/helm/helm_dependency_list.md +++ b/docs/helm/helm_dependency_list.md @@ -23,13 +23,14 @@ helm dependency list [flags] CHART ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm dependency](helm_dependency.md) - manage a chart's dependencies -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_dependency_update.md b/docs/helm/helm_dependency_update.md index 2fa782c34..191a69644 100644 --- a/docs/helm/helm_dependency_update.md +++ b/docs/helm/helm_dependency_update.md @@ -36,13 +36,14 @@ helm dependency update [flags] CHART ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm dependency](helm_dependency.md) - manage a chart's dependencies -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_fetch.md b/docs/helm/helm_fetch.md index 56996774e..5b4127c0a 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 @@ -45,13 +45,14 @@ helm fetch [flags] [chart URL | repo/chartname] [...] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 17-Aug-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_get.md b/docs/helm/helm_get.md index 418566c78..ce6718b28 100644 --- a/docs/helm/helm_get.md +++ b/docs/helm/helm_get.md @@ -37,9 +37,10 @@ helm get [flags] RELEASE_NAME ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` @@ -49,4 +50,4 @@ helm get [flags] RELEASE_NAME * [helm get manifest](helm_get_manifest.md) - download the manifest for a named release * [helm get values](helm_get_values.md) - download the values file for a named release -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_get_hooks.md b/docs/helm/helm_get_hooks.md index c90d5a5e4..a2fb36acd 100644 --- a/docs/helm/helm_get_hooks.md +++ b/docs/helm/helm_get_hooks.md @@ -18,20 +18,26 @@ helm get hooks [flags] RELEASE_NAME ### Options ``` - --revision int32 get the named release with revision + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --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 ``` ### Options inherited from parent commands ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/helm/helm_get_manifest.md b/docs/helm/helm_get_manifest.md index 294ae9528..1cf712d9b 100644 --- a/docs/helm/helm_get_manifest.md +++ b/docs/helm/helm_get_manifest.md @@ -20,20 +20,26 @@ helm get manifest [flags] RELEASE_NAME ### Options ``` - --revision int32 get the named release with revision + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --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 ``` ### Options inherited from parent commands ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/helm/helm_get_values.md b/docs/helm/helm_get_values.md index 7067d8ac0..1e57a2303 100644 --- a/docs/helm/helm_get_values.md +++ b/docs/helm/helm_get_values.md @@ -16,21 +16,27 @@ helm get values [flags] RELEASE_NAME ### Options ``` - -a, --all dump all (computed) values - --revision int32 get the named release with revision + -a, --all dump all (computed) values + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --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 ``` ### Options inherited from parent commands ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/helm/helm_history.md b/docs/helm/helm_history.md index afd411515..84792081a 100644 --- a/docs/helm/helm_history.md +++ b/docs/helm/helm_history.md @@ -40,13 +40,14 @@ helm history [flags] RELEASE_NAME ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_home.md b/docs/helm/helm_home.md index c5c4f50e3..069f6a162 100644 --- a/docs/helm/helm_home.md +++ b/docs/helm/helm_home.md @@ -18,13 +18,14 @@ helm home ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_init.md b/docs/helm/helm_init.md index 2d224c7f1..eeee9b013 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") @@ -55,13 +58,14 @@ helm init ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### 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 7-Nov-2017 diff --git a/docs/helm/helm_inspect.md b/docs/helm/helm_inspect.md index a740c3b27..7fc56cacc 100644 --- a/docs/helm/helm_inspect.md +++ b/docs/helm/helm_inspect.md @@ -32,9 +32,10 @@ helm inspect [CHART] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` @@ -43,4 +44,4 @@ helm inspect [CHART] * [helm inspect chart](helm_inspect_chart.md) - shows inspect chart * [helm inspect values](helm_inspect_values.md) - shows inspect values -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_inspect_chart.md b/docs/helm/helm_inspect_chart.md index e9d5bc08c..e1e88fbbe 100644 --- a/docs/helm/helm_inspect_chart.md +++ b/docs/helm/helm_inspect_chart.md @@ -30,13 +30,14 @@ helm inspect chart [CHART] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm inspect](helm_inspect.md) - inspect a chart -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_inspect_values.md b/docs/helm/helm_inspect_values.md index a151c404b..348336b8f 100644 --- a/docs/helm/helm_inspect_values.md +++ b/docs/helm/helm_inspect_values.md @@ -30,13 +30,14 @@ helm inspect values [CHART] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm inspect](helm_inspect.md) - inspect a chart -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_install.md b/docs/helm/helm_install.md index a8f00e434..f3a8ee06d 100644 --- a/docs/helm/helm_install.md +++ b/docs/helm/helm_install.md @@ -37,21 +37,22 @@ To check the generated manifests of a release without installing the chart, the '--debug' and '--dry-run' flags can be combined. This will still require a round-trip to the Tiller server. -If --verify is set, the chart MUST have a provenance file, and the provenenace -fall MUST pass all verification steps. +If --verify is set, the chart MUST have a provenance file, and the provenance +file MUST pass all verification steps. -There are four different ways you can express the chart you want to install: +There are five different ways you can express the chart you want to install: 1. By chart reference: helm install stable/mariadb 2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz 3. By path to an unpacked chart directory: helm install ./nginx 4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz +5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx CHART REFERENCES A chart reference is a convenient way of reference a chart in a chart repository. -When you use a chart reference ('stable/mariadb'), Helm will look in the local +When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local configuration for a chart repository named 'stable', and will then look for a chart in that repository whose name is 'mariadb'. It will install the latest version of that chart unless you also supply a version number with the @@ -70,7 +71,8 @@ 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. + --dep-up run helm dependency update before installing the chart + --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 +89,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 @@ -97,13 +99,14 @@ helm install [CHART] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 15-Aug-2017 +###### Auto generated by spf13/cobra on 23-Nov-2017 diff --git a/docs/helm/helm_lint.md b/docs/helm/helm_lint.md index 618d5c5e2..a07a43627 100644 --- a/docs/helm/helm_lint.md +++ b/docs/helm/helm_lint.md @@ -28,13 +28,14 @@ helm lint [flags] PATH ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_list.md b/docs/helm/helm_list.md index f32233756..b1dcb19de 100644 --- a/docs/helm/helm_list.md +++ b/docs/helm/helm_list.md @@ -62,13 +62,14 @@ helm list [flags] [FILTER] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 12-Jul-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_package.md b/docs/helm/helm_package.md index 88374b737..71960f41e 100644 --- a/docs/helm/helm_package.md +++ b/docs/helm/helm_package.md @@ -23,6 +23,7 @@ helm package [flags] [CHART_PATH] [...] ### Options ``` + --app-version string set the appVersion on the chart to this version -u, --dependency-update update dependencies from "requirements.yaml" to dir "charts/" before packaging -d, --destination string location to write the chart. (default ".") --key string name of the key to use when signing. Used if --sign is true @@ -36,13 +37,14 @@ helm package [flags] [CHART_PATH] [...] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 24-Nov-2017 diff --git a/docs/helm/helm_plugin.md b/docs/helm/helm_plugin.md index 79653f5de..5a636724c 100644 --- a/docs/helm/helm_plugin.md +++ b/docs/helm/helm_plugin.md @@ -13,9 +13,10 @@ Manage client-side Helm plugins. ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` @@ -26,4 +27,4 @@ Manage client-side Helm plugins. * [helm plugin remove](helm_plugin_remove.md) - remove one or more Helm plugins * [helm plugin update](helm_plugin_update.md) - update one or more Helm plugins -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_plugin_install.md b/docs/helm/helm_plugin_install.md index 635e68fff..f93378b7b 100644 --- a/docs/helm/helm_plugin_install.md +++ b/docs/helm/helm_plugin_install.md @@ -5,7 +5,12 @@ install one or more Helm plugins ### Synopsis -install one or more Helm plugins + +This command allows you to install a plugin from a url to a VCS repo or a local path. + +Example usage: + $ helm plugin install https://github.com/technosophos/helm-template + ``` helm plugin install [options] ... @@ -21,13 +26,14 @@ helm plugin install [options] ... ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 20-Nov-2017 diff --git a/docs/helm/helm_plugin_list.md b/docs/helm/helm_plugin_list.md index 823f4369b..403ef1a6f 100644 --- a/docs/helm/helm_plugin_list.md +++ b/docs/helm/helm_plugin_list.md @@ -15,13 +15,14 @@ helm plugin list ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_plugin_remove.md b/docs/helm/helm_plugin_remove.md index 8d02ee751..6c4530ce1 100644 --- a/docs/helm/helm_plugin_remove.md +++ b/docs/helm/helm_plugin_remove.md @@ -15,13 +15,14 @@ helm plugin remove ... ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_plugin_update.md b/docs/helm/helm_plugin_update.md index 9817c052b..cbfe9ad5e 100644 --- a/docs/helm/helm_plugin_update.md +++ b/docs/helm/helm_plugin_update.md @@ -15,13 +15,14 @@ helm plugin update ... ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_repo.md b/docs/helm/helm_repo.md index 06cc1f4b2..3f184e875 100644 --- a/docs/helm/helm_repo.md +++ b/docs/helm/helm_repo.md @@ -17,9 +17,10 @@ Example usage: ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` @@ -31,4 +32,4 @@ Example usage: * [helm repo remove](helm_repo_remove.md) - remove a chart repository * [helm repo update](helm_repo_update.md) - update information of available charts locally from chart repositories -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_repo_add.md b/docs/helm/helm_repo_add.md index 34e1c5b8c..c16797c63 100644 --- a/docs/helm/helm_repo_add.md +++ b/docs/helm/helm_repo_add.md @@ -24,13 +24,14 @@ helm repo add [flags] [NAME] [URL] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_repo_index.md b/docs/helm/helm_repo_index.md index 533e38848..aece31367 100644 --- a/docs/helm/helm_repo_index.md +++ b/docs/helm/helm_repo_index.md @@ -31,13 +31,14 @@ helm repo index [flags] [DIR] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_repo_list.md b/docs/helm/helm_repo_list.md index f4ab740af..484ce3c68 100644 --- a/docs/helm/helm_repo_list.md +++ b/docs/helm/helm_repo_list.md @@ -15,13 +15,14 @@ helm repo list [flags] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_repo_remove.md b/docs/helm/helm_repo_remove.md index 0198f9618..b34cee61c 100644 --- a/docs/helm/helm_repo_remove.md +++ b/docs/helm/helm_repo_remove.md @@ -15,13 +15,14 @@ helm repo remove [flags] [NAME] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_repo_update.md b/docs/helm/helm_repo_update.md index 61cca84eb..0c9fb6efd 100644 --- a/docs/helm/helm_repo_update.md +++ b/docs/helm/helm_repo_update.md @@ -21,13 +21,14 @@ helm repo update ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_reset.md b/docs/helm/helm_reset.md index 78218a4e9..dfb78c376 100644 --- a/docs/helm/helm_reset.md +++ b/docs/helm/helm_reset.md @@ -31,13 +31,14 @@ helm reset ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 27-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_rollback.md b/docs/helm/helm_rollback.md index 53baef487..a2fedc559 100644 --- a/docs/helm/helm_rollback.md +++ b/docs/helm/helm_rollback.md @@ -37,13 +37,14 @@ helm rollback [flags] [RELEASE] [REVISION] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_search.md b/docs/helm/helm_search.md index 8522e4867..1dc7e38ba 100644 --- a/docs/helm/helm_search.md +++ b/docs/helm/helm_search.md @@ -28,13 +28,14 @@ helm search [keyword] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_serve.md b/docs/helm/helm_serve.md index 989694103..4ce8a997a 100644 --- a/docs/helm/helm_serve.md +++ b/docs/helm/helm_serve.md @@ -36,13 +36,14 @@ helm serve ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_status.md b/docs/helm/helm_status.md index 82fa9faec..2331558c0 100644 --- a/docs/helm/helm_status.md +++ b/docs/helm/helm_status.md @@ -35,13 +35,14 @@ helm status [flags] RELEASE_NAME ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_template.md b/docs/helm/helm_template.md index adfffa269..983652dba 100644 --- a/docs/helm/helm_template.md +++ b/docs/helm/helm_template.md @@ -31,6 +31,7 @@ helm template [flags] CHART --name-template string specify template used to name the release --namespace string namespace to install the release into --notes show the computed NOTES.txt file as well + --output-dir string writes the executed templates to files in output-dir instead of stdout --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) ``` @@ -39,13 +40,14 @@ helm template [flags] CHART ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 11-Sep-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/helm/helm_test.md b/docs/helm/helm_test.md index ae6f3e0cd..b46d43e64 100644 --- a/docs/helm/helm_test.md +++ b/docs/helm/helm_test.md @@ -32,13 +32,14 @@ helm test [RELEASE] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_upgrade.md b/docs/helm/helm_upgrade.md index fdf95854f..5f4fde96c 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 @@ -67,13 +67,14 @@ helm upgrade [RELEASE] [CHART] ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 15-Aug-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_verify.md b/docs/helm/helm_verify.md index cd967c52e..74c3ee488 100644 --- a/docs/helm/helm_verify.md +++ b/docs/helm/helm_verify.md @@ -30,13 +30,14 @@ helm verify [flags] PATH ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/helm/helm_version.md b/docs/helm/helm_version.md index 3b55fafa0..1e46ceb18 100644 --- a/docs/helm/helm_version.md +++ b/docs/helm/helm_version.md @@ -44,13 +44,14 @@ helm version ``` --debug enable verbose output - --home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm") + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") --host string address of Tiller. Overrides $HELM_HOST --kube-context string name of the kubeconfig context to use + --kubeconfig string path to kubeconfig file. Overrides $KUBECONFIG --tiller-namespace string namespace of Tiller (default "kube-system") ``` ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 23-Jun-2017 +###### Auto generated by spf13/cobra on 7-Nov-2017 diff --git a/docs/index.md b/docs/index.md index b787463a6..4ca93bd1f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,8 @@ - [Frequently Asked Questions](install_faq.md) - [Using Helm](using_helm.md) - Learn the Helm tools - [Plugins](plugins.md) + - [Role-based Access Control](rbac.md) + - [TLS/SSL for Helm and Tiller](tiller_ssl.md) - Use Helm-to-Tiller encryption - [Developing Charts](charts.md) - An introduction to chart development - [Chart Lifecycle Hooks](charts_hooks.md) - [Chart Tips and Tricks](charts_tips_and_tricks.md) diff --git a/docs/install.md b/docs/install.md index af5d57a7b..96aea1f76 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 --override 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 98995d47a..fe1856bed 100644 --- a/docs/man/man1/helm_install.1 +++ b/docs/man/man1/helm_install.1 @@ -122,6 +122,10 @@ charts in a repository, use 'helm search'. \fB\-\-cert\-file\fP="" identify HTTPS client using this SSL certificate file +.PP +\fB\-\-dep\-up\fP[=false] + run helm dependency update before installing the chart. + .PP \fB\-\-devel\fP[=false] use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. @@ -192,7 +196,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] @@ -236,4 +240,4 @@ charts in a repository, use 'helm search'. .SH HISTORY .PP -19\-May\-2017 Auto generated by spf13/cobra +31\-Oct\-2017 Auto generated by spf13/cobra 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/docs/plugins.md b/docs/plugins.md index 1bee8bd56..c14063cf8 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -31,16 +31,17 @@ plugins do the "detail work" of performing a desired action. ## Installing a Plugin -A Helm plugin management system is in the works. But in the short term, plugins -are installed by copying the plugin directory into `$(helm home)/plugins`. +Plugins are installed using the `$ helm plugin install command. You can pass in a path to a plugin on your local file system or a url of a remote VCS repo. The `helm plugin install` command clones or copies the plugin at the path/url given into `$ (helm home)/plugins` ```console -$ cp -a myplugin/ $(helm home)/plugins/ +$ helm plugin install https://github.com/technosophos/helm-template ``` If you have a plugin tar distribution, simply untar the plugin into the `$(helm home)/plugins` directory. +You can also install tarball plugins directly from url by issuing `helm plugin install http://domain/path/to/plugin.tar.gz` + ## Building Plugins In many ways, a plugin is similar to a chart. Each plugin has a top-level diff --git a/docs/quickstart.md b/docs/quickstart.md index dcf266867..caad7abf6 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -20,6 +20,9 @@ $ kubectl config current-context my-cluster ``` +If your cluster has Role-Based Access Control (RBAC) enabled, you may want +to [configure a service account and rules](rbac.md) before proceeding. + ## Install Helm Download a binary release of the Helm client. You can use tools like @@ -45,6 +48,10 @@ This will install Tiller into the Kubernetes cluster you saw with **TIP:** When you want to upgrade Tiller, just run `helm init --upgrade`. +By default, when Tiller is installed,it does not have authentication enabled. +To learn more about configuring strong TLS authentication for Tiller, consult +[the Tiller TLS guide](tiller_ssl.md). + ## Install an Example Chart To install a chart, you can run the `helm install` command. Helm has diff --git a/docs/rbac.md b/docs/rbac.md new file mode 100644 index 000000000..bc138ceee --- /dev/null +++ b/docs/rbac.md @@ -0,0 +1,281 @@ +# Role-based Access Control + +In Kubernetes, granting a role to an application-specific service account is a best practice to ensure that your application is operating in the scope that you have specified. Read more about service account permissions [in the official Kubernetes docs](https://kubernetes.io/docs/admin/authorization/rbac/#service-account-permissions). + +Bitnami also has a fantastic guide for [configuring RBAC in your cluster](https://docs.bitnami.com/kubernetes/how-to/configure-rbac-in-your-kubernetes-cluster/) that takes you through RBAC basics. + +This guide is for users who want to restrict tiller's capabilities to install resources to certain namespaces, or to grant a helm client running access to a tiller instance. + +## Tiller and Role-based Access Control + +You can add a service account to Tiller using the `--service-account ` flag while you're configuring helm. As a prerequisite, you'll have to create a role binding which specifies a [role](https://kubernetes.io/docs/admin/authorization/rbac/#role-and-clusterrole) and a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) name that have been set up in advance. + +Once you have satisfied the pre-requisite and have a service account with the correct permissions, you'll run a command like this: `helm init --service-account ` + +### Example: Service account with cluster-admin role + +```console +$ kubectl create serviceaccount tiller --namespace kube-system +serviceaccount "tiller" created +``` + +In `rbac-config.yaml`: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tiller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: tiller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: tiller + namespace: kube-system +``` + +_Note: The cluster-admin role is created by default in a Kubernetes cluster, so you don't have to define it explicitly._ + +```console +$ kubectl create -f rbac-config.yaml +serviceaccount "tiller" created +clusterrolebinding "tiller" created +$ helm init --service-account tiller +``` + +### Example: Deploy tiller in a namespace, restricted to deploying resources only in that namespace + +In the example above, we gave Tiller admin access to the entire cluster. You are not at all required to give Tiller cluster-admin access for it to work. Instead of specifying a ClusterRole or a ClusterRoleBinding, you can specify a Role and RoleBinding to limit Tiller's scope to a particular namespace. + +```console +$ kubectl create namespace tiller-world +namespace "tiller-world" created +$ kubectl create serviceaccount tiller --namespace tiller-world +serviceaccount "tiller" created +``` + +Define a Role that allows tiller to manage all resources in `tiller-world` like in `role-tiller.yaml`: + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-manager + namespace: tiller-world +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["*"] + verbs: ["*"] +``` + +```console +$ kubectl create -f role-tiller.yaml +role "tiller-manager" created +``` + +In `rolebinding-tiller.yaml`, + +```yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-binding + namespace: tiller-world +subjects: +- kind: ServiceAccount + name: tiller + namespace: tiller-world +roleRef: + kind: Role + name: tiller-manager + apiGroup: rbac.authorization.k8s.io +``` + +```console +$ kubectl create -f rolebinding-tiller.yaml +rolebinding "tiller-binding" created +``` + +Afterwards you can run `helm init` to install tiller in the `tiller-world` namespace. + +```console +$ helm init --service-account tiller --tiller-namespace tiller-world +$HELM_HOME has been configured at /Users/awesome-user/.helm. + +Tiller (the helm server side component) has been installed into your Kubernetes Cluster. +Happy Helming! + +$ helm install nginx --tiller-namespace tiller-world --namespace tiller-world +NAME: wayfaring-yak +LAST DEPLOYED: Mon Aug 7 16:00:16 2017 +NAMESPACE: tiller-world +STATUS: DEPLOYED + +RESOURCES: +==> v1/Pod +NAME READY STATUS RESTARTS AGE +wayfaring-yak-alpine 0/1 ContainerCreating 0 0s +``` + +### Example: Deploy tiller in a namespace, restricted to deploying resources in another namespace + +In the example above, we gave Tiller admin access to the namespace it was deployed inside. Now, let's limit Tiller's scope to deploy resources in a different namespace! + +For example, let's install tiller in the namespace `myorg-system` and allow tiller to deploy resources in the namespace `myorg-users`. + +```console +$ kubectl create namespace myorg-system +namespace "myorg-system" created +$ kubectl create serviceaccount tiller --namespace myorg-system +serviceaccount "tiller" created +``` + +Define a Role that allows tiller to manage all resources in `myorg-users` like in `role-tiller.yaml`: + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-manager + namespace: myorg-users +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["*"] + verbs: ["*"] +``` + +```console +$ kubectl create -f role-tiller.yaml +role "tiller-manager" created +``` + +Bind the service account to that role. In `rolebinding-tiller.yaml`, + +```yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-binding + namespace: myorg-users +subjects: +- kind: ServiceAccount + name: tiller + namespace: myorg-system +roleRef: + kind: Role + name: tiller-manager + apiGroup: rbac.authorization.k8s.io +``` + +```console +$ kubectl create -f rolebinding-tiller.yaml +rolebinding "tiller-binding" created +``` + +We'll also need to grant tiller access to read configmaps in myorg-system so it can store release information. In `role-tiller-myorg-system.yaml`: + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + namespace: myorg-system + name: tiller-manager +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["configmaps"] + verbs: ["*"] +``` + +```console +$ kubectl create -f role-tiller-myorg-system.yaml +role "tiller-manager" created +``` + +And the respective role binding. In `rolebinding-tiller-myorg-system.yaml`: + +```yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-binding + namespace: myorg-system +subjects: +- kind: ServiceAccount + name: tiller + namespace: myorg-system +roleRef: + kind: Role + name: tiller-manager + apiGroup: rbac.authorization.k8s.io +``` + +```console +$ kubectl create -f rolebinding-tiller-myorg-system.yaml +rolebinding "tiller-binding" created +``` + +## Helm and Role-based Access Control + +When running a helm client in a pod, in order for the helm client to talk to a tiller instance, it will need certain privileges to be granted. Specifically, the helm client will need to be able to create pods, forward ports and be able to list pods in the namespace where tiller is running (so it can find tiller). + +### Example: Deploy Helm in a namespace, talking to Tiller in another namespace + +In this example, we will assume tiller is running in a namespace called `tiller-world` and that the helm client is running in a namespace called `helm-world`. By default, tiller is running in the `kube-system` namespace. + +In `helm-user.yaml`: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: helm + namespace: helm-world +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: tiller-user + namespace: tiller-world +rules: +- apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create +- apiGroups: + - "" + resources: + - pods + verbs: + - list +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: tiller-user-binding + namespace: tiller-world +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: tiller-user +subjects: +- kind: ServiceAccount + name: helm + namespace: helm-world +``` + +```console +$ kubectl create -f helm-user.yaml +serviceaccount "helm" created +role "tiller-user" created +rolebinding "tiller-user-binding" created +``` diff --git a/docs/related.md b/docs/related.md index 73db34829..1f9ec68c1 100644 --- a/docs/related.md +++ b/docs/related.md @@ -36,6 +36,8 @@ or [pull request](https://github.com/kubernetes/helm/pulls). - [App Registry](https://github.com/app-registry/helm-plugin) - Plugin to manage charts via the [App Registry specification](https://github.com/app-registry/spec) - [helm-secrets](https://github.com/futuresimple/helm-secrets) - Plugin to manage and store secrets safely - [helm-edit](https://github.com/mstrzele/helm-edit) - Plugin for editing release's values +- [helm-gcs](https://github.com/nouney/helm-gcs) - Plugin to manage repositories on Google Cloud Storage +- [helm-github](https://github.com/sagansystems/helm-github) - Plugin to install Helm Charts from Github repositories We also encourage GitHub authors to use the [helm-plugin](https://github.com/search?q=topic%3Ahelm-plugin&type=Repositories) tag on their plugin repositories. diff --git a/docs/service_accounts.md b/docs/service_accounts.md deleted file mode 100644 index 612074d03..000000000 --- a/docs/service_accounts.md +++ /dev/null @@ -1,112 +0,0 @@ -# Tiller and Service Accounts - -In Kubernetes, granting a role to an application-specific service account is a best practice to ensure that your application is operating in the scope that you have specified. Read more about service account permissions [in the official Kubernetes docs](https://kubernetes.io/docs/admin/authorization/rbac/#service-account-permissions). Bitnami also has a fantastic guide for [configuring RBAC in your cluster](https://docs.bitnami.com/kubernetes/how-to/configure-rbac-in-your-kubernetes-cluster/) that takes you through RBAC basics. - -You can add a service account to Tiller using the `--service-account ` flag while you're configuring helm. As a prerequisite, you'll have to create a role binding which specifies a [role](https://kubernetes.io/docs/admin/authorization/rbac/#role-and-clusterrole) and a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) name that have been set up in advance. - -Once you have satisfied the pre-requisite and have a service account with the correct permissions, you'll run a command like this: `helm init --service-account ` - -## Example: Service account with cluster-admin role - -```console -$ kubectl create serviceaccount tiller --namespace kube-system -``` - -In `rbac-config.yaml`: -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: tiller - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: tiller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: - - kind: ServiceAccount - name: tiller - namespace: kube-system -``` - -_Note: The cluster-admin role is created by default in a Kubernetes cluster, so you don't have to define it explicitly._ - -```console -$ kubectl create -f rbac-config.yaml -$ helm init --service-account tiller -``` - -## Example: Service account restricted to a namespace -In the example above, we gave Tiller admin access to the entire cluster. You are not at all required to give Tiller cluster-admin access for it to work. Instead of specifying a ClusterRole or a ClusterRoleBinding, you can specify a Role and RoleBinding to limit Tiller's scope to a particular namespace. - -```console -$ kubectl create namespace tiller-world -namespace "tiller-world" created -$ kubectl create serviceaccount tiller --namespace tiller-world -serviceaccount "tiller" created -``` - -Define a Role like in `role-tiller.yaml`: -```yaml -kind: Role -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - namespace: tiller-world - name: tiller-manager -rules: -- apiGroups: ["", "extensions", "apps"] - resources: ["deployments", "replicasets", "pods", "configmaps", "secrets", "namespaces"] - verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # You can also use ["*"] -``` - -```console -$ kubectl create -f role-tiller.yaml -role "tiller-manager" created -``` - -In `rolebinding-tiller.yaml`, -```yaml -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - name: tiller-binding - namespace: tiller-world -subjects: -- kind: ServiceAccount - name: tiller - namespace: tiller-world -roleRef: - kind: Role - name: tiller-manager - apiGroup: rbac.authorization.k8s.io -``` - -```console -$ kubectl create -f rolebinding-tiller.yaml -rolebinding "tiller-binding" created -``` - -```console -$ helm init --service-account tiller --tiller-namespace tiller-world -$HELM_HOME has been configured at /Users/awesome-user/.helm. - -Tiller (the helm server side component) has been installed into your Kubernetes Cluster. -Happy Helming! - -$ helm install nginx --tiller-namespace tiller-world --namespace tiller-world -NAME: wayfaring-yak -LAST DEPLOYED: Mon Aug 7 16:00:16 2017 -NAMESPACE: tiller-world -STATUS: DEPLOYED - -RESOURCES: -==> v1/Pod -NAME READY STATUS RESTARTS AGE -wayfaring-yak-alpine 0/1 ContainerCreating 0 0s -``` - diff --git a/docs/tiller_ssl.md b/docs/tiller_ssl.md new file mode 100644 index 000000000..71ddd8080 --- /dev/null +++ b/docs/tiller_ssl.md @@ -0,0 +1,291 @@ +# Using SSL Between Helm and Tiller + +This document explains how to create strong SSL/TLS connections between Helm and +Tiller. The emphasis here is on creating an internal CA, and using both the +cryptographic and identity functions of SSL. + +> Support for TLS-based auth was introduced in Helm 2.3.0 + +Configuring SSL is considered an advanced topic, and knowledge of Helm and Tiller +is assumed. + +## Overview + +The Tiller authentication model uses client-side SSL certificates. Tiller itself +verifies these certificates using a certificate authority. Likewise, the client +also verifies Tiller's identity by certificate authority. + +There are numerous possible configurations for setting up certificates and authorities, +but the method we cover here will work for most situations. + +> As of Helm 2.7.2, Tiller _requires_ that the client certificate be validated +> by its CA. In prior versions, Tiller used a weaker validation strategy that +> allowed self-signed certificates. + +In this guide, we will show how to: + +- Create a private CA that is used to issue certificates for Tiller clients and + servers. +- Create a certificate for Tiller +- Create a certificate for the Helm client +- Create a Tiller instance that uses the certificate +- Configure the Helm client to use the CA and client-side certificate + +By the end of this guide, you should have a Tiller instance running that will +only accept connections from clients who can be authenticated by SSL certificate. + +## Generating Certificate Authorities and Certificates + +One way to generate SSL CAs is via the `openssl` command line tool. There are many +guides and best practices documents available online. This explanation is focused +on getting ready within a small amount of time. For production configurations, +we urge readers to read [the official documentation](https://www.openssl.org) and +consult other resources. + +### Generate a Certificate Authority + +The simplest way to generate a certificate authority is to run two commands: + +```console +$ openssl genrsa -out ./ca.key.pem 4096 +$ openssl req -key ca.key.pem -new -x509 -days 7300 -sha256 -out ca.cert.pem -extensions v3_ca +Enter pass phrase for ca.key.pem: +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:CO +Locality Name (eg, city) []:Boulder +Organization Name (eg, company) [Internet Widgits Pty Ltd]:tiller +Organizational Unit Name (eg, section) []: +Common Name (e.g. server FQDN or YOUR name) []:tiller +Email Address []:tiller@example.com +``` + +Note that the data input above is _sample data_. You should customize to your own +specifications. + +The above will generate both a secret key and a CA. Note that these two files are +very important. The key in particular should be handled with particular care. + +Often, you will want to generate an intermediate signing key. For the sake of brevity, +we will be signing keys with our root CA. + +### Generating Certificates + +We will be generating two certificates, each representing a type of certificate: + +- One certificate is for Tiller. You will want one of these _per tiller host_ that + you run. +- One certificate is for the user. You will want one of these _per helm user_. + +Since the commands to generate these are the same, we'll be creating both at the +same time. The names will indicate their target. + +First, the Tiller key: + +```console +$ openssl genrsa -out ./tiller.key.pem 4096 +Generating RSA private key, 4096 bit long modulus +..........................................................................................................................................................................................................................................................................................................................++ +............................................................................++ +e is 65537 (0x10001) +Enter pass phrase for ./tiller.key.pem: +Verifying - Enter pass phrase for ./tiller.key.pem: +``` + +Next, generate the Helm client's key: + +```console +$ openssl genrsa -out ./helm.key.pem 4096 +Generating RSA private key, 4096 bit long modulus +.....++ +......................................................................................................................................................................................++ +e is 65537 (0x10001) +Enter pass phrase for ./helm.key.pem: +Verifying - Enter pass phrase for ./helm.key.pem: +``` + +Again, for production use you will generate one client certificate for each user. + +Next we need to create certificates from these keys. For each certificate, this is +a two-step process of creating a CSR, and then creating the certificate. + +```console +$ openssl req -key tiller.key.pem -new -sha256 -out tiller.csr.pem +Enter pass phrase for tiller.key.pem: +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:CO +Locality Name (eg, city) []:Boulder +Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tiller Server +Organizational Unit Name (eg, section) []: +Common Name (e.g. server FQDN or YOUR name) []:tiller-server +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: +``` + +And we repeat this step for the Helm client certificate: + +```console +$ openssl req -key helm.key.pem -new -sha256 -out helm.csr.pem +# Answer the questions with your client user's info +``` + +(In rare cases, we've had to add the `-nodes` flag when generating the request.) + +Now we sign each of these CSRs with the CA certificate we created: + +```console +$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in tiller.csr.pem -out tiller.cert.pem +Signature ok +subject=/C=US/ST=CO/L=Boulder/O=Tiller Server/CN=tiller-server +Getting CA Private Key +Enter pass phrase for ca.key.pem: +``` + +And again for the client certificate: + +```console +$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in helm.csr.pem -out helm.cert.pem +``` + +At this point, the important files for us are these: + +``` +# The CA. Make sure the key is kept secret. +ca.cert.pem +ca.key.pem +# The Helm client files +helm.cert.pem +helm.key.pem +# The Tiller server files. +tiller.cert.pem +tiller.key.pem +``` + +Now we're ready to move on to the next steps. + +## Creating a Custom Tiller Installation + +Helm includes full support for creating a deployment configured for SSL. By specifying +a few flags, the `helm init` command can create a new Tiller installation complete +with all of our SSL configuration. + +To take a look at what this will generate, run this command: + +```console +$ helm init --dry-run --debug --tiller-tls --tiller-tls-cert ./tiller.cert.pem --tiller-tls-key ./tiller.key.pem --tiller-tls-verify --tls-ca-cert ca.cert.pem +``` + +The output will show you a Deployment, a Secret, and a Service. Your SSL information +will be preloaded into the Secret, which the Deployment will mount to pods as they +start up. + +If you want to customize the manifest, you can save that output to a file and then +use `kubectl create` to load it into your cluster. + +> We strongly recommend enabling RBAC on your cluster and adding [service accounts](rbac.md) +> with RBACS. + +Otherwise, you can remove the `--dry-run` and `--debug` flags. We also recommend +putting Tiller in a non-system namespace (`--tiller-namespace=something`) and enable +a service account (`--service-account=somename`). But for this example we will stay +with the basics: + +```console +$ helm init --tiller-tls --tiller-tls-cert ./tiller.cert.pem --tiller-tls-key ./tiller.key.pem --tiller-tls-verify --tls-ca-cert ca.cert.pem +``` + +In a minute or two it should be ready. We can check Tiller like this: + +```console +$ kubectl -n kube-system get deployment +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +... other stuff +tiller-deploy 1 1 1 1 2m +``` + +If there is a problem, you may want to use `kubectl get pods -n kube-system` to +find out what went wrong. With the SSL/TLS support, the most common problems all +have to do with improperly generated TLS certificates or accidentally swapping the +cert and the key. + +At this point, you should get a _failure_ when you run basic Helm commands: + +```console +$ helm ls +Error: transport is closing +``` + +This is because your Helm client does not have the correct certificate to authenticate +to Tiller. + +## Configuring the Helm Client + +The Tiller server is now running with TLS protection. It's time to configure the +Helm client to also perform TLS operations. + +For a quick test, we can specify our configuration manually. We'll run a normal +Helm command (`helm ls`), but with SSL/TLS enabled. + +```console +helm ls --tls --tls-ca-cert ca.cert.pem --tls-cert helm.cert.pem --tls-key helm.key.pem +``` + +This configuration sends our client-side certificate to establish identity, uses +the client key for encryption, and uses the CA certificate to validate the remote +Tiller's identity. + +Typing a line that that is cumbersome, though. The shortcut is to move the key, +cert, and CA into `$HELM_HOME`: + +```console +$ cp ca.cert.pem $(helm home)/ca.pem +$ cp helm.cert.pem $(helm home)/cert.pem +$ cp helm.key.pem $(helm home)/key.pem +``` + +With this, you can simply run `helm ls --tls` to enable TLS. + +### Troubleshooting + +*Running a command, I get `Error: transport is closing`* + +This is almost always due to a configuration error in which the client is missing +a certificate (`--tls-cert`) or the certificate is bad. + +*I'm using a certificate, but get `Error: remote error: tls: bad certificate`* + +This means that Tiller's CA cannot verify your certificate. In the examples above, +we used a single CA to generate both the client and server certificates. In these +examples, the CA has _signed_ the client's certificate. We then load that CA +up to Tiller. So when the client certificate is sent to the server, Tiller +checks the client certificate against the CA. + +*If I use `--tls-verify` on the client, I get `Error: x509: certificate is valid for tiller-server, not localhost`* + +If you plan to use `--tls-verify` on the client, you will need to make sure that +the host name that Helm connects to matches the host name on the certificate. In +some cases this is awkward, since Helm will connect over localhost, or the FQDN is +not available for public resolution. + +## References + +https://github.com/denji/golang-tls +https://www.openssl.org/docs/ +https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html diff --git a/docs/using_helm.md b/docs/using_helm.md index 77e4cffc2..d2e1768a8 100755 --- a/docs/using_helm.md +++ b/docs/using_helm.md @@ -230,6 +230,10 @@ There are two ways to pass configuration data during install: - `--set`: Specify overrides on the command line. If both are used, `--set` values are merged into `--values` with higher precedence. +Overrides specified with `--set` are persisted in a configmap. Values that have been +`--set` can be viewed for a given release with `helm get values `. +Values that have been `--set` can be cleared by running `helm upgrade` with `--reset-values` +specified. #### The Format and Limitations of `--set` @@ -494,7 +498,7 @@ accepts chart source code, and (after audit) packages those for you. In some cases you may wish to scope Tiller or deploy multiple Tillers to a single cluster. Here are some best practices when operating in those circumstances. 1. Tiller can be [installed](install.md) into any namespace. By default, it is installed into kube-system. You can run multiple Tillers provided they each run in their own namespace. -2. Limiting Tiller to only be able to install into specific namespaces and/or resource types is controlled by Kubernetes [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/) roles and rolebindings. You can add a service account to Tiller when configuring Helm via `helm init --service-acount `. You can find more information about that [here](service_accounts.md). +2. Limiting Tiller to only be able to install into specific namespaces and/or resource types is controlled by Kubernetes [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/) roles and rolebindings. You can add a service account to Tiller when configuring Helm via `helm init --service-account `. You can find more information about that [here](rbac.md). 3. Release names are unique PER TILLER INSTANCE. 4. Charts should only contain resources that exist in a single namespace. 5. It is not recommended to have multiple Tillers configured to manage resources in the same namespace. diff --git a/glide.lock b/glide.lock index c368466fb..e24eca700 100644 --- a/glide.lock +++ b/glide.lock @@ -1,14 +1,22 @@ -hash: 0759b118eb4017d612af767460cdec467d6f78013ad1efff1c82676f1df84a75 -updated: 2017-09-26T15:21:30.833774-07:00 +hash: cdacf92805a7371d74694ab6a82d7475639e4b20113ff3bca961f42b6554cbb0 +updated: 2017-12-01T15:12:23.724268985-05: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: 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,27 +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/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 @@ -101,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 @@ -136,27 +130,54 @@ imports: version: 4bd1920723d7b7c925de087aa32e2187708897f7 subpackages: - proto + - ptypes - ptypes/any + - ptypes/duration - ptypes/timestamp +- name: github.com/google/btree + version: 7d79101e329e5a3adf994758c578dab82b90c017 - name: github.com/google/gofuzz 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: 2500245aa6110c562d17020fb31a2c133d737799 + 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 @@ -168,7 +189,7 @@ imports: - name: github.com/Masterminds/semver version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd - name: github.com/Masterminds/sprig - version: 4c164950cd0a8d3724ddb78982e2c56dc7f47112 + version: b217b9c388de2cacde4354c536e520c52c055563 - name: github.com/Masterminds/vcs version: 3084677c2c188840777bff30054f2b553729d329 - name: github.com/mattn/go-runewidth @@ -177,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: @@ -219,14 +247,13 @@ imports: - name: github.com/spf13/pflag version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 - name: github.com/technosophos/moniker - version: 9f956786b91d9786ca11aa5be6104542fa911546 + version: ab470f5e105a44d0c87ea21bacd6a335c4816d83 - name: github.com/ugorji/go version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74 subpackages: - codec - - codec/codecgen - name: golang.org/x/crypto - version: d172538b2cfce0c13cee31e647d0367aa8cd2486 + version: 81e90905daefcd6fd217b62423c0908922eadb30 subpackages: - cast5 - openpgp @@ -240,9 +267,10 @@ imports: - scrypt - ssh/terminal - name: golang.org/x/net - version: f2499483f923065a842d38eb4c7f1927e6fc6e6d + version: 1c05540f6879653db88113bc4a2b70aec4bd491f subpackages: - context + - context/ctxhttp - http2 - http2/hpack - idna @@ -251,18 +279,25 @@ imports: - trace - name: golang.org/x/oauth2 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 @@ -273,39 +308,142 @@ 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/genproto + version: 09f6ed296fc66555a25fe4ce95173148778dfa85 + subpackages: + - googleapis/rpc/status - name: google.golang.org/grpc - version: 8050b9cbc271307e5a716a9d782803d09b0d6f2d + version: 5ffe3083946d5603a0578721101dc8165b1d5b5f subpackages: + - balancer - codes + - connectivity - credentials + - grpclb/grpc_lb_v1/messages - grpclog - internal - keepalive - metadata - naming - peer + - resolver - stats + - status - tap - 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 - subpackages: + version: cadaf100c0a3dd6b254f320d6d651df079ec8e0a + subpackages: + - admission/v1alpha1 + - admissionregistration/v1alpha1 + - apps/v1beta1 + - apps/v1beta2 + - authentication/v1 + - authentication/v1beta1 + - authorization/v1 + - authorization/v1beta1 + - autoscaling/v1 + - 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: 2308857ad3b8b18abf74ff734853973eda9da94d + version: c1e53d745d0fe45bf7d5d44697e6eface25fceca subpackages: - pkg/admission - pkg/apis/apiserver - pkg/apis/apiserver/install - pkg/apis/apiserver/v1alpha1 + - pkg/apis/audit - pkg/authentication/authenticator - pkg/authentication/serviceaccount - pkg/authentication/user @@ -313,20 +451,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: d3faa3f8f2e85c8089e80a661955626ae24abf80 + 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 @@ -342,7 +578,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 @@ -353,6 +588,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 @@ -364,10 +600,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 @@ -389,8 +626,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 @@ -400,28 +641,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 @@ -449,24 +668,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 @@ -474,9 +688,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 @@ -486,16 +704,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 @@ -505,6 +725,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: @@ -513,6 +734,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 6b231c417..5aa6899e5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -14,7 +14,7 @@ import: - package: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - package: github.com/Masterminds/sprig - version: ^2.13 + version: ^2.14.1 - package: github.com/ghodss/yaml - package: github.com/Masterminds/semver version: ~1.3.1 @@ -26,15 +26,23 @@ import: - ptypes/any - ptypes/timestamp - package: google.golang.org/grpc - version: 1.2.1 + version: 1.7.2 + # 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 @@ -48,37 +56,7 @@ 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: v8.0.0 -- 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 version: ^1.1.4 diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index fe06e14e0..5cdf7ab4b 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,18 +230,23 @@ 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 -}} {{/* 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). +If release name contains chart name it will be used as a full name. */}} -{{- define "fullname" -}} +{{- define ".fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} +{{- end -}} ` // CreateFrom creates a new chart, but scaffolds it from the src chart. @@ -318,27 +324,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 cf11ae2e4..6d70f97b4 100644 --- a/pkg/chartutil/load.go +++ b/pkg/chartutil/load.go @@ -243,8 +243,13 @@ func LoadDir(dir string) (*chart.Chart, error) { files := []*BufferedFile{} topdir += string(filepath.Separator) - err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error { + walk := func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) + if n == "" { + // No need to process top level. Avoid bug with helmignore .* matching + // empty names. See issue 1779. + return nil + } // Normalize to / since it will also work on Windows n = filepath.ToSlash(n) @@ -273,10 +278,45 @@ func LoadDir(dir string) (*chart.Chart, error) { files = append(files, &BufferedFile{Name: n, Data: data}) return nil - }) - if err != nil { + } + if err = filepath.Walk(topdir, symWalk(topdir, "", walk)); err != nil { return c, err } return LoadFiles(files) } + +// symWalk walks topdir with optional symbolic link dir, symdir. The symdir will +// be used as the path name sent to walkFn. +func symWalk(topdir, symdir string, walkFn filepath.WalkFunc) filepath.WalkFunc { + return func(name string, fi os.FileInfo, err error) error { + // Recover the symbolic path instead of the real path. + if symdir != "" { + relative, err := filepath.Rel(topdir, name) + if err != nil { + return err + } + name = filepath.Join(symdir, relative) + } + + // Recursively walk symlinked directories. + if isSymlink(fi) { + resolved, err := filepath.EvalSymlinks(name) + if err != nil { + return fmt.Errorf("error evaluating symlink %s: %s", name, err) + } + if fi, err = os.Lstat(resolved); err != nil { + return err + } + if fi.IsDir() { + return filepath.Walk(resolved, symWalk(resolved, name, walkFn)) + } + } + + return walkFn(name, fi, err) + } +} + +func isSymlink(fi os.FileInfo) bool { + return fi.Mode()&os.ModeSymlink != 0 +} diff --git a/pkg/chartutil/requirements_test.go b/pkg/chartutil/requirements_test.go index ef09bcd2e..24388f86c 100644 --- a/pkg/chartutil/requirements_test.go +++ b/pkg/chartutil/requirements_test.go @@ -419,6 +419,25 @@ func TestDependentChartWithSubChartsAbsentInRequirements(t *testing.T) { } +func TestDependentChartWithSubChartsHelmignore(t *testing.T) { + if _, err := Load("testdata/dependent-chart-helmignore"); err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } +} + +func TestDependentChartsWithSubChartsSymlink(t *testing.T) { + c, err := Load("testdata/joonix") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + if c.Metadata.Name != "joonix" { + t.Fatalf("Unexpected chart name: %s", c.Metadata.Name) + } + if n := len(c.Dependencies); n != 1 { + t.Fatalf("Expected 1 dependency for this chart, but got %d", n) + } +} + func TestDependentChartsWithSubchartsAllSpecifiedInRequirements(t *testing.T) { c, err := Load("testdata/dependent-chart-with-all-in-requirements-yaml") if err != nil { diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore b/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore new file mode 100644 index 000000000..8a71bc82e --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore @@ -0,0 +1,2 @@ +ignore/ +.* diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 000000000..ced5a4a6a Binary files /dev/null and b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz differ diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/pkg/chartutil/testdata/joonix/Chart.yaml b/pkg/chartutil/testdata/joonix/Chart.yaml new file mode 100644 index 000000000..c3464c56e --- /dev/null +++ b/pkg/chartutil/testdata/joonix/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: joonix +version: 1.2.3 diff --git a/pkg/chartutil/testdata/joonix/charts/frobnitz b/pkg/chartutil/testdata/joonix/charts/frobnitz new file mode 120000 index 000000000..fde1b78ac --- /dev/null +++ b/pkg/chartutil/testdata/joonix/charts/frobnitz @@ -0,0 +1 @@ +../../frobnitz \ No newline at end of file diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 15e97f6a1..f0fd5073c 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -217,7 +217,15 @@ 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) + repoURL, err := url.Parse(rc.URL) + if err != nil { + return repoURL, r.Client, err + } + q := repoURL.Query() + // We need a trailing slash for ResolveReference to work, but make sure there isn't already one + repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/" + u = repoURL.ResolveReference(u) + u.RawQuery = q.Encode() return u, r.Client, err } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 4049b7979..0100772e9 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -43,6 +43,11 @@ 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: "reference, testing-relative repo", ref: "testing-relative/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, + {name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, + {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, + {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, {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/cache/testing-relative-index.yaml b/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-index.yaml new file mode 100644 index 000000000..210f92e45 --- /dev/null +++ b/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-index.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +entries: + foo: + - name: foo + description: Foo Chart With Relative Path + engine: gotpl + home: https://k8s.io/helm + keywords: [] + maintainers: [] + sources: + - https://github.com/kubernetes/charts + urls: + - charts/foo-1.2.3.tgz + version: 1.2.3 + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d + bar: + - name: bar + description: Bar Chart With Relative Path + engine: gotpl + home: https://k8s.io/helm + keywords: [] + maintainers: [] + sources: + - https://github.com/kubernetes/charts + urls: + - bar-1.2.3.tgz + version: 1.2.3 + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d diff --git a/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-trailing-slash-index.yaml b/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-trailing-slash-index.yaml new file mode 100644 index 000000000..210f92e45 --- /dev/null +++ b/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-trailing-slash-index.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +entries: + foo: + - name: foo + description: Foo Chart With Relative Path + engine: gotpl + home: https://k8s.io/helm + keywords: [] + maintainers: [] + sources: + - https://github.com/kubernetes/charts + urls: + - charts/foo-1.2.3.tgz + version: 1.2.3 + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d + bar: + - name: bar + description: Bar Chart With Relative Path + engine: gotpl + home: https://k8s.io/helm + keywords: [] + maintainers: [] + sources: + - https://github.com/kubernetes/charts + urls: + - bar-1.2.3.tgz + version: 1.2.3 + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d diff --git a/pkg/downloader/testdata/helmhome/repository/repositories.yaml b/pkg/downloader/testdata/helmhome/repository/repositories.yaml index 9b0cfe972..374d95c8a 100644 --- a/pkg/downloader/testdata/helmhome/repository/repositories.yaml +++ b/pkg/downloader/testdata/helmhome/repository/repositories.yaml @@ -10,3 +10,9 @@ repositories: url: "http://example.com/charts" - name: malformed url: "http://dl.example.com" + - name: testing-querystring + url: "http://example.com?key=value" + - name: testing-relative + url: "http://example.com/helm" + - name: testing-relative-trailing-slash + url: "http://example.com/helm/" \ No newline at end of file diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 9afa31f60..5a2146ec6 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -20,9 +20,11 @@ import ( "fmt" "io" "net/http" + "strings" "k8s.io/helm/pkg/tlsutil" "k8s.io/helm/pkg/urlutil" + "k8s.io/helm/pkg/version" ) //httpGetter is the efault HTTP(/S) backend handler @@ -34,7 +36,15 @@ type httpGetter struct { func (g *httpGetter) Get(href string) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) - resp, err := g.client.Get(href) + // Set a helm specific user agent so that a repo server and metrics can + // separate helm calls from other tools interacting with repos. + req, err := http.NewRequest("GET", href, nil) + if err != nil { + return buf, err + } + req.Header.Set("User-Agent", "Helm/"+strings.TrimPrefix(version.GetVersion(), "v")) + + resp, err := g.client.Do(req) if err != nil { return buf, err } diff --git a/pkg/helm/client.go b/pkg/helm/client.go index dbaf01d96..33d56e88d 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -23,6 +23,7 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/chart" @@ -289,6 +290,11 @@ func (h *Client) connect(ctx context.Context) (conn *grpc.ClientConn, err error) opts := []grpc.DialOption{ grpc.WithTimeout(5 * time.Second), grpc.WithBlock(), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + // Send keepalive every 30 seconds to prevent the connection from + // getting closed by upstreams + Time: time.Duration(30) * time.Second, + }), } switch { case h.opts.useTLS: diff --git a/pkg/helm/environment/environment.go b/pkg/helm/environment/environment.go index 3ddd90449..b8bcf0def 100644 --- a/pkg/helm/environment/environment.go +++ b/pkg/helm/environment/environment.go @@ -28,11 +28,12 @@ import ( "github.com/spf13/pflag" + "k8s.io/client-go/util/homedir" "k8s.io/helm/pkg/helm/helmpath" ) // DefaultHelmHome is the default HELM_HOME. -var DefaultHelmHome = filepath.Join("$HOME", ".helm") +var DefaultHelmHome = filepath.Join(homedir.HomeDir(), ".helm") // EnvSettings describes all of the environment settings. type EnvSettings struct { @@ -46,6 +47,8 @@ type EnvSettings struct { Debug bool // KubeContext is the name of the kubeconfig context. KubeContext string + // KubeConfig is the name of the kubeconfig file. + KubeConfig string } // AddFlags binds flags to the given flagset. @@ -53,6 +56,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar((*string)(&s.Home), "home", DefaultHelmHome, "location of your Helm config. Overrides $HELM_HOME") fs.StringVar(&s.TillerHost, "host", "", "address of Tiller. Overrides $HELM_HOST") fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use") + fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to kubeconfig file. Overrides $KUBECONFIG") fs.BoolVar(&s.Debug, "debug", false, "enable verbose output") fs.StringVar(&s.TillerNamespace, "tiller-namespace", "kube-system", "namespace of Tiller") } @@ -77,6 +81,7 @@ var envMap = map[string]string{ "debug": "HELM_DEBUG", "home": "HELM_HOME", "host": "HELM_HOST", + "kubeconfig": "KUBECONFIG", "tiller-namespace": "TILLER_NAMESPACE", } diff --git a/pkg/helm/fake.go b/pkg/helm/fake.go index c66777673..8d809f658 100644 --- a/pkg/helm/fake.go +++ b/pkg/helm/fake.go @@ -17,9 +17,12 @@ limitations under the License. package helm // import "k8s.io/helm/pkg/helm" import ( + "errors" "fmt" + "math/rand" "sync" + "github.com/golang/protobuf/ptypes/timestamp" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" rls "k8s.io/helm/pkg/proto/hapi/services" @@ -30,7 +33,15 @@ import ( type FakeClient struct { Rels []*release.Release Responses map[string]release.TestRun_Status - Err error + Opts options +} + +// Option returns the fake release client +func (c *FakeClient) Option(opts ...Option) Interface { + for _, opt := range opts { + opt(&c.Opts) + } + return c } var _ Interface = &FakeClient{} @@ -42,31 +53,49 @@ func (c *FakeClient) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesR Count: int64(len(c.Rels)), Releases: c.Rels, } - return resp, c.Err + return resp, nil } -// InstallRelease returns a response with the first Release on the fake release client +// InstallRelease creates a new release and returns a InstallReleaseResponse containing that release func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { - return &rls.InstallReleaseResponse{ - Release: c.Rels[0], - }, nil + chart := &chart.Chart{} + return c.InstallReleaseFromChart(chart, ns, opts...) } -// InstallReleaseFromChart returns a response with the first Release on the fake release client +// InstallReleaseFromChart adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { + for _, opt := range opts { + opt(&c.Opts) + } + + releaseName := c.Opts.instReq.Name + + // Check to see if the release already exists. + rel, err := c.ReleaseStatus(releaseName, nil) + if err == nil && rel != nil { + return nil, errors.New("cannot re-use a name that is still in use") + } + + release := ReleaseMock(&MockReleaseOptions{Name: releaseName, Namespace: ns}) + c.Rels = append(c.Rels, release) + return &rls.InstallReleaseResponse{ - Release: c.Rels[0], + Release: release, }, nil } -// DeleteRelease returns nil, nil +// DeleteRelease deletes a release from the FakeClient func (c *FakeClient) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { - return nil, nil -} + for i, rel := range c.Rels { + if rel.Name == rlsName { + c.Rels = append(c.Rels[:i], c.Rels[i+1:]...) + return &rls.UninstallReleaseResponse{ + Release: rel, + }, nil + } + } -// UpdateRelease returns nil, nil -func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { - return nil, nil + return nil, fmt.Errorf("No such release: %s", rlsName) } // GetVersion returns a fake version @@ -78,9 +107,20 @@ func (c *FakeClient) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, }, nil } -// UpdateReleaseFromChart returns nil, nil +// UpdateRelease returns an UpdateReleaseResponse containing the updated release, if it exists +func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + return c.UpdateReleaseFromChart(rlsName, &chart.Chart{}, opts...) +} + +// UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists func (c *FakeClient) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { - return nil, nil + // Check to see if the release already exists. + rel, err := c.ReleaseContent(rlsName, nil) + if err != nil { + return nil, err + } + + return &rls.UpdateReleaseResponse{Release: rel.Release}, nil } // RollbackRelease returns nil, nil @@ -88,32 +128,35 @@ func (c *FakeClient) RollbackRelease(rlsName string, opts ...RollbackOption) (*r return nil, nil } -// ReleaseStatus returns a release status response with info from the first release in the fake -// release client +// ReleaseStatus returns a release status response with info from the matching release name. func (c *FakeClient) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { - if c.Rels[0] != nil { - return &rls.GetReleaseStatusResponse{ - Name: c.Rels[0].Name, - Info: c.Rels[0].Info, - Namespace: c.Rels[0].Namespace, - }, nil + for _, rel := range c.Rels { + if rel.Name == rlsName { + return &rls.GetReleaseStatusResponse{ + Name: rel.Name, + Info: rel.Info, + Namespace: rel.Namespace, + }, nil + } } return nil, fmt.Errorf("No such release: %s", rlsName) } -// ReleaseContent returns the configuration for the first release in the fake release client +// ReleaseContent returns the configuration for the matching release name in the fake release client. func (c *FakeClient) ReleaseContent(rlsName string, opts ...ContentOption) (resp *rls.GetReleaseContentResponse, err error) { - if len(c.Rels) > 0 { - resp = &rls.GetReleaseContentResponse{ - Release: c.Rels[0], + for _, rel := range c.Rels { + if rel.Name == rlsName { + return &rls.GetReleaseContentResponse{ + Release: rel, + }, nil } } - return resp, c.Err + return resp, fmt.Errorf("No such release: %s", rlsName) } // ReleaseHistory returns a release's revision history. func (c *FakeClient) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) { - return &rls.GetHistoryResponse{Releases: c.Rels}, c.Err + return &rls.GetHistoryResponse{Releases: c.Rels}, nil } // RunReleaseTest executes a pre-defined tests on a release @@ -141,7 +184,89 @@ func (c *FakeClient) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) ( return results, errc } -// Option returns the fake release client -func (c *FakeClient) Option(opt ...Option) Interface { - return c +// MockHookTemplate is the hook template used for all mock release objects. +var MockHookTemplate = `apiVersion: v1 +kind: Job +metadata: + annotations: + "helm.sh/hooks": pre-install +` + +// MockManifest is the manifest used for all mock release objects. +var MockManifest = `apiVersion: v1 +kind: Secret +metadata: + name: fixture +` + +// MockReleaseOptions allows for user-configurable options on mock release objects. +type MockReleaseOptions struct { + Name string + Version int32 + Chart *chart.Chart + StatusCode release.Status_Code + Namespace string +} + +// ReleaseMock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. +func ReleaseMock(opts *MockReleaseOptions) *release.Release { + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + + name := opts.Name + if name == "" { + name = "testrelease-" + string(rand.Intn(100)) + } + + var version int32 = 1 + if opts.Version != 0 { + version = opts.Version + } + + namespace := opts.Namespace + if namespace == "" { + namespace = "default" + } + + ch := opts.Chart + if opts.Chart == nil { + ch = &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + Version: "0.1.0-beta.1", + }, + Templates: []*chart.Template{ + {Name: "templates/foo.tpl", Data: []byte(MockManifest)}, + }, + } + } + + scode := release.Status_DEPLOYED + if opts.StatusCode > 0 { + scode = opts.StatusCode + } + + return &release.Release{ + Name: name, + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: scode}, + Description: "Release mock", + }, + Chart: ch, + Config: &chart.Config{Raw: `name: "value"`}, + Version: version, + Namespace: namespace, + Hooks: []*release.Hook{ + { + Name: "pre-install-hook", + Kind: "Job", + Path: "pre-install-hook.yaml", + Manifest: MockHookTemplate, + LastRun: &date, + Events: []release.Hook_Event{release.Hook_PRE_INSTALL}, + }, + }, + Manifest: MockManifest, + } } diff --git a/pkg/helm/fake_test.go b/pkg/helm/fake_test.go new file mode 100644 index 000000000..9c0a53759 --- /dev/null +++ b/pkg/helm/fake_test.go @@ -0,0 +1,283 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helm + +import ( + "reflect" + "testing" + + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + rls "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestFakeClient_ReleaseStatus(t *testing.T) { + releasePresent := ReleaseMock(&MockReleaseOptions{Name: "release-present"}) + releaseNotPresent := ReleaseMock(&MockReleaseOptions{Name: "release-not-present"}) + + type fields struct { + Rels []*release.Release + } + type args struct { + rlsName string + opts []StatusOption + } + tests := []struct { + name string + fields fields + args args + want *rls.GetReleaseStatusResponse + wantErr bool + }{ + { + name: "Get a single release that exists", + fields: fields{ + Rels: []*release.Release{ + releasePresent, + }, + }, + args: args{ + rlsName: releasePresent.Name, + opts: nil, + }, + want: &rls.GetReleaseStatusResponse{ + Name: releasePresent.Name, + Info: releasePresent.Info, + Namespace: releasePresent.Namespace, + }, + + wantErr: false, + }, + { + name: "Get a release that does not exist", + fields: fields{ + Rels: []*release.Release{ + releasePresent, + }, + }, + args: args{ + rlsName: releaseNotPresent.Name, + opts: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "Get a single release that exists from list", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin", Namespace: "default"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir", Namespace: "default"}), + releasePresent, + }, + }, + args: args{ + rlsName: releasePresent.Name, + opts: nil, + }, + want: &rls.GetReleaseStatusResponse{ + Name: releasePresent.Name, + Info: releasePresent.Info, + Namespace: releasePresent.Namespace, + }, + + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &FakeClient{ + Rels: tt.fields.Rels, + } + got, err := c.ReleaseStatus(tt.args.rlsName, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("FakeClient.ReleaseStatus() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FakeClient.ReleaseStatus() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFakeClient_InstallReleaseFromChart(t *testing.T) { + installChart := &chart.Chart{} + type fields struct { + Rels []*release.Release + } + type args struct { + ns string + opts []InstallOption + } + tests := []struct { + name string + fields fields + args args + want *rls.InstallReleaseResponse + relsAfter []*release.Release + wantErr bool + }{ + { + name: "Add release to an empty list.", + fields: fields{ + Rels: []*release.Release{}, + }, + args: args{ + ns: "default", + opts: []InstallOption{ReleaseName("new-release")}, + }, + want: &rls.InstallReleaseResponse{ + Release: ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + wantErr: false, + }, + { + name: "Try to add a release where the name already exists.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + }, + args: args{ + ns: "default", + opts: []InstallOption{ReleaseName("new-release")}, + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &FakeClient{ + Rels: tt.fields.Rels, + } + got, err := c.InstallReleaseFromChart(installChart, tt.args.ns, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("FakeClient.InstallReleaseFromChart() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FakeClient.InstallReleaseFromChart() = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(c.Rels, tt.relsAfter) { + t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", got, tt.relsAfter) + } + }) + } +} + +func TestFakeClient_DeleteRelease(t *testing.T) { + type fields struct { + Rels []*release.Release + } + type args struct { + rlsName string + opts []DeleteOption + } + tests := []struct { + name string + fields fields + args args + want *rls.UninstallReleaseResponse + relsAfter []*release.Release + wantErr bool + }{ + { + name: "Delete a release that exists.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + }, + args: args{ + rlsName: "trepid-tapir", + opts: []DeleteOption{}, + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + }, + want: &rls.UninstallReleaseResponse{ + Release: ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + wantErr: false, + }, + { + name: "Delete a release that does not exist.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + }, + args: args{ + rlsName: "release-that-does-not-exists", + opts: []DeleteOption{}, + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + want: nil, + wantErr: true, + }, + { + name: "Delete when only 1 item exists.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + }, + args: args{ + rlsName: "trepid-tapir", + opts: []DeleteOption{}, + }, + relsAfter: []*release.Release{}, + want: &rls.UninstallReleaseResponse{ + Release: ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &FakeClient{ + Rels: tt.fields.Rels, + } + got, err := c.DeleteRelease(tt.args.rlsName, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("FakeClient.DeleteRelease() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FakeClient.DeleteRelease() = %v, want %v", got, tt.want) + } + + if !reflect.DeepEqual(c.Rels, tt.relsAfter) { + t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", got, tt.relsAfter) + } + }) + } +} diff --git a/pkg/helm/helmpath/helmhome.go b/pkg/helm/helmpath/helmhome.go index 7afb96364..3c35c3b69 100644 --- a/pkg/helm/helmpath/helmhome.go +++ b/pkg/helm/helmpath/helmhome.go @@ -88,7 +88,22 @@ func (h Home) Plugins() string { return h.Path("plugins") } -// Archive returns the path to download chart archives +// Archive returns the path to download chart archives. func (h Home) Archive() string { return h.Path("cache", "archive") } + +// TLSCaCert returns the path to fetch the CA certificate. +func (h Home) TLSCaCert() string { + return h.Path("ca.pem") +} + +// TLSCert returns the path to fetch the client certificate. +func (h Home) TLSCert() string { + return h.Path("cert.pem") +} + +// TLSKey returns the path to fetch the client public key. +func (h Home) TLSKey() string { + return h.Path("key.pem") +} diff --git a/pkg/helm/helmpath/helmhome_unix_test.go b/pkg/helm/helmpath/helmhome_unix_test.go index 339883f22..634faeef7 100644 --- a/pkg/helm/helmpath/helmhome_unix_test.go +++ b/pkg/helm/helmpath/helmhome_unix_test.go @@ -39,6 +39,9 @@ func TestHelmHome(t *testing.T) { isEq(t, hh.RelativeIndex("t"), "t-index.yaml") isEq(t, hh.Starters(), "/r/starters") isEq(t, hh.Archive(), "/r/cache/archive") + isEq(t, hh.TLSCaCert(), "/r/ca.pem") + isEq(t, hh.TLSCert(), "/r/cert.pem") + isEq(t, hh.TLSKey(), "/r/key.pem") } func TestHelmHome_expand(t *testing.T) { diff --git a/pkg/helm/helmpath/helmhome_windows_test.go b/pkg/helm/helmpath/helmhome_windows_test.go index 779454cc3..43db9b8f1 100644 --- a/pkg/helm/helmpath/helmhome_windows_test.go +++ b/pkg/helm/helmpath/helmhome_windows_test.go @@ -36,4 +36,7 @@ func TestHelmHome(t *testing.T) { isEq(t, hh.RelativeIndex("t"), "t-index.yaml") isEq(t, hh.Starters(), "r:\\starters") isEq(t, hh.Archive(), "r:\\cache\\archive") + isEq(t, hh.TLSCaCert(), "r:\\ca.pem") + isEq(t, hh.TLSCert(), "r:\\cert.pem") + isEq(t, hh.TLSKey(), "r:\\key.pem") } diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 2b30cd3c5..4f6924db2 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -348,7 +348,8 @@ func ResetValues(reset bool) UpdateOption { } } -// ReuseValues will (if true) trigger resetting the values to their original state. +// ReuseValues will cause Tiller to reuse the values from the last release. +// This is ignored if ResetValues is true. func ReuseValues(reuse bool) UpdateOption { return func(opts *options) { opts.reuseValues = reuse @@ -425,7 +426,7 @@ func WithMaxHistory(max int32) HistoryOption { // NewContext creates a versioned context. func NewContext() context.Context { md := metadata.Pairs("x-helm-api-client", version.GetVersion()) - return metadata.NewContext(context.TODO(), md) + return metadata.NewOutgoingContext(context.TODO(), md) } // ReleaseTestOption allows configuring optional request data for 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/ignore/rules.go b/pkg/ignore/rules.go index aba089613..76f45fc7a 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -82,6 +82,11 @@ func (r *Rules) Len() int { // Ignore evaluates path against the rules in order. Evaluation stops when a match // is found. Matching a negative rule will stop evaluation. func (r *Rules) Ignore(path string, fi os.FileInfo) bool { + // Don't match on empty dirs. + if path == "" { + return false + } + // Disallow ignoring the current working directory. // See issue: // 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?" diff --git a/pkg/ignore/rules_test.go b/pkg/ignore/rules_test.go index 6afc4345b..17b8bf403 100644 --- a/pkg/ignore/rules_test.go +++ b/pkg/ignore/rules_test.go @@ -102,6 +102,9 @@ func TestIgnore(t *testing.T) { // "." should never get ignored. https://github.com/kubernetes/helm/issues/1776 {`.*`, ".", false}, {`.*`, "./", false}, + {`.*`, ".joonix", true}, + {`.*`, "helm.txt", false}, + {`.*`, "", false}, // Directory tests {`cargo/`, "cargo", true}, diff --git a/pkg/ignore/testdata/.joonix b/pkg/ignore/testdata/.joonix new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/kube/client.go b/pkg/kube/client.go index a642ebc05..f117d7ee9 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,18 +44,20 @@ 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" ) +const ( + // MissingGetHeader is added to Get's outout when a resource is not found. + MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n" +) + // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. var ErrNoObjectsVisited = goerrors.New("no objects visited") @@ -103,13 +109,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 +119,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, ""). @@ -215,7 +222,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { } } if len(missing) > 0 { - buf.WriteString("==> MISSING\nKIND\t\tNAME\n") + buf.WriteString(MissingGetHeader) for _, s := range missing { fmt.Fprintln(buf, s) } 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/config.go b/pkg/kube/config.go index b6560486e..541d4eba6 100644 --- a/pkg/kube/config.go +++ b/pkg/kube/config.go @@ -19,7 +19,7 @@ package kube // import "k8s.io/helm/pkg/kube" import "k8s.io/client-go/tools/clientcmd" // GetConfig returns a Kubernetes client config for a given context. -func GetConfig(context string) clientcmd.ClientConfig { +func GetConfig(context string, kubeconfig string) clientcmd.ClientConfig { rules := clientcmd.NewDefaultClientConfigLoadingRules() rules.DefaultClientConfig = &clientcmd.DefaultClientConfig @@ -28,5 +28,10 @@ func GetConfig(context string) clientcmd.ClientConfig { if context != "" { overrides.CurrentContext = context } + + if kubeconfig != "" { + rules.ExplicitPath = kubeconfig + } + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides) } 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..08280f25d 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 @@ -58,7 +59,6 @@ func NewTunnel(client rest.Interface, config *rest.Config, namespace, podName st // Close disconnects a tunnel connection func (t *Tunnel) Close() { close(t.stopChan) - close(t.readyChan) } // ForwardPort opens a tunnel to a kubernetes pod @@ -71,10 +71,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 99a9bb15e..abab31661 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{} @@ -63,25 +60,25 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { return false, err } switch value := obj.(type) { - case (*v1.ReplicationController): - list, err := getPods(client, value.Namespace, value.Spec.Selector) + case *v1.ReplicationController: + 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{}) + case *v1.Pod: + 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{}) + case *extensions.Deployment: + 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 } @@ -90,32 +87,38 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { currentDeployment, } deployments = append(deployments, newDeployment) - case (*extensions.DaemonSet): - list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) + case *extensions.DaemonSet: + 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 (*extensions.ReplicaSet): - list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) + case *appsv1beta2.StatefulSet: + 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{}) + case *extensions.ReplicaSet: + 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 := 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{}) + case *v1.Service: + svc, err := kcs.Core().Services(value.Namespace).Get(value.Name, metav1.GetOptions{}) if err != nil { return false, err } @@ -178,20 +181,10 @@ func (c *Client) deploymentsReady(deployments []deployment) bool { 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/rules/chartfile.go b/pkg/lint/rules/chartfile.go index fb94cc4b9..0dab0d250 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -141,6 +141,8 @@ func validateChartMaintainer(cf *chart.Metadata) error { return errors.New("each maintainer requires a name") } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) + } else if maintainer.Url != "" && !govalidator.IsURL(maintainer.Url) { + return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.Url, maintainer.Name) } } return nil diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go new file mode 100644 index 000000000..203f038f2 --- /dev/null +++ b/pkg/plugin/installer/http_installer.go @@ -0,0 +1,207 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package installer // import "k8s.io/helm/pkg/plugin/installer" + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin/cache" + "os" + "path/filepath" + "regexp" + "strings" +) + +// HTTPInstaller installs plugins from an archive served by a web server. +type HTTPInstaller struct { + CacheDir string + PluginName string + base + extractor Extractor + getter getter.Getter +} + +// TarGzExtractor extracts gzip compressed tar archives +type TarGzExtractor struct{} + +// Extractor provides an interface for extracting archives +type Extractor interface { + Extract(buffer *bytes.Buffer, targetDir string) error +} + +// Extractors contains a map of suffixes and matching implementations of extractor to return +var Extractors = map[string]Extractor{ + ".tar.gz": &TarGzExtractor{}, + ".tgz": &TarGzExtractor{}, +} + +// NewExtractor creates a new extractor matching the source file name +func NewExtractor(source string) (Extractor, error) { + for suffix, extractor := range Extractors { + if strings.HasSuffix(source, suffix) { + return extractor, nil + } + } + return nil, fmt.Errorf("no extractor implemented yet for %s", source) +} + +// NewHTTPInstaller creates a new HttpInstaller. +func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error) { + + key, err := cache.Key(source) + if err != nil { + return nil, err + } + + extractor, err := NewExtractor(source) + if err != nil { + return nil, err + } + + getConstructor, err := getter.ByScheme("http", environment.EnvSettings{}) + if err != nil { + return nil, err + } + + get, err := getConstructor.New(source, "", "", "") + if err != nil { + return nil, err + } + + i := &HTTPInstaller{ + CacheDir: home.Path("cache", "plugins", key), + PluginName: stripPluginName(filepath.Base(source)), + base: newBase(source, home), + extractor: extractor, + getter: get, + } + return i, nil +} + +// helper that relies on some sort of convention for plugin name (plugin-name-) +func stripPluginName(name string) string { + var strippedName string + for suffix := range Extractors { + if strings.HasSuffix(name, suffix) { + strippedName = strings.TrimSuffix(name, suffix) + break + } + } + re := regexp.MustCompile(`(.*)-[0-9]+\..*`) + return re.ReplaceAllString(strippedName, `$1`) +} + +// Install downloads and extracts the tarball into the cache directory and creates a symlink to the plugin directory in $HELM_HOME. +// +// Implements Installer. +func (i *HTTPInstaller) Install() error { + + pluginData, err := i.getter.Get(i.Source) + if err != nil { + return err + } + + err = i.extractor.Extract(pluginData, i.CacheDir) + if err != nil { + return err + } + + if !isPlugin(i.CacheDir) { + return ErrMissingMetadata + } + + src, err := filepath.Abs(i.CacheDir) + if err != nil { + return err + } + + return i.link(src) +} + +// Update updates a local repository +// Not implemented for now since tarball most likely will be packaged by version +func (i *HTTPInstaller) Update() error { + return fmt.Errorf("method Update() not implemented for HttpInstaller") +} + +// Override link because we want to use HttpInstaller.Path() not base.Path() +func (i *HTTPInstaller) link(from string) error { + debug("symlinking %s to %s", from, i.Path()) + return os.Symlink(from, i.Path()) +} + +// Path is overridden because we want to join on the plugin name not the file name +func (i HTTPInstaller) Path() string { + if i.base.Source == "" { + return "" + } + return filepath.Join(i.base.HelmHome.Plugins(), i.PluginName) +} + +// Extract extracts compressed archives +// +// Implements Extractor. +func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { + uncompressedStream, err := gzip.NewReader(buffer) + if err != nil { + return err + } + + tarReader := tar.NewReader(uncompressedStream) + + os.MkdirAll(targetDir, 0755) + + for true { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + return err + } + + path := filepath.Join(targetDir, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + if err := os.Mkdir(path, 0755); err != nil { + return err + } + case tar.TypeReg: + outFile, err := os.Create(path) + if err != nil { + return err + } + defer outFile.Close() + if _, err := io.Copy(outFile, tarReader); err != nil { + return err + } + default: + return fmt.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) + } + } + + return nil + +} diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go new file mode 100644 index 000000000..ca1a71e3e --- /dev/null +++ b/pkg/plugin/installer/http_installer_test.go @@ -0,0 +1,189 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package installer // import "k8s.io/helm/pkg/plugin/installer" + +import ( + "bytes" + "encoding/base64" + "fmt" + "io/ioutil" + "k8s.io/helm/pkg/helm/helmpath" + "os" + "testing" +) + +var _ Installer = new(HTTPInstaller) + +// Fake http client +type TestHTTPGetter struct { + MockResponse *bytes.Buffer + MockError error +} + +func (t *TestHTTPGetter) Get(href string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError } + +// Fake plugin tarball data +var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA=" + +func TestStripName(t *testing.T) { + if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { + t.Errorf("name does not match expected value") + } + if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { + t.Errorf("name does not match expected value") + } + if stripPluginName("fake-plugin.tgz") != "fake-plugin" { + t.Errorf("name does not match expected value") + } + if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" { + t.Errorf("name does not match expected value") + } +} + +func TestHTTPInstaller(t *testing.T) { + source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + i, err := NewForSource(source, "0.0.1", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a HTTPInstaller was returned + httpInstaller, ok := i.(*HTTPInstaller) + if !ok { + t.Error("expected a HTTPInstaller") + } + + // inject fake http client responding with minimal plugin tarball + mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) + if err != nil { + t.Fatalf("Could not decode fake tgz plugin: %s", err) + } + + httpInstaller.getter = &TestHTTPGetter{ + MockResponse: bytes.NewBuffer(mockTgz), + } + + // install the plugin + if err := Install(i); err != nil { + t.Error(err) + } + if i.Path() != home.Path("plugins", "fake-plugin") { + t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) + } + + // Install again to test plugin exists error + if err := Install(i); err == nil { + t.Error("expected error for plugin exists, got none") + } else if err.Error() != "plugin already exists" { + t.Errorf("expected error for plugin exists, got (%v)", err) + } + +} + +func TestHTTPInstallerNonExistentVersion(t *testing.T) { + source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + i, err := NewForSource(source, "0.0.2", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a HTTPInstaller was returned + httpInstaller, ok := i.(*HTTPInstaller) + if !ok { + t.Error("expected a HTTPInstaller") + } + + // inject fake http client responding with error + httpInstaller.getter = &TestHTTPGetter{ + MockError: fmt.Errorf("failed to download plugin for some reason"), + } + + // attempt to install the plugin + if err := Install(i); err == nil { + t.Error("expected error from http client") + } + +} + +func TestHTTPInstallerUpdate(t *testing.T) { + source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + i, err := NewForSource(source, "0.0.1", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a HTTPInstaller was returned + httpInstaller, ok := i.(*HTTPInstaller) + if !ok { + t.Error("expected a HTTPInstaller") + } + + // inject fake http client responding with minimal plugin tarball + mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) + if err != nil { + t.Fatalf("Could not decode fake tgz plugin: %s", err) + } + + httpInstaller.getter = &TestHTTPGetter{ + MockResponse: bytes.NewBuffer(mockTgz), + } + + // install the plugin before updating + if err := Install(i); err != nil { + t.Error(err) + } + if i.Path() != home.Path("plugins", "fake-plugin") { + t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) + } + + // Update plugin, should fail because it is not implemented + if err := Update(i); err == nil { + t.Error("update method not implemented for http installer") + } +} diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index 6ecec980a..10433f4cd 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -23,6 +23,7 @@ import ( "path/filepath" "k8s.io/helm/pkg/helm/helmpath" + "strings" ) // ErrMissingMetadata indicates that plugin.yaml is missing. @@ -68,6 +69,8 @@ func NewForSource(source, version string, home helmpath.Home) (Installer, error) // Check if source is a local directory if isLocalReference(source) { return NewLocalInstaller(source, home) + } else if isRemoteHTTPArchive(source) { + return NewHTTPInstaller(source, home) } return NewVCSInstaller(source, version, home) } @@ -87,6 +90,18 @@ func isLocalReference(source string) bool { return err == nil } +// isRemoteHTTPArchive checks if the source is a http/https url and is an archive +func isRemoteHTTPArchive(source string) bool { + if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + for suffix := range Extractors { + if strings.HasSuffix(source, suffix) { + return true + } + } + } + return false +} + // isPlugin checks if the directory contains a plugin.yaml file. func isPlugin(dirname string) bool { _, err := os.Stat(filepath.Join(dirname, "plugin.yaml")) diff --git a/pkg/proto/hapi/chart/metadata.pb.go b/pkg/proto/hapi/chart/metadata.pb.go index af9f58153..c9034ffdc 100644 --- a/pkg/proto/hapi/chart/metadata.pb.go +++ b/pkg/proto/hapi/chart/metadata.pb.go @@ -39,6 +39,8 @@ type Maintainer struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // Email is an optional email address to contact the named maintainer Email string `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"` + // Url is an optional URL to an address for the named maintainer + Url string `protobuf:"bytes,3,opt,name=url" json:"url,omitempty"` } func (m *Maintainer) Reset() { *m = Maintainer{} } @@ -60,6 +62,13 @@ func (m *Maintainer) GetEmail() string { return "" } +func (m *Maintainer) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + // Metadata for a Chart file. This models the structure of a Chart.yaml file. // // Spec: https://k8s.io/helm/blob/master/docs/design/chart_format.md#the-chart-file @@ -226,31 +235,32 @@ func init() { func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor2) } var fileDescriptor2 = []byte{ - // 412 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5d, 0x6b, 0xd5, 0x40, - 0x10, 0x35, 0xbd, 0x9f, 0x99, 0x58, 0x0d, 0x83, 0x94, 0xb5, 0x88, 0x84, 0x8b, 0x42, 0x9e, 0x52, - 0x50, 0x90, 0xe2, 0x83, 0xa0, 0x50, 0xfa, 0xa0, 0xbd, 0x95, 0xe0, 0x07, 0xf8, 0xb6, 0x26, 0x43, - 0xef, 0xd2, 0x64, 0x37, 0xec, 0x6e, 0x2b, 0xf7, 0x47, 0xfb, 0x1f, 0x64, 0x37, 0xd9, 0x26, 0x95, - 0xbe, 0xcd, 0x39, 0x67, 0xe6, 0xec, 0x9e, 0x61, 0xe0, 0xf9, 0x8e, 0x77, 0xe2, 0xa4, 0xda, 0x71, - 0x6d, 0x4f, 0x5a, 0xb2, 0xbc, 0xe6, 0x96, 0x17, 0x9d, 0x56, 0x56, 0x21, 0x38, 0xa9, 0xf0, 0xd2, - 0xe6, 0x1d, 0xc0, 0x05, 0x17, 0xd2, 0x72, 0x21, 0x49, 0x23, 0xc2, 0x5c, 0xf2, 0x96, 0x58, 0x94, - 0x45, 0x79, 0x5c, 0xfa, 0x1a, 0x9f, 0xc1, 0x82, 0x5a, 0x2e, 0x1a, 0x76, 0xe0, 0xc9, 0x1e, 0x6c, - 0xfe, 0xce, 0x61, 0x7d, 0x31, 0xd8, 0x3e, 0x38, 0x86, 0x30, 0xdf, 0xa9, 0x96, 0x86, 0x29, 0x5f, - 0x23, 0x83, 0x95, 0x51, 0x37, 0xba, 0x22, 0xc3, 0x66, 0xd9, 0x2c, 0x8f, 0xcb, 0x00, 0x9d, 0x72, - 0x4b, 0xda, 0x08, 0x25, 0xd9, 0xdc, 0x0f, 0x04, 0x88, 0x19, 0x24, 0x35, 0x99, 0x4a, 0x8b, 0xce, - 0x3a, 0x75, 0xe1, 0xd5, 0x29, 0x85, 0xc7, 0xb0, 0xbe, 0xa6, 0xfd, 0x1f, 0xa5, 0x6b, 0xc3, 0x96, - 0xde, 0xf6, 0x0e, 0xe3, 0x29, 0x24, 0xed, 0x5d, 0x3c, 0xc3, 0x56, 0xd9, 0x2c, 0x4f, 0xde, 0x1c, - 0x15, 0xe3, 0x02, 0x8a, 0x31, 0x7d, 0x39, 0x6d, 0xc5, 0x23, 0x58, 0x92, 0xbc, 0x12, 0x92, 0xd8, - 0xda, 0x3f, 0x39, 0x20, 0x97, 0x4b, 0x54, 0x4a, 0xb2, 0xb8, 0xcf, 0xe5, 0x6a, 0x7c, 0x09, 0xc0, - 0x3b, 0xf1, 0x63, 0x08, 0x00, 0x5e, 0x99, 0x30, 0xf8, 0x02, 0xe2, 0x4a, 0xc9, 0x5a, 0xf8, 0x04, - 0x89, 0x97, 0x47, 0xc2, 0x39, 0x5a, 0x7e, 0x65, 0xd8, 0xe3, 0xde, 0xd1, 0xd5, 0xbd, 0x63, 0x17, - 0x1c, 0x0f, 0x83, 0x63, 0x60, 0x9c, 0x5e, 0x53, 0xa7, 0xa9, 0xe2, 0x96, 0x6a, 0xf6, 0x24, 0x8b, - 0xf2, 0x75, 0x39, 0x61, 0xf0, 0x15, 0x1c, 0x5a, 0xd1, 0x34, 0xa4, 0x83, 0xc5, 0x53, 0x6f, 0x71, - 0x9f, 0xc4, 0x73, 0x48, 0xb8, 0x94, 0xca, 0x72, 0xf7, 0x0f, 0xc3, 0x52, 0xbf, 0x9d, 0xd7, 0xf7, - 0xb6, 0x13, 0x2e, 0xe7, 0xe3, 0xd8, 0x77, 0x26, 0xad, 0xde, 0x97, 0xd3, 0xc9, 0xe3, 0x0f, 0x90, - 0xfe, 0xdf, 0x80, 0x29, 0xcc, 0xae, 0x69, 0x3f, 0xdc, 0x84, 0x2b, 0xdd, 0x25, 0xdd, 0xf2, 0xe6, - 0x26, 0xdc, 0x44, 0x0f, 0xde, 0x1f, 0x9c, 0x46, 0x9b, 0x0c, 0x96, 0x67, 0xfd, 0x7a, 0x13, 0x58, - 0x7d, 0xdf, 0x7e, 0xde, 0x5e, 0xfe, 0xdc, 0xa6, 0x8f, 0x30, 0x86, 0xc5, 0xf9, 0xe5, 0xb7, 0xaf, - 0x5f, 0xd2, 0xe8, 0xd3, 0xea, 0xd7, 0xc2, 0xff, 0xe8, 0xf7, 0xd2, 0xdf, 0xf0, 0xdb, 0x7f, 0x01, - 0x00, 0x00, 0xff, 0xff, 0xd7, 0x2b, 0xf5, 0x83, 0xe0, 0x02, 0x00, 0x00, + // 420 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5b, 0x6b, 0xd4, 0x40, + 0x14, 0x36, 0xcd, 0xde, 0x72, 0x62, 0x35, 0x1c, 0xa4, 0x8c, 0x45, 0x24, 0x2c, 0x0a, 0xfb, 0xb4, + 0x05, 0x7d, 0x29, 0x3e, 0x08, 0x0a, 0xa5, 0x82, 0x76, 0x2b, 0xc1, 0x0b, 0xf8, 0x36, 0x26, 0x87, + 0xee, 0xd0, 0x64, 0x26, 0x4c, 0x66, 0x2b, 0xfb, 0xa3, 0xfd, 0x0f, 0x32, 0x27, 0x49, 0x93, 0x95, + 0xbe, 0x7d, 0x97, 0x99, 0x6f, 0xe6, 0x1c, 0x3e, 0x78, 0xbe, 0x95, 0xb5, 0x3a, 0xcb, 0xb7, 0xd2, + 0xba, 0xb3, 0x8a, 0x9c, 0x2c, 0xa4, 0x93, 0xeb, 0xda, 0x1a, 0x67, 0x10, 0xbc, 0xb5, 0x66, 0x6b, + 0xf9, 0x09, 0xe0, 0x4a, 0x2a, 0xed, 0xa4, 0xd2, 0x64, 0x11, 0x61, 0xa2, 0x65, 0x45, 0x22, 0x48, + 0x83, 0x55, 0x94, 0x31, 0xc6, 0x67, 0x30, 0xa5, 0x4a, 0xaa, 0x52, 0x1c, 0xb1, 0xd8, 0x12, 0x4c, + 0x20, 0xdc, 0xd9, 0x52, 0x84, 0xac, 0x79, 0xb8, 0xfc, 0x3b, 0x81, 0xc5, 0x55, 0xf7, 0xd0, 0x83, + 0x41, 0x08, 0x93, 0xad, 0xa9, 0xa8, 0xcb, 0x61, 0x8c, 0x02, 0xe6, 0x8d, 0xd9, 0xd9, 0x9c, 0x1a, + 0x11, 0xa6, 0xe1, 0x2a, 0xca, 0x7a, 0xea, 0x9d, 0x3b, 0xb2, 0x8d, 0x32, 0x5a, 0x4c, 0xf8, 0x42, + 0x4f, 0x31, 0x85, 0xb8, 0xa0, 0x26, 0xb7, 0xaa, 0x76, 0xde, 0x9d, 0xb2, 0x3b, 0x96, 0xf0, 0x14, + 0x16, 0xb7, 0xb4, 0xff, 0x63, 0x6c, 0xd1, 0x88, 0x19, 0xc7, 0xde, 0x73, 0x3c, 0x87, 0xb8, 0xba, + 0x1f, 0xb8, 0x11, 0xf3, 0x34, 0x5c, 0xc5, 0x6f, 0x4e, 0xd6, 0xc3, 0x4a, 0xd6, 0xc3, 0x3e, 0xb2, + 0xf1, 0x51, 0x3c, 0x81, 0x19, 0xe9, 0x1b, 0xa5, 0x49, 0x2c, 0xf8, 0xc9, 0x8e, 0xf9, 0xb9, 0x54, + 0x6e, 0xb4, 0x88, 0xda, 0xb9, 0x3c, 0xc6, 0x97, 0x00, 0xb2, 0x56, 0x3f, 0xba, 0x01, 0x80, 0x9d, + 0x91, 0x82, 0x2f, 0x20, 0xca, 0x8d, 0x2e, 0x14, 0x4f, 0x10, 0xb3, 0x3d, 0x08, 0x3e, 0xd1, 0xc9, + 0x9b, 0x46, 0x3c, 0x6e, 0x13, 0x3d, 0x6e, 0x13, 0xeb, 0x3e, 0xf1, 0xb8, 0x4f, 0xec, 0x15, 0xef, + 0x17, 0x54, 0x5b, 0xca, 0xa5, 0xa3, 0x42, 0x3c, 0x49, 0x83, 0xd5, 0x22, 0x1b, 0x29, 0xf8, 0x0a, + 0x8e, 0x9d, 0x2a, 0x4b, 0xb2, 0x7d, 0xc4, 0x53, 0x8e, 0x38, 0x14, 0xf1, 0x12, 0x62, 0xa9, 0xb5, + 0x71, 0xd2, 0xff, 0xa3, 0x11, 0x09, 0x6f, 0xe7, 0xf5, 0xc1, 0x76, 0xfa, 0x2e, 0x7d, 0x18, 0xce, + 0x5d, 0x68, 0x67, 0xf7, 0xd9, 0xf8, 0xe6, 0xe9, 0x7b, 0x48, 0xfe, 0x3f, 0xe0, 0x3b, 0x73, 0x4b, + 0xfb, 0xae, 0x13, 0x1e, 0xfa, 0x6e, 0xdd, 0xc9, 0x72, 0xd7, 0x77, 0xa2, 0x25, 0xef, 0x8e, 0xce, + 0x83, 0x65, 0x0a, 0xb3, 0x8b, 0x76, 0xbd, 0x31, 0xcc, 0xbf, 0x6f, 0x3e, 0x6f, 0xae, 0x7f, 0x6e, + 0x92, 0x47, 0x18, 0xc1, 0xf4, 0xf2, 0xfa, 0xdb, 0xd7, 0x2f, 0x49, 0xf0, 0x71, 0xfe, 0x6b, 0xca, + 0x3f, 0xfa, 0x3d, 0xe3, 0x56, 0xbf, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x40, 0x4c, 0x34, 0x92, + 0xf2, 0x02, 0x00, 0x00, } diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 97506e607..b95a7ae07 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 @@ -179,7 +184,7 @@ func (r *ChartRepository) generateIndex() error { } // FindChartInRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repostiories +// without adding repo to repositories func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { // Download and write the index file to a temporary location @@ -222,5 +227,28 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF return "", fmt.Errorf("%s has no downloadable URLs", errMsg) } - return cv.URLs[0], nil + chartURL := cv.URLs[0] + + absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL) + if err != nil { + return "", fmt.Errorf("failed to make chart URL absolute: %v", err) + } + + return absoluteChartURL, nil +} + +// ResolveReferenceURL resolves refURL relative to baseURL. +// If refURL is absolute, it simply returns refURL. +func ResolveReferenceURL(baseURL, refURL string) (string, error) { + parsedBaseURL, err := url.Parse(baseURL) + if err != nil { + return "", fmt.Errorf("failed to parse %s as URL: %v", baseURL, err) + } + + parsedRefURL, err := url.Parse(refURL) + if err != nil { + return "", fmt.Errorf("failed to parse %s as URL: %v", refURL, err) + } + + return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil } diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 9f1bc995a..948ee12d3 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -277,3 +277,21 @@ func TestErrorFindChartInRepoURL(t *testing.T) { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) } } + +func TestResolveReferenceURL(t *testing.T) { + chartURL, err := ResolveReferenceURL("http://localhost:8123/charts/", "nginx-0.2.0.tgz") + if err != nil { + t.Errorf("%s", err) + } + if chartURL != "http://localhost:8123/charts/nginx-0.2.0.tgz" { + t.Errorf("%s", chartURL) + } + + chartURL, err = ResolveReferenceURL("http://localhost:8123", "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz") + if err != nil { + t.Errorf("%s", err) + } + if chartURL != "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz" { + t.Errorf("%s", chartURL) + } +} 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/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/storage/storage.go b/pkg/storage/storage.go index e1a570188..4ac5dbf4f 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -18,6 +18,7 @@ package storage // import "k8s.io/helm/pkg/storage" import ( "fmt" + "strings" rspb "k8s.io/helm/pkg/proto/hapi/release" relutil "k8s.io/helm/pkg/releaseutil" @@ -127,14 +128,13 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { "OWNER": "TILLER", "STATUS": "DEPLOYED", }) - switch { - case err != nil: - return nil, err - case len(ls) == 0: - return nil, fmt.Errorf("%q has no deployed releases", name) - default: + if err == nil { return ls[0], nil } + if strings.Contains(err.Error(), "not found") { + return nil, fmt.Errorf("%q has no deployed releases", name) + } + return nil, err } // History returns the revision history for the release with the provided name, or diff --git a/pkg/tiller/kind_sorter.go b/pkg/tiller/kind_sorter.go index 964c01549..f367e65c8 100644 --- a/pkg/tiller/kind_sorter.go +++ b/pkg/tiller/kind_sorter.go @@ -36,6 +36,7 @@ var InstallOrder SortOrder = []string{ "PersistentVolume", "PersistentVolumeClaim", "ServiceAccount", + "CustomResourceDefinition", "ClusterRole", "ClusterRoleBinding", "Role", @@ -72,6 +73,7 @@ var UninstallOrder SortOrder = []string{ "Role", "ClusterRoleBinding", "ClusterRole", + "CustomResourceDefinition", "ServiceAccount", "PersistentVolumeClaim", "PersistentVolume", diff --git a/pkg/tiller/kind_sorter_test.go b/pkg/tiller/kind_sorter_test.go index 975db0563..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"}, @@ -49,10 +53,6 @@ func TestKindSorter(t *testing.T) { Name: "r", Head: &util.SimpleHead{Kind: "Deployment"}, }, - { - Name: "1", - Head: &util.SimpleHead{Kind: "StorageClass"}, - }, { Name: "!", Head: &util.SimpleHead{Kind: "HonkyTonkSet"}, @@ -121,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"}, @@ -132,8 +136,8 @@ func TestKindSorter(t *testing.T) { order SortOrder expected string }{ - {"install", InstallOrder, "abcde1fghijklmnopqrstuvw!"}, - {"uninstall", UninstallOrder, "wvmutsrqponlkjihgf1edcba!"}, + {"install", InstallOrder, "abcde1fgh2ijklmnopqrstuvw!"}, + {"uninstall", UninstallOrder, "wvmutsrqponlkji2hgf1edcba!"}, } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { diff --git a/pkg/tiller/release_modules.go b/pkg/tiller/release_modules.go index b5fbbeb44..876e1ba37 100644 --- a/pkg/tiller/release_modules.go +++ b/pkg/tiller/release_modules.go @@ -161,7 +161,7 @@ func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient env filesToKeep, filesToDelete := filterManifestsToKeep(files) if len(filesToKeep) > 0 { - kept = summarizeKeptManifests(filesToKeep) + kept = summarizeKeptManifests(filesToKeep, kubeClient, rel.Namespace) } errs = []error{} diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go index 18cf56737..d251db753 100644 --- a/pkg/tiller/release_update.go +++ b/pkg/tiller/release_update.go @@ -69,8 +69,8 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele return nil, nil, errMissingChart } - // finds the non-deleted release with the given name - currentRelease, err := s.env.Releases.Last(req.Name) + // finds the deployed release with the given name + currentRelease, err := s.env.Releases.Deployed(req.Name) if err != nil { return nil, nil, err } @@ -80,9 +80,15 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele return nil, nil, err } + // finds the non-deleted release with the given name + lastRelease, err := s.env.Releases.Last(req.Name) + if err != nil { + return nil, nil, err + } + // Increment revision count. This is passed to templates, and also stored on // the release object. - revision := currentRelease.Version + 1 + revision := lastRelease.Version + 1 ts := timeconv.Now() options := chartutil.ReleaseOptions{ @@ -151,7 +157,6 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil { msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err) s.Log("warning: %s", msg) - originalRelease.Info.Status.Code = release.Status_SUPERSEDED updatedRelease.Info.Status.Code = release.Status_FAILED updatedRelease.Info.Description = msg s.recordRelease(originalRelease, true) diff --git a/pkg/tiller/release_update_test.go b/pkg/tiller/release_update_test.go index a3eb37f4a..0f2bcbabd 100644 --- a/pkg/tiller/release_update_test.go +++ b/pkg/tiller/release_update_test.go @@ -234,8 +234,8 @@ func TestUpdateReleaseFailure(t *testing.T) { if err != nil { t.Errorf("Expected to be able to get previous release") } - if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { - t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) + if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_DEPLOYED { + t.Errorf("Expected Deployed status on previous Release version. Got %v", oldStatus) } } diff --git a/pkg/tiller/resource_policy.go b/pkg/tiller/resource_policy.go index 2102ab66b..66da1283f 100644 --- a/pkg/tiller/resource_policy.go +++ b/pkg/tiller/resource_policy.go @@ -17,7 +17,11 @@ limitations under the License. package tiller import ( + "bytes" "strings" + + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/tiller/environment" ) // resourcePolicyAnno is the annotation name for a resource policy @@ -34,7 +38,6 @@ func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) { keep := []Manifest{} for _, m := range manifests { - if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 { remaining = append(remaining, m) continue @@ -55,10 +58,19 @@ func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) { return keep, remaining } -func summarizeKeptManifests(manifests []Manifest) string { - message := "These resources were kept due to the resource policy:\n" +func summarizeKeptManifests(manifests []Manifest, kubeClient environment.KubeClient, namespace string) string { + var message string for _, m := range manifests { + // check if m is in fact present from k8s client's POV. + output, err := kubeClient.Get(namespace, bytes.NewBufferString(m.Content)) + if err != nil || strings.Contains(output, kube.MissingGetHeader) { + continue + } + details := "[" + m.Head.Kind + "] " + m.Head.Metadata.Name + "\n" + if message == "" { + message = "These resources were kept due to the resource policy:\n" + } message = message + details } return message diff --git a/pkg/tiller/server.go b/pkg/tiller/server.go index 8d6b6fa13..57826578e 100644 --- a/pkg/tiller/server.go +++ b/pkg/tiller/server.go @@ -78,7 +78,7 @@ func splitMethod(fullMethod string) (string, string) { } func versionFromContext(ctx context.Context) string { - if md, ok := metadata.FromContext(ctx); ok { + if md, ok := metadata.FromIncomingContext(ctx); ok { if v, ok := md["x-helm-api-client"]; ok && len(v) > 0 { return v[0] } diff --git a/pkg/tlsutil/tls.go b/pkg/tlsutil/tls.go index 422bddacb..df698fd4e 100644 --- a/pkg/tlsutil/tls.go +++ b/pkg/tlsutil/tls.go @@ -65,7 +65,7 @@ func CertPoolFromFile(filename string) (*x509.CertPool, error) { func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return nil, fmt.Errorf("can't load key pair from cert %s and key %s", certFile, keyFile) + return nil, fmt.Errorf("can't load key pair from cert %s and key %s: %s", certFile, keyFile, err) } return &cert, err } 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" diff --git a/scripts/get b/scripts/get index 88a6fa1a7..d900ab2c2 100755 --- a/scripts/get +++ b/scripts/get @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright 2016 The Kubernetes Authors All rights reserved. #