diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 868090005..8fecb0479 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,7 +53,7 @@ An issue that we are not sure we will be doing will not be added to any mileston A milestone (and hence release) is considered done when all outstanding issues/PRs have been closed or moved to another milestone. -## Semver +## Semantic Versioning Helm maintains a strong commitment to backward compatibility. All of our changes to protocols and formats are backward compatible from Helm 2.0 until Helm 3.0. No features, flags, or commands are removed or substantially modified (other than bug fixes). diff --git a/Makefile b/Makefile index 54cc1ff18..77d7c8ff7 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 linux/s390x windows/amd64 DIST_DIRS = find * -type d -exec APP = helm diff --git a/SECURITY_CONTACTS b/SECURITY_CONTACTS new file mode 100644 index 000000000..7298ea2d2 --- /dev/null +++ b/SECURITY_CONTACTS @@ -0,0 +1,20 @@ +# Defined below are the security contacts for this repo. +# +# They are the contact point for the Product Security Team to reach out +# to for triaging and handling of incoming issues. +# +# The below names agree to abide by the +# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy) +# and will be removed and replaced if they violate that agreement. +# +# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE +# INSTRUCTIONS AT https://github.com/kubernetes/helm/blob/master/CONTRIBUTING.md#reporting-a-security-issue + +adamreese +bacongobbler +mattfarina +michelleN +prydonius +SlickNik +technosophos +thomastaylor312 diff --git a/cmd/helm/init.go b/cmd/helm/init.go index d368945ec..92f4c3794 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -312,9 +312,12 @@ func (i *initCmd) run() error { "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") } } else { - fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.\n\n"+ - "Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ - "For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation") + fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.") + if !tlsVerify { + fmt.Fprintln(i.out, "\nPlease note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ + "To prevent this, run `helm init` with the --tiller-tls-verify flag.\n"+ + "For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation") + } } if err := i.ping(); err != nil { return err diff --git a/cmd/helm/install.go b/cmd/helm/install.go index d1c24c213..ae50684e8 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -258,6 +258,12 @@ func (i *installCmd) run() error { if err := man.Update(); err != nil { return prettyError(err) } + + // Update all dependencies which are present in /charts. + chartRequested, err = chartutil.Load(i.chartPath) + if err != nil { + return prettyError(err) + } } else { return prettyError(err) } diff --git a/cmd/helm/installer/options.go b/cmd/helm/installer/options.go index 13cf43dcc..3769d12e1 100644 --- a/cmd/helm/installer/options.go +++ b/cmd/helm/installer/options.go @@ -50,7 +50,7 @@ type Options struct { // Force allows to force upgrading tiller if deployed version is greater than current version ForceUpgrade bool - // ImageSpec indentifies the image Tiller will use when deployed. + // ImageSpec identifies the image Tiller will use when deployed. // // Valid if and only if UseCanary is false. ImageSpec string diff --git a/cmd/helm/list.go b/cmd/helm/list.go index c2633d21c..4614c7f67 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -17,10 +17,12 @@ limitations under the License. package main import ( + "encoding/json" "fmt" "io" "strings" + "github.com/ghodss/yaml" "github.com/gosuri/uitable" "github.com/spf13/cobra" @@ -75,6 +77,22 @@ type listCmd struct { pending bool client helm.Interface colWidth uint + output string +} + +type listResult struct { + Next string + Releases []listRelease +} + +type listRelease struct { + Name string + Revision int32 + Updated string + Status string + Chart string + AppVersion string + Namespace string } func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { @@ -114,6 +132,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { f.BoolVar(&list.pending, "pending", false, "show pending releases") f.StringVar(&list.namespace, "namespace", "", "show releases within a specific namespace") f.UintVar(&list.colWidth, "col-width", 60, "specifies the max column width of output") + f.StringVar(&list.output, "output", "", "output the specified format (json or yaml)") // TODO: Do we want this as a feature of 'helm list'? //f.BoolVar(&list.superseded, "history", true, "show historical releases") @@ -148,23 +167,17 @@ func (l *listCmd) run() error { return prettyError(err) } - if len(res.GetReleases()) == 0 { - return nil - } + rels := filterList(res.Releases) - if res.Next != "" && !l.short { - fmt.Fprintf(l.out, "\tnext: %s\n", res.Next) - } + result := getListResult(rels, res.Next) - rels := filterList(res.Releases) + output, err := formatResult(l.output, l.short, result, l.colWidth) - if l.short { - for _, r := range rels { - fmt.Fprintln(l.out, r.Name) - } - return nil + if err != nil { + return prettyError(err) } - fmt.Fprintln(l.out, formatList(rels, l.colWidth)) + + fmt.Fprintln(l.out, output) return nil } @@ -233,23 +246,98 @@ func (l *listCmd) statusCodes() []release.Status_Code { return status } -func formatList(rels []*release.Release, colWidth uint) string { - table := uitable.New() - - table.MaxColWidth = colWidth - table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "NAMESPACE") +func getListResult(rels []*release.Release, next string) listResult { + listReleases := []listRelease{} for _, r := range rels { md := r.GetChart().GetMetadata() - c := fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion()) t := "-" if tspb := r.GetInfo().GetLastDeployed(); tspb != nil { t = timeconv.String(tspb) } - s := r.GetInfo().GetStatus().GetCode().String() - v := r.GetVersion() - a := md.GetAppVersion() - n := r.GetNamespace() - table.AddRow(r.GetName(), v, t, s, c, a, n) + + lr := listRelease{ + Name: r.GetName(), + Revision: r.GetVersion(), + Updated: t, + Status: r.GetInfo().GetStatus().GetCode().String(), + Chart: fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion()), + AppVersion: md.GetAppVersion(), + Namespace: r.GetNamespace(), + } + listReleases = append(listReleases, lr) + } + + return listResult{ + Releases: listReleases, + Next: next, } - return table.String() +} + +func shortenListResult(result listResult) []string { + names := []string{} + for _, r := range result.Releases { + names = append(names, r.Name) + } + + return names +} + +func formatResult(format string, short bool, result listResult, colWidth uint) (string, error) { + var output string + var err error + + var shortResult []string + var finalResult interface{} + if short { + shortResult = shortenListResult(result) + finalResult = shortResult + } else { + finalResult = result + } + + switch format { + case "": + if short { + output = formatTextShort(shortResult) + } else { + output = formatText(result, colWidth) + } + case "json": + o, e := json.Marshal(finalResult) + if e != nil { + err = fmt.Errorf("Failed to Marshal JSON output: %s", e) + } else { + output = string(o) + } + case "yaml": + o, e := yaml.Marshal(finalResult) + if e != nil { + err = fmt.Errorf("Failed to Marshal YAML output: %s", e) + } else { + output = string(o) + } + default: + err = fmt.Errorf("Unknown output format \"%s\"", format) + } + return output, err +} + +func formatText(result listResult, colWidth uint) string { + nextOutput := "" + if result.Next != "" { + nextOutput = fmt.Sprintf("\tnext: %s\n", result.Next) + } + + table := uitable.New() + table.MaxColWidth = colWidth + table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "NAMESPACE") + for _, lr := range result.Releases { + table.AddRow(lr.Name, lr.Revision, lr.Updated, lr.Status, lr.Chart, lr.AppVersion, lr.Namespace) + } + + return fmt.Sprintf("%s%s", nextOutput, table.String()) +} + +func formatTextShort(shortResult []string) string { + return strings.Join(shortResult, "\n") } diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index e292b4b5a..e0faee935 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -18,16 +18,18 @@ package main import ( "io" + "regexp" "testing" "github.com/spf13/cobra" "io/ioutil" + "os" + "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" - "os" ) func TestListCmd(t *testing.T) { @@ -46,6 +48,11 @@ func TestListCmd(t *testing.T) { ch, _ := chartutil.Load(chartPath) tests := []releaseCase{ + { + name: "empty", + rels: []*release.Release{}, + expected: "", + }, { name: "with a release", rels: []*release.Release{ @@ -67,6 +74,77 @@ func TestListCmd(t *testing.T) { }, expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tAPP VERSION\tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\t2.X.A \tdefault \n", }, + { + name: "with json output", + flags: []string{"--max", "1", "--output", "json"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide"}), + }, + expected: regexp.QuoteMeta(`{"Next":"atlas-guide","Releases":[{"Name":"thomas-guide","Revision":1,"Updated":"`) + `([^"]*)` + regexp.QuoteMeta(`","Status":"DEPLOYED","Chart":"foo-0.1.0-beta.1","AppVersion":"","Namespace":"default"}]} +`), + }, + { + name: "with yaml output", + flags: []string{"--max", "1", "--output", "yaml"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide"}), + }, + expected: regexp.QuoteMeta(`Next: atlas-guide +Releases: +- AppVersion: "" + Chart: foo-0.1.0-beta.1 + Name: thomas-guide + Namespace: default + Revision: 1 + Status: DEPLOYED + Updated: `) + `(.*)` + ` + +`, + }, + { + name: "with short json output", + flags: []string{"-q", "--output", "json"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}), + }, + expected: regexp.QuoteMeta(`["atlas"] +`), + }, + { + name: "with short yaml output", + flags: []string{"-q", "--output", "yaml"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}), + }, + expected: regexp.QuoteMeta(`- atlas + +`), + }, + { + name: "with json output without next", + flags: []string{"--output", "json"}, + rels: []*release.Release{}, + expected: regexp.QuoteMeta(`{"Next":"","Releases":[]} +`), + }, + { + name: "with yaml output without next", + flags: []string{"--output", "yaml"}, + rels: []*release.Release{}, + expected: regexp.QuoteMeta(`Next: "" +Releases: [] + +`), + }, + { + name: "with unknown output format", + flags: []string{"--output", "_unknown_"}, + rels: []*release.Release{}, + err: true, + expected: regexp.QuoteMeta(``), + }, { name: "list, one deployed, one failed", flags: []string{"-q"}, diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index d15dc5666..263b6bb35 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -68,8 +68,8 @@ func TestTemplateCmd(t *testing.T) { }, { name: "check_execute_non_existent", - desc: "verify --execute fails on a template that doesnt exist", - args: []string{subchart1ChartPath, "-x", "templates/thisdoesntexist.yaml"}, + desc: "verify --execute fails on a template that doesn't exist", + args: []string{subchart1ChartPath, "-x", "templates/thisdoesn'texist.yaml"}, expectError: "could not find template", }, { diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 5d2db3816..c97187b2a 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -85,7 +85,7 @@ var ( // rootServer is the root gRPC server. // - // Each gRPC service registers itself to this server during init(). + // Each gRPC service registers itself to this server during start(). rootServer *grpc.Server // env is the default environment. diff --git a/docs/index.md b/docs/README.md similarity index 100% rename from docs/index.md rename to docs/README.md diff --git a/docs/charts.md b/docs/charts.md index a55038e3e..656731182 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -265,7 +265,7 @@ dependencies: - name: subchart1 repository: http://localhost:10191 version: 0.1.0 - condition: subchart1.enabled, global.subchart1.enabled + condition: subchart1.enabled,global.subchart1.enabled tags: - front-end - subchart1 @@ -303,7 +303,6 @@ The `--set` parameter can be used as usual to alter tag and condition values. ```` helm install --set tags.front-end=true --set subchart2.enabled=false - ```` ##### Tags and Condition Resolution diff --git a/docs/charts_tips_and_tricks.md b/docs/charts_tips_and_tricks.md index 484d8b936..a83b44457 100644 --- a/docs/charts_tips_and_tricks.md +++ b/docs/charts_tips_and_tricks.md @@ -106,6 +106,43 @@ 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. +## Using the 'tpl' Function + +The `tpl` function allows developers to evaluate strings as templates inside a template. +This is useful to pass a template string as a value to a chart or render external configuration files. +Syntax: `{{ tpl TEMPLATE_STRING VALUES }}` + +Examples: +``` +# values +template: "{{ .Values.name }}" +name: "Tom" + +# template +{{ tpl .Values.template . }} + +# output +Tom +``` + +Rendering a external configuration file: +``` +# external configuration file conf/app.conf +firstName={{ .Values.firstName }} +lastName={{ .Values.lastName }} + +# values +firstName: Peter +lastName: Parker + +# template +{{ tpl (.Files.Get "conf/app.conf") . }} + +# output +firstName=Peter +lastName=Parker +``` + ## 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: diff --git a/docs/examples/README.md b/docs/examples/README.md index 723040ca8..f936e1551 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -14,6 +14,5 @@ It simply deploys a single pod running Alpine Linux. The `nginx` chart shows how to compose several resources into one chart, and it illustrates more complex template usage. -It deploys a `deployment` (which creates a `replica set`), a `config -map`, and a `service`. The replica set starts an nginx pod. The config +It deploys a `Deployment` (which creates a `ReplicaSet`), a `ConfigMap`, and a `Service`. The replica set starts an nginx pod. The config map stores the files that the nginx server can serve. diff --git a/docs/helm/helm_list.md b/docs/helm/helm_list.md index 1d5bf7ea2..99872a413 100755 --- a/docs/helm/helm_list.md +++ b/docs/helm/helm_list.md @@ -49,6 +49,7 @@ helm list [flags] [FILTER] -m, --max int maximum number of releases to fetch (default 256) --namespace string show releases within a specific namespace -o, --offset string next release name in the list, used to offset from start value + --output string output the specified format (json or yaml) --pending show pending releases -r, --reverse reverse the sort order -q, --short output short (quiet) listing format @@ -73,4 +74,4 @@ helm list [flags] [FILTER] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 8-Mar-2018 +###### Auto generated by spf13/cobra on 17-Apr-2018 diff --git a/docs/rbac.md b/docs/rbac.md index 2a3dfe7a0..36d06e62a 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -71,7 +71,7 @@ metadata: name: tiller-manager namespace: tiller-world rules: -- apiGroups: ["", "extensions", "apps"] +- apiGroups: ["", "batch", "extensions", "apps"] resources: ["*"] verbs: ["*"] ``` diff --git a/docs/related.md b/docs/related.md index 997e3f01d..bf55df549 100644 --- a/docs/related.md +++ b/docs/related.md @@ -16,6 +16,7 @@ or [pull request](https://github.com/kubernetes/helm/pulls). - [GitLab, Consumer Driven Contracts, Helm and Kubernetes](https://medium.com/@enxebre/gitlab-consumer-driven-contracts-helm-and-kubernetes-b7235a60a1cb#.xwp1y4tgi) - [Writing a Helm Chart](https://www.influxdata.com/packaged-kubernetes-deployments-writing-helm-chart/) - [Creating a Helm Plugin in 3 Steps](http://technosophos.com/2017/03/21/creating-a-helm-plugin.html) +- [Awesome Helm](https://github.com/cdwv/awesome-helm) - List of awesome Helm resources ## Video, Audio, and Podcast diff --git a/docs/release_checklist.md b/docs/release_checklist.md index d678e7748..5187b720a 100644 --- a/docs/release_checklist.md +++ b/docs/release_checklist.md @@ -2,9 +2,36 @@ **IMPORTANT**: If your experience deviates from this document, please document the changes to keep it up-to-date. +## Release Meetings +As part of the release process, two of the weekly developer calls will be co-opted +as "release meetings." + +### Start of the Release Cycle +The first developer call after a release will be used as the release meeting to +start the next release cycle. During this meeting, the following items must be +identified: + +- Release date +- Goals/Objectives for this release +- The release manager (basically whoever is going to cut the release) +- Any other important details for the community + +All of this information should be added to the GitHub milestone for the given +release. This should give the community and maintainers a clear set of guidelines +to follow when choosing whether or not to add issues and PRs to a given release. + +### End (almost) of the Release Cycle +The developer call closest to two weeks before the scheduled release date will +be used to review any remaining PRs that should be pulled into the release. This +is the place to debate whether or not we should wait before cutting a release and +any other concerns. At the end of this meeting, if the release date has not been +pushed out, the first RC should be cut. Subsequent developer calls in between this +meeting and the release date should have some time set aside to see if any bugs +were found. Once the release date is reached, the final release can be cut + ## A Maintainer's Guide to Releasing Helm -So you're in charge of a new release for helm? Cool. Here's what to do... +So you're in charge of a new release for Helm? Cool. Here's what to do... ![TODO: Nothing](images/nothing.png) diff --git a/docs/using_helm.md b/docs/using_helm.md index 0d15637d4..5490abbe1 100755 --- a/docs/using_helm.md +++ b/docs/using_helm.md @@ -43,7 +43,7 @@ carefully curated and maintained charts. This chart repository is named You can see which charts are available by running `helm search`: -``` +```console $ helm search NAME VERSION DESCRIPTION stable/drupal 0.3.2 One of the most versatile open source content m... @@ -56,7 +56,7 @@ stable/mysql 0.1.0 Chart for MySQL With no filter, `helm search` shows you all of the available charts. You can narrow down your results by searching with a filter: -``` +```console $ helm search mysql NAME VERSION DESCRIPTION stable/mysql 0.1.0 Chart for MySQL @@ -69,7 +69,7 @@ Why is `mariadb` in the list? Because its package description relates it to MySQL. We can use `helm inspect chart` to see this: -``` +```console $ helm inspect stable/mariadb Fetched stable/mariadb to mariadb-0.5.1.tgz description: Chart for MariaDB @@ -91,7 +91,7 @@ package you want to install, you can use `helm install` to install it. To install a new package, use the `helm install` command. At its simplest, it takes only one argument: The name of the chart. -``` +```console $ helm install stable/mariadb Fetched stable/mariadb-0.3.0 to /Users/mattbutcher/Code/Go/src/k8s.io/helm/mariadb-0.3.0.tgz happy-panda @@ -139,7 +139,7 @@ may take a long time to install into the cluster. To keep track of a release's state, or to re-read configuration information, you can use `helm status`: -``` +```console $ helm status happy-panda Last Deployed: Wed Sep 28 12:32:28 2016 Namespace: default @@ -392,14 +392,14 @@ is not a full list of cli flags. To see a description of all flags, just run When it is time to uninstall or delete a release from the cluster, use the `helm delete` command: -``` +```console $ helm delete happy-panda ``` This will remove the release from the cluster. You can see all of your currently deployed releases with the `helm list` command: -``` +```console $ helm list NAME VERSION UPDATED STATUS CHART inky-cat 1 Wed Sep 28 12:59:46 2016 DEPLOYED alpine-0.1.0 diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 30c6310b2..fa6fb2783 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -129,8 +129,8 @@ kind: Ingress metadata: name: {{ $fullName }} labels: - app: {{ template ".name" . }} - chart: {{ template ".chart" . }} + app: {{ include ".name" . }} + chart: {{ include ".chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} {{- with .Values.ingress.annotations }} @@ -164,22 +164,22 @@ spec: const defaultDeployment = `apiVersion: apps/v1beta2 kind: Deployment metadata: - name: {{ template ".fullname" . }} + name: {{ include ".fullname" . }} labels: - app: {{ template ".name" . }} - chart: {{ template ".chart" . }} + app: {{ include ".name" . }} + chart: {{ include ".chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: - app: {{ template ".name" . }} + app: {{ include ".name" . }} release: {{ .Release.Name }} template: metadata: labels: - app: {{ template ".name" . }} + app: {{ include ".name" . }} release: {{ .Release.Name }} spec: containers: @@ -217,10 +217,10 @@ spec: const defaultService = `apiVersion: v1 kind: Service metadata: - name: {{ template ".fullname" . }} + name: {{ include ".fullname" . }} labels: - app: {{ template ".name" . }} - chart: {{ template ".chart" . }} + app: {{ include ".name" . }} + chart: {{ include ".chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} spec: @@ -231,7 +231,7 @@ spec: protocol: TCP name: http selector: - app: {{ template ".name" . }} + app: {{ include ".name" . }} release: {{ .Release.Name }} ` @@ -241,16 +241,16 @@ const defaultNotes = `1. Get the application URL by running these commands: http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} {{- 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 {{ include ".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 {{ include ".fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include ".fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo http://$SERVICE_IP:{{ .Values.service.port }} {{- 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={{ include ".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:80 {{- end }} diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index a3031f19a..fa4863de8 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -16,11 +16,21 @@ limitations under the License. package getter import ( + "io" "net/http" + "net/http/httptest" + "os" "path/filepath" + "strconv" "testing" ) +type TestFileHandler struct{} + +func (h *TestFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + HandleClient(w, r) +} + func TestHTTPGetter(t *testing.T) { g, err := newHTTPGetter("http://example.com", "", "", "") if err != nil { @@ -56,3 +66,42 @@ func TestHTTPGetter(t *testing.T) { t.Fatal("Expected newHTTPGetter to return a non-default HTTP client") } } + +func HandleClient(writer http.ResponseWriter, request *http.Request) { + f, _ := os.Open("testdata/sssd-0.1.0.tgz") + defer f.Close() + + b := make([]byte, 512) + f.Read(b) + //Get the file size + FileStat, _ := f.Stat() + FileSize := strconv.FormatInt(FileStat.Size(), 10) + + //Simulating improper header values from bitbucket + writer.Header().Set("Content-Type", "application/x-tar") + writer.Header().Set("Content-Encoding", "gzip") + writer.Header().Set("Content-Length", FileSize) + + f.Seek(0, 0) + io.Copy(writer, f) + return +} + +func TestHTTPGetterTarDownload(t *testing.T) { + h := &TestFileHandler{} + server := httptest.NewServer(h) + defer server.Close() + + g, err := newHTTPGetter(server.URL, "", "", "") + if err != nil { + t.Fatal(err) + } + + data, _ := g.Get(server.URL) + mimeType := http.DetectContentType(data.Bytes()) + + expectedMimeType := "application/x-gzip" + if mimeType != expectedMimeType { + t.Fatalf("Expected response with MIME type %s, but got %s", expectedMimeType, mimeType) + } +} diff --git a/pkg/getter/testdata/sssd-0.1.0.tgz b/pkg/getter/testdata/sssd-0.1.0.tgz new file mode 100644 index 000000000..2f34fece6 Binary files /dev/null and b/pkg/getter/testdata/sssd-0.1.0.tgz differ diff --git a/pkg/helm/fake.go b/pkg/helm/fake.go index 0a9e77c44..68d39a6ab 100644 --- a/pkg/helm/fake.go +++ b/pkg/helm/fake.go @@ -49,9 +49,28 @@ var _ Interface = (*FakeClient)(nil) // ListReleases lists the current releases func (c *FakeClient) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) { + reqOpts := c.Opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.listReq + rels := c.Rels + count := int64(len(c.Rels)) + var next string + limit := req.GetLimit() + // TODO: Handle all other options. + if limit != 0 && limit < count { + rels = rels[:limit] + count = limit + next = c.Rels[limit].GetName() + } + resp := &rls.ListReleasesResponse{ - Count: int64(len(c.Rels)), - Releases: c.Rels, + Count: count, + Releases: rels, + } + if next != "" { + resp.Next = next } return resp, nil } diff --git a/pkg/kube/namespace.go b/pkg/kube/namespace.go index 9d2793d87..6547e4abc 100644 --- a/pkg/kube/namespace.go +++ b/pkg/kube/namespace.go @@ -40,7 +40,16 @@ func getNamespace(client internalclientset.Interface, namespace string) (*core.N func ensureNamespace(client internalclientset.Interface, namespace string) error { _, err := getNamespace(client, namespace) if err != nil && errors.IsNotFound(err) { - return createNamespace(client, namespace) + err = createNamespace(client, namespace) + + // If multiple commands which run `ensureNamespace` are run in + // parallel, then protect against the race condition in which + // the namespace did not exist when `getNamespace` was executed, + // but did exist when `createNamespace` was executed. If that + // happens, we can just proceed as normal. + if errors.IsAlreadyExists(err) { + return nil + } } return err } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 4b39e0bb2..d308cef1b 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -163,7 +163,7 @@ func (s *Storage) History(name string) ([]*rspb.Release, error) { return s.Driver.Query(map[string]string{"NAME": name, "OWNER": "TILLER"}) } -// removeLeastRecent removes items from history until the lengh number of releases +// removeLeastRecent removes items from history until the length number of releases // does not exceed max. // // We allow max to be set explicitly so that calling functions can "make space" diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 90670a4dd..dae949d8e 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -325,6 +325,11 @@ func inMap(k rune, m map[rune]bool) bool { func typedVal(v []rune, st bool) interface{} { val := string(v) + + if st { + return val + } + if strings.EqualFold(val, "true") { return true } @@ -337,8 +342,8 @@ func typedVal(v []rune, st bool) interface{} { return nil } - // If this value does not start with zero, and not returnString, try parsing it to an int - if !st && len(val) != 0 && val[0] != '0' { + // If this value does not start with zero, try parsing it to an int + if len(val) != 0 && val[0] != '0' { if iv, err := strconv.ParseInt(val, 10, 64); err == nil { return iv } diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index c897cf0a7..22f0e753a 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -75,6 +75,16 @@ func TestParseSet(t *testing.T) { expect: map[string]interface{}{"long_int_string": "1234567890"}, err: false, }, + { + str: "boolean=true", + expect: map[string]interface{}{"boolean": "true"}, + err: false, + }, + { + str: "is_null=null", + expect: map[string]interface{}{"is_null": "null"}, + err: false, + }, } tests := []struct { str string @@ -117,6 +127,15 @@ func TestParseSet(t *testing.T) { str: "long_int=1234567890", expect: map[string]interface{}{"long_int": 1234567890}, }, + { + str: "boolean=true", + expect: map[string]interface{}{"boolean": true}, + }, + { + str: "is_null=null", + expect: map[string]interface{}{"is_null": nil}, + err: false, + }, { str: "name1,name2=", err: true, @@ -336,12 +355,13 @@ func TestParseInto(t *testing.T) { "inner2": "value2", }, } - input := "outer.inner1=value1,outer.inner3=value3" + input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4" expect := map[string]interface{}{ "outer": map[string]interface{}{ "inner1": "value1", "inner2": "value2", "inner3": "value3", + "inner4": 4, }, } @@ -362,6 +382,39 @@ func TestParseInto(t *testing.T) { t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) } } +func TestParseIntoString(t *testing.T) { + got := map[string]interface{}{ + "outer": map[string]interface{}{ + "inner1": "overwrite", + "inner2": "value2", + }, + } + input := "outer.inner1=1,outer.inner3=3" + expect := map[string]interface{}{ + "outer": map[string]interface{}{ + "inner1": "1", + "inner2": "value2", + "inner3": "3", + }, + } + + if err := ParseIntoString(input, got); err != nil { + t.Fatal(err) + } + + y1, err := yaml.Marshal(expect) + if err != nil { + t.Fatal(err) + } + y2, err := yaml.Marshal(got) + if err != nil { + t.Fatalf("Error serializing parsed value: %s", err) + } + + if string(y1) != string(y2) { + t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) + } +} func TestToYAML(t *testing.T) { // The TestParse does the hard part. We just verify that YAML formatting is diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 366fdf522..18518dfc1 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -98,8 +98,6 @@ type Engine interface { type KubeClient interface { // Create creates one or more resources. // - // namespace must contain a valid existing namespace. - // // reader must contain a YAML stream (one or more YAML documents separated // by "\n---\n"). Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 1a2b3c4da..a75b7fc86 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -190,7 +190,7 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { s.Log("name %s exists but is not in use, reusing name", start) return start, nil } else if reuse { - return "", errors.New("cannot re-use a name that is still in use") + return "", fmt.Errorf("a released named %s is in use, cannot re-use a name that is still in use", start) } return "", fmt.Errorf("a release named %s already exists.\nRun: helm ls --all %s; to check the status of the release\nOr run: helm del --purge %s; to delete it", start, start, start)