diff --git a/Makefile b/Makefile index 2dfbd2c3c..a8f778377 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ build: .PHONY: build-cross build-cross: LDFLAGS += -extldflags "-static" build-cross: - CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP) + CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) $(if $(TAGS),-tags '$(TAGS)',) -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP) .PHONY: dist dist: diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index a45179a48..becf412a1 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -73,7 +73,7 @@ func Upgrade(client kubernetes.Interface, opts *Options) error { if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil { return err } - // If the service does not exists that would mean we are upgrading from a Tiller version + // If the service does not exist that would mean we are upgrading from a Tiller version // that didn't deploy the service, so install it. _, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{}) if apierrors.IsNotFound(err) { @@ -176,7 +176,6 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) { return nil, err } } - automountServiceAccountToken := opts.ServiceAccount != "" d := &v1beta1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: opts.Namespace, @@ -190,8 +189,7 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) { Labels: labels, }, Spec: v1.PodSpec{ - ServiceAccountName: opts.ServiceAccount, - AutomountServiceAccountToken: &automountServiceAccountToken, + ServiceAccountName: opts.ServiceAccount, Containers: []v1.Container{ { Name: "tiller", diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index 80219505a..dbb7143e3 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -96,9 +96,6 @@ func TestDeploymentManifestForServiceAccount(t *testing.T) { if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount { t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got) } - if got := *d.Spec.Template.Spec.AutomountServiceAccountToken; got != (tt.serviceAccount != "") { - t.Errorf("%s: unexpected automountServiceAccountToken = %t for serviceAccount %q", tt.name, got, tt.serviceAccount) - } } } diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index ef24e7883..f4c97bde7 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -131,7 +131,7 @@ func manuallyProcessArgs(args []string) ([]string, []string) { switch a := args[i]; a { case "--debug": known = append(known, a) - case "--host", "--kube-context", "--home": + case "--host", "--kube-context", "--home", "--tiller-namespace": known = append(known, a, args[i+1]) i++ default: diff --git a/cmd/helm/search.go b/cmd/helm/search.go index 845bfd0be..ab284a898 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -47,6 +47,7 @@ type searchCmd struct { versions bool regexp bool version string + colWidth uint } func newSearchCmd(out io.Writer) *cobra.Command { @@ -66,6 +67,7 @@ func newSearchCmd(out io.Writer) *cobra.Command { f.BoolVarP(&sc.regexp, "regexp", "r", false, "use regular expressions for searching") f.BoolVarP(&sc.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line") f.StringVarP(&sc.version, "version", "v", "", "search using semantic versioning constraints") + f.UintVar(&sc.colWidth, "col-width", 60, "specifies the max column width of output") return cmd } @@ -93,7 +95,7 @@ func (s *searchCmd) run(args []string) error { return err } - fmt.Fprintln(s.out, s.formatSearchResults(data)) + fmt.Fprintln(s.out, s.formatSearchResults(data, s.colWidth)) return nil } @@ -126,12 +128,12 @@ func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, err return data, nil } -func (s *searchCmd) formatSearchResults(res []*search.Result) string { +func (s *searchCmd) formatSearchResults(res []*search.Result, colWidth uint) string { if len(res) == 0 { return "No results found" } table := uitable.New() - table.MaxColWidth = 50 + table.MaxColWidth = colWidth table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION") for _, r := range res { table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description) diff --git a/docs/chart_template_guide/accessing_files.md b/docs/chart_template_guide/accessing_files.md index 250fd9520..11747d4f0 100644 --- a/docs/chart_template_guide/accessing_files.md +++ b/docs/chart_template_guide/accessing_files.md @@ -119,9 +119,10 @@ You have multiple options with Globs: ```yaml -{{ range $path := .Files.Glob "**.yaml" }} -{{ $path }}: | -{{ .Files.Get $path }} +{{ $root := . }} +{{ range $path, $bytes := .Files.Glob "**.yaml" }} +{{ $path }}: |- +{{ $root.Files.Get $path }} {{ end }} ``` @@ -129,7 +130,7 @@ Or ```yaml {{ range $path, $bytes := .Files.Glob "foo/*" }} -{{ $path }}: '{{ b64enc $bytes }}' +{{ $path.base }}: '{{ $root.Files.Get $path | b64enc }}' {{ end }} ``` diff --git a/docs/helm/helm_search.md b/docs/helm/helm_search.md index f59814b9a..1ed04e880 100644 --- a/docs/helm/helm_search.md +++ b/docs/helm/helm_search.md @@ -19,6 +19,7 @@ helm search [keyword] ### Options ``` + --col-width uint specifies the max column width of output (default 60) -r, --regexp use regular expressions for searching -v, --version string search using semantic versioning constraints -l, --versions show the long listing, with each version of each chart on its own line @@ -38,4 +39,4 @@ helm search [keyword] ### 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 23-Apr-2018 diff --git a/docs/plugins.md b/docs/plugins.md index 82bcfe33b..3087d1b39 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -183,7 +183,7 @@ If a plugin specifies `useTunnel: true`, Helm will do the following (in order): 5. Close the tunnel The tunnel is removed as soon as the `command` returns. So, for example, a -command cannot background a process and assume that that process will be able +command cannot background a process and assume that process will be able to use the tunnel. ## A Note on Flag Parsing diff --git a/docs/securing_installation.md b/docs/securing_installation.md index 5c420242e..4083bf188 100644 --- a/docs/securing_installation.md +++ b/docs/securing_installation.md @@ -53,7 +53,7 @@ This situation may change in the future. While the community has several methods In the default installation the gRPC endpoint that Tiller offers is available inside the cluster (not external to the cluster) without authentication configuration applied. Without applying authentication, any process in the cluster can use the gRPC endpoint to perform operations inside the cluster. In a local or secured private cluster, this enables rapid usage and is normal. (When running outside the cluster, Helm authenticates through the Kubernetes API server to reach Tiller, leveraging existing Kubernetes authentication support.) -Shared and production clusters -- for the most part -- should use Helm 2.7.2 at a minimum and configure TLS for each Tiller gRPC endpoint to ensure that within the cluster usage of gRPC endpoints is only for the properly authenticated identity for that endpoint. Doing so enables any number of Tiller instances to be deployed in any number of namespaces and yet no unauthenticated usage of any gRPC endpoint is possible. Finally, usa Helm `init` with the `--tiller-tls-verify` option to install Tiller with TLS enabled and to verify remote certificates, and all other Helm commands should use the `--tls` option. +Shared and production clusters -- for the most part -- should use Helm 2.7.2 at a minimum and configure TLS for each Tiller gRPC endpoint to ensure that within the cluster usage of gRPC endpoints is only for the properly authenticated identity for that endpoint. Doing so enables any number of Tiller instances to be deployed in any number of namespaces and yet no unauthenticated usage of any gRPC endpoint is possible. Finally, use Helm `init` with the `--tiller-tls-verify` option to install Tiller with TLS enabled and to verify remote certificates, and all other Helm commands should use the `--tls` option. For more information about the proper steps to configure Tiller and use Helm properly with TLS configured, see [Using SSL between Helm and Tiller](tiller_ssl.md). @@ -95,10 +95,10 @@ If these steps are followed, an example `helm init` command might look something $ helm init \ --tiller-tls \ --tiller-tls-verify \ ---tiller-tls-ca-cert=ca.pem \ --tiller-tls-cert=cert.pem \ --tiller-tls-key=key.pem \ ---service-account=accountname +--tls-ca-cert=ca.pem \ +--service-account=accountname ``` This command will start Tiller with both strong authentication over gRPC, and a service account to which RBAC policies have been applied. diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 9f2a8cc1f..30c6310b2 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -143,14 +143,14 @@ spec: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - - {{ . }} + - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - - host: {{ . }} + - host: {{ . | quote }} http: paths: - path: {{ $ingressPath }} @@ -307,8 +307,9 @@ func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { } schart.Templates = updatedTemplates - schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "", schart.Metadata.Name))} - + if schart.Values != nil { + schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "", schart.Metadata.Name))} + } return SaveDir(schart, dest) } diff --git a/pkg/chartutil/files.go b/pkg/chartutil/files.go index ced8ce15a..a09bb8f43 100644 --- a/pkg/chartutil/files.go +++ b/pkg/chartutil/files.go @@ -175,7 +175,7 @@ func ToYaml(v interface{}) string { // Swallow errors inside of a template. return "" } - return strings.TrimSuffix(string(data), "\n") + return string(data) } // FromYaml converts a YAML document into a map[string]interface{}. @@ -211,7 +211,8 @@ func ToToml(v interface{}) string { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func ToJson(v interface{}) string { +// TODO: change the function signature in Helm 3 +func ToJson(v interface{}) string { // nolint data, err := json.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -226,7 +227,8 @@ func ToJson(v interface{}) string { // JSON documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func FromJson(str string) map[string]interface{} { +// TODO: change the function signature in Helm 3 +func FromJson(str string) map[string]interface{} { // nolint m := map[string]interface{}{} if err := json.Unmarshal([]byte(str), &m); err != nil { diff --git a/pkg/chartutil/files_test.go b/pkg/chartutil/files_test.go index 5cec35883..731c82e6f 100644 --- a/pkg/chartutil/files_test.go +++ b/pkg/chartutil/files_test.go @@ -72,10 +72,10 @@ func TestToConfig(t *testing.T) { f := NewFiles(getTestFiles()) out := f.Glob("**/captain.txt").AsConfig() - as.Equal("captain.txt: The Captain", out) + as.Equal("captain.txt: The Captain\n", out) out = f.Glob("ship/**").AsConfig() - as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt", out) + as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt\n", out) } func TestToSecret(t *testing.T) { @@ -84,7 +84,7 @@ func TestToSecret(t *testing.T) { f := NewFiles(getTestFiles()) out := f.Glob("ship/**").AsSecrets() - as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0", out) + as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0\n", out) } func TestLines(t *testing.T) { @@ -99,7 +99,7 @@ func TestLines(t *testing.T) { } func TestToYaml(t *testing.T) { - expect := "foo: bar" + expect := "foo: bar\n" v := struct { Foo string `json:"foo"` }{ diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index fe2f3ce92..59b9d4d75 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -85,7 +85,7 @@ type ChartDownloader struct { // Returns a string path to the location where the file was downloaded and a verification // (if provenance was verified), or an error if something bad happened. func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) { - u, r, g, err := c.ResolveChartVersionAndGetRepo(ref, version) + u, g, err := c.ResolveChartVersion(ref, version) if err != nil { return "", nil, err } @@ -104,7 +104,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven // If provenance is requested, verify it. ver := &provenance.Verification{} if c.Verify > VerifyNever { - body, err := r.Client.Get(u.String() + ".prov") + body, err := g.Get(u.String() + ".prov") if err != nil { if c.Verify == VerifyAlways { return destfile, ver, fmt.Errorf("Failed to fetch provenance %q", u.String()+".prov") @@ -144,28 +144,14 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven // * If version is empty, this will return the URL for the latest version // * If no version can be found, an error is returned func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, getter.Getter, error) { - u, r, _, err := c.ResolveChartVersionAndGetRepo(ref, version) - if r != nil { - return u, r.Client, err - } - return u, nil, err -} - -// ResolveChartVersionAndGetRepo is the same as the ResolveChartVersion method, but returns the chart repositoryy. -func (c *ChartDownloader) ResolveChartVersionAndGetRepo(ref, version string) (*url.URL, *repo.ChartRepository, *getter.HttpGetter, error) { u, err := url.Parse(ref) if err != nil { - return nil, nil, nil, fmt.Errorf("invalid chart URL format: %s", ref) + return nil, nil, fmt.Errorf("invalid chart URL format: %s", ref) } rf, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile()) if err != nil { - return u, nil, nil, err - } - - g, err := getter.NewHTTPGetter(ref, "", "", "") - if err != nil { - return u, nil, nil, err + return u, nil, err } if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 { @@ -180,23 +166,26 @@ func (c *ChartDownloader) ResolveChartVersionAndGetRepo(ref, version string) (*u // If there is no special config, return the default HTTP client and // swallow the error. if err == ErrNoOwnerRepo { - r := &repo.ChartRepository{} - r.Client = g - g.SetCredentials(c.getRepoCredentials(r)) - return u, r, g, err + getterConstructor, err := c.Getters.ByScheme(u.Scheme) + if err != nil { + return u, nil, err + } + getter, err := getterConstructor(ref, "", "", "") + return u, getter, err } - return u, nil, nil, err + return u, nil, err } r, err := repo.NewChartRepository(rc, c.Getters) + c.setCredentials(r) // If we get here, we don't need to go through the next phase of looking // up the URL. We have it already. So we just return. - return u, r, g, err + return u, r.Client, err } // See if it's of the form: repo/path_to_chart p := strings.SplitN(u.Path, "/", 2) if len(p) < 2 { - return u, nil, nil, fmt.Errorf("Non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u) + return u, nil, fmt.Errorf("Non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u) } repoName := p[0] @@ -204,56 +193,58 @@ func (c *ChartDownloader) ResolveChartVersionAndGetRepo(ref, version string) (*u rc, err := pickChartRepositoryConfigByName(repoName, rf.Repositories) if err != nil { - return u, nil, nil, err + return u, nil, err } r, err := repo.NewChartRepository(rc, c.Getters) if err != nil { - return u, nil, nil, err + return u, nil, err } - g.SetCredentials(c.getRepoCredentials(r)) + c.setCredentials(r) // Next, we need to load the index, and actually look up the chart. i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) if err != nil { - return u, r, g, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) + return u, r.Client, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) } cv, err := i.Get(chartName, version) if err != nil { - return u, r, g, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'). %s", chartName, version, r.Config.Name, err) + return u, r.Client, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'). %s", chartName, version, r.Config.Name, err) } if len(cv.URLs) == 0 { - return u, r, g, fmt.Errorf("chart %q has no downloadable URLs", ref) + return u, r.Client, fmt.Errorf("chart %q has no downloadable URLs", ref) } // TODO: Seems that picking first URL is not fully correct u, err = url.Parse(cv.URLs[0]) if err != nil { - return u, r, g, fmt.Errorf("invalid chart URL format: %s", ref) + return u, r.Client, fmt.Errorf("invalid chart URL format: %s", ref) } // If the URL is relative (no scheme), prepend the chart repo's base URL if !u.IsAbs() { repoURL, err := url.Parse(rc.URL) if err != nil { - return repoURL, r, nil, err + 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() - g, err := getter.NewHTTPGetter(rc.URL, "", "", "") - if err != nil { - return repoURL, r, nil, err - } - g.SetCredentials(c.getRepoCredentials(r)) - return u, r, g, err + return u, r.Client, err } - return u, r, g, nil + return u, r.Client, nil +} + +// If HttpGetter is used, this method sets the configured repository credentials on the HttpGetter. +func (c *ChartDownloader) setCredentials(r *repo.ChartRepository) { + if t, ok := r.Client.(*getter.HttpGetter); ok { + t.SetCredentials(c.getRepoCredentials(r)) + } } // If this ChartDownloader is not configured to use credentials, and the chart repository sent as an argument is, diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go index 76f45fc7a..185d289bb 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -77,7 +77,7 @@ func (r *Rules) Len() int { return len(r.patterns) } -// Ignore evalutes the file at the given path, and returns true if it should be ignored. +// Ignore evaluates the file at the given path, and returns true if it should be ignored. // // Ignore evaluates path against the rules in order. Evaluation stops when a match // is found. Matching a negative rule will stop evaluation. diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 34d979d47..e4c6b6a9f 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -178,7 +178,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { // versions per cluster, but this certainly won't hurt anything, so let's be safe. gvk := info.ResourceMapping().GroupVersionKind vk := gvk.Version + "/" + gvk.Kind - objs[vk] = append(objs[vk], info.Object) + objs[vk] = append(objs[vk], info.AsInternal()) //Get the relation pods objPods, err = c.getSelectRelationPod(info, objPods) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index bf03a68bb..438f66d7c 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -119,12 +119,9 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error { parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" indexURL = parsedURL.String() - g, err := getter.NewHTTPGetter(indexURL, r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile) - if err != nil { - return err - } - g.SetCredentials(r.Config.Username, r.Config.Password) - resp, err := g.Get(indexURL) + + r.setCredentials() + resp, err := r.Client.Get(indexURL) if err != nil { return err } @@ -152,6 +149,13 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error { return ioutil.WriteFile(cp, index, 0644) } +// If HttpGetter is used, this method sets the configured repository credentials on the HttpGetter. +func (r *ChartRepository) setCredentials() { + if t, ok := r.Client.(*getter.HttpGetter); ok { + t.SetCredentials(r.Config.Username, r.Config.Password) + } +} + // Index generates an index for the chart repository and writes an index.yaml file. func (r *ChartRepository) Index() error { err := r.generateIndex() diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 8d20c3bc3..90670a4dd 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -36,7 +36,7 @@ func ToYAML(s string) (string, error) { return "", err } d, err := yaml.Marshal(m) - return strings.TrimSuffix(string(d), "\n"), err + return string(d), err } // Parse parses a set line. diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 482377c32..c897cf0a7 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -370,7 +370,7 @@ func TestToYAML(t *testing.T) { if err != nil { t.Fatal(err) } - expect := "name: value" + expect := "name: value\n" if o != expect { t.Errorf("Expected %q, got %q", expect, o) } diff --git a/rootfs/Dockerfile b/rootfs/Dockerfile index 53757cd8d..ca5ad2225 100644 --- a/rootfs/Dockerfile +++ b/rootfs/Dockerfile @@ -12,15 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM alpine:3.3 +FROM alpine:3.7 RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* ENV HOME /tmp -COPY tiller /tiller +COPY tiller /bin/tiller EXPOSE 44134 - -CMD ["/tiller"] +USER nobody +ENTRYPOINT ["/bin/tiller"] diff --git a/rootfs/Dockerfile.experimental b/rootfs/Dockerfile.experimental index 990bcde51..66a218477 100644 --- a/rootfs/Dockerfile.experimental +++ b/rootfs/Dockerfile.experimental @@ -12,15 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM alpine:3.3 +FROM alpine:3.7 RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* ENV HOME /tmp -COPY tiller /tiller +COPY tiller /bin/tiller EXPOSE 44134 - -CMD ["/tiller", "--experimental-release"] +USER nobody +ENTRYPOINT ["/bin/tiller", "--experimental-release"]