Merge branch 'master' into feat/storage-memory

pull/1007/head
fibonacci1729 9 years ago
commit ae2d6c5050

7
.gitignore vendored

@ -1,7 +1,8 @@
.DS_Store
.coverage/ .coverage/
.vimrc
_dist/
_proto/*.pb.go
bin/ bin/
rootfs/tiller rootfs/tiller
vendor/ vendor/
_proto/*.pb.go
.vimrc
.DS_Store

@ -162,6 +162,9 @@ message UpdateReleaseRequest {
hapi.chart.Config values = 3; hapi.chart.Config values = 3;
// dry_run, if true, will run through the release logic, but neither create // dry_run, if true, will run through the release logic, but neither create
bool dry_run = 4; bool dry_run = 4;
// DisableHooks causes the server to skip running any hooks for the upgrade.
bool disable_hooks = 5;
} }
// UpdateReleaseResponse is the response to an update request. // UpdateReleaseResponse is the response to an update request.

@ -84,12 +84,13 @@ func newRootCmd(out io.Writer) *cobra.Command {
cmd.AddCommand( cmd.AddCommand(
newCreateCmd(out), newCreateCmd(out),
newDeleteCmd(nil, out),
newGetCmd(nil, out), newGetCmd(nil, out),
newInitCmd(out),
newInspectCmd(nil, out),
newInstallCmd(nil, out),
newListCmd(nil, out), newListCmd(nil, out),
newStatusCmd(nil, out), newStatusCmd(nil, out),
newInstallCmd(nil, out),
newDeleteCmd(nil, out),
newInspectCmd(nil, out),
newUpgradeCmd(nil, out), newUpgradeCmd(nil, out),
) )
return cmd return cmd

@ -19,6 +19,7 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -32,58 +33,54 @@ Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.he
` `
var ( var (
tillerImg string
clientOnly bool
defaultRepository = "kubernetes-charts" defaultRepository = "kubernetes-charts"
defaultRepositoryURL = "http://storage.googleapis.com/kubernetes-charts" defaultRepositoryURL = "http://storage.googleapis.com/kubernetes-charts"
) )
func init() { type initCmd struct {
f := initCmd.Flags() image string
f.StringVarP(&tillerImg, "tiller-image", "i", "", "override tiller image") clientOnly bool
f.BoolVarP(&clientOnly, "client-only", "c", false, "If set does not install tiller") out io.Writer
RootCommand.AddCommand(initCmd)
} }
var initCmd = &cobra.Command{ func newInitCmd(out io.Writer) *cobra.Command {
Use: "init", i := &initCmd{
Short: "initialize Helm on both client and server", out: out,
Long: initDesc, }
RunE: runInit, cmd := &cobra.Command{
Use: "init",
Short: "initialize Helm on both client and server",
Long: initDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return errors.New("This command does not accept arguments")
}
return i.run()
},
}
cmd.Flags().StringVarP(&i.image, "tiller-image", "i", "", "override tiller image")
cmd.Flags().BoolVarP(&i.clientOnly, "client-only", "c", false, "If set does not install tiller")
return cmd
} }
// runInit initializes local config and installs tiller to Kubernetes Cluster // runInit initializes local config and installs tiller to Kubernetes Cluster
func runInit(cmd *cobra.Command, args []string) error { func (i *initCmd) run() error {
if len(args) != 0 {
return errors.New("This command does not accept arguments. \n")
}
if err := ensureHome(); err != nil { if err := ensureHome(); err != nil {
return err return err
} }
if !clientOnly { if !i.clientOnly {
if err := installTiller(); err != nil { if err := client.Install(tillerNamespace, i.image, flagDebug); err != nil {
return err return fmt.Errorf("error installing: %s", err)
} }
fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been installed into your Kubernetes Cluster.")
} else { } else {
fmt.Println("Not installing tiller due to 'client-only' flag having been set") fmt.Fprintln(i.out, "Not installing tiller due to 'client-only' flag having been set")
} }
fmt.Fprintln(i.out, "Happy Helming!")
fmt.Println("Happy Helming!")
return nil
}
func installTiller() error {
if err := client.Install(tillerNamespace, tillerImg, flagDebug); err != nil {
return fmt.Errorf("error installing: %s", err)
}
fmt.Println("\nTiller (the helm server side component) has been installed into your Kubernetes Cluster.")
return nil return nil
} }
// requireHome checks to see if $HELM_HOME exists, and returns an error if it does not.
func requireHome() error { func requireHome() error {
dirs := []string{homePath(), repositoryDirectory(), cacheDirectory(), localRepoDirectory()} dirs := []string{homePath(), repositoryDirectory(), cacheDirectory(), localRepoDirectory()}
for _, d := range dirs { for _, d := range dirs {

@ -130,11 +130,10 @@ func (i *inspectCmd) run() error {
fmt.Fprintln(i.out, string(cf)) fmt.Fprintln(i.out, string(cf))
} }
if i.output == both { if (i.output == valuesOnly || i.output == both) && chrt.Values != nil {
fmt.Fprintln(i.out, "---") if i.output == both {
} fmt.Fprintln(i.out, "---")
}
if i.output == valuesOnly || i.output == both {
fmt.Fprintln(i.out, chrt.Values.Raw) fmt.Fprintln(i.out, chrt.Values.Raw)
} }

@ -61,4 +61,17 @@ func TestInspect(t *testing.T) {
t.Errorf("Expected\n%q\nGot\n%q\n", expect[i], got) t.Errorf("Expected\n%q\nGot\n%q\n", expect[i], got)
} }
} }
// Regression tests for missing values. See issue #1024.
b.Reset()
insp = &inspectCmd{
chartpath: "testdata/testcharts/novals",
output: "values",
out: b,
}
insp.run()
if b.Len() != 0 {
t.Errorf("expected empty values buffer, got %q", b.String())
}
} }

@ -17,6 +17,7 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -24,6 +25,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -59,10 +63,11 @@ type installCmd struct {
chartPath string chartPath string
dryRun bool dryRun bool
disableHooks bool disableHooks bool
reuseName bool replace bool
out io.Writer out io.Writer
client helm.Interface client helm.Interface
values *values values *values
nameTemplate string
} }
func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
@ -98,8 +103,9 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.namespace, "namespace", "default", "the namespace to install the release into") f.StringVar(&inst.namespace, "namespace", "default", "the namespace to install the release into")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&inst.reuseName, "reuse-name", false, "force Tiller to re-use the given name, even if that name is already used. This is unsafe in production") f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
f.Var(inst.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2") f.Var(inst.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2")
f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
return cmd return cmd
} }
@ -113,13 +119,23 @@ func (i *installCmd) run() error {
return err return err
} }
// If template is specified, try to run the template.
if i.nameTemplate != "" {
i.name, err = generateName(i.nameTemplate)
if err != nil {
return err
}
// Print the final name so the user knows what the final name of the release is.
fmt.Printf("final name: %s\n", i.name)
}
res, err := i.client.InstallRelease( res, err := i.client.InstallRelease(
i.chartPath, i.chartPath,
i.namespace, i.namespace,
helm.ValueOverrides(rawVals), helm.ValueOverrides(rawVals),
helm.ReleaseName(i.name), helm.ReleaseName(i.name),
helm.InstallDryRun(i.dryRun), helm.InstallDryRun(i.dryRun),
helm.InstallReuseName(i.reuseName), helm.InstallReuseName(i.replace),
helm.InstallDisableHooks(i.disableHooks)) helm.InstallDisableHooks(i.disableHooks))
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
@ -249,3 +265,16 @@ func locateChartPath(name string) (string, error) {
return name, fmt.Errorf("file %q not found", origname) return name, fmt.Errorf("file %q not found", origname)
} }
func generateName(nameTemplate string) (string, error) {
t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate)
if err != nil {
return "", err
}
var b bytes.Buffer
err = t.Execute(&b, nil)
if err != nil {
return "", err
}
return b.String(), nil
}

@ -19,6 +19,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"regexp"
"strings" "strings"
"testing" "testing"
@ -59,12 +60,20 @@ func TestInstall(t *testing.T) {
}, },
// Install, re-use name // Install, re-use name
{ {
name: "install and reuse name", name: "install and replace release",
args: []string{"testdata/testcharts/alpine"}, args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name aeneas --reuse-name", " "), flags: strings.Split("--name aeneas --replace", " "),
expected: "aeneas", expected: "aeneas",
resp: releaseMock(&releaseOptions{name: "aeneas"}), resp: releaseMock(&releaseOptions{name: "aeneas"}),
}, },
// Install, using the name-template
{
name: "install with name-template",
args: []string{"testdata/testcharts/alpine"},
flags: []string{"--name-template", "{{upper \"foobar\"}}"},
expected: "FOOBAR",
resp: releaseMock(&releaseOptions{name: "FOOBAR"}),
},
} }
runReleaseCases(t, tests, func(c *fakeReleaseClient, out io.Writer) *cobra.Command { runReleaseCases(t, tests, func(c *fakeReleaseClient, out io.Writer) *cobra.Command {
@ -113,3 +122,78 @@ sailor: sinbad
t.Errorf("Expected String() to be \n%s\nGot\n%s\n", y, out) t.Errorf("Expected String() to be \n%s\nGot\n%s\n", y, out)
} }
} }
type nameTemplateTestCase struct {
tpl string
expected string
expectedErrorStr string
}
func TestNameTemplate(t *testing.T) {
testCases := []nameTemplateTestCase{
// Just a straight up nop please
{
tpl: "foobar",
expected: "foobar",
expectedErrorStr: "",
},
// Random numbers at the end for fun & profit
{
tpl: "foobar-{{randNumeric 6}}",
expected: "foobar-[0-9]{6}$",
expectedErrorStr: "",
},
// Random numbers in the middle for fun & profit
{
tpl: "foobar-{{randNumeric 4}}-baz",
expected: "foobar-[0-9]{4}-baz$",
expectedErrorStr: "",
},
// No such function
{
tpl: "foobar-{{randInt}}",
expected: "",
expectedErrorStr: "function \"randInt\" not defined",
},
// Invalid template
{
tpl: "foobar-{{",
expected: "",
expectedErrorStr: "unexpected unclosed action",
},
}
for _, tc := range testCases {
n, err := generateName(tc.tpl)
if err != nil {
if tc.expectedErrorStr == "" {
t.Errorf("Was not expecting error, but got: %v", err)
continue
}
re, compErr := regexp.Compile(tc.expectedErrorStr)
if compErr != nil {
t.Errorf("Expected error string failed to compile: %v", compErr)
continue
}
if !re.MatchString(err.Error()) {
t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err)
continue
}
}
if err == nil && tc.expectedErrorStr != "" {
t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr)
}
if tc.expected != "" {
re, err := regexp.Compile(tc.expected)
if err != nil {
t.Errorf("Expected string failed to compile: %v", err)
continue
}
if !re.MatchString(n) {
t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
}
}
}
}

@ -47,7 +47,10 @@ var lintCommand = &cobra.Command{
RunE: lintCmd, RunE: lintCmd,
} }
var flagStrict bool
func init() { func init() {
lintCommand.Flags().BoolVarP(&flagStrict, "strict", "", false, "fail on lint warnings")
RootCommand.AddCommand(lintCommand) RootCommand.AddCommand(lintCommand)
} }
@ -59,6 +62,13 @@ func lintCmd(cmd *cobra.Command, args []string) error {
paths = args paths = args
} }
var lowestTolerance int
if flagStrict {
lowestTolerance = support.WarningSev
} else {
lowestTolerance = support.ErrorSev
}
var total int var total int
var failures int var failures int
for _, path := range paths { for _, path := range paths {
@ -77,7 +87,7 @@ func lintCmd(cmd *cobra.Command, args []string) error {
} }
total = total + 1 total = total + 1
if linter.HighestSeverity >= support.ErrorSev { if linter.HighestSeverity >= lowestTolerance {
failures = failures + 1 failures = failures + 1
} }
} }

@ -17,17 +17,19 @@ limitations under the License.
package main package main
import ( import (
"os"
"path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )
var serveDesc = `This command starts a local chart repository server that serves the charts saved in your $HELM_HOME/local/ directory.` var serveDesc = `This command starts a local chart repository server that serves charts from a local directory.`
var repoPath string
//TODO: add repoPath flag to be passed in in case you want
// to serve charts from a different local dir
func init() { func init() {
serveCmd.Flags().StringVar(&repoPath, "repo-path", localRepoDirectory(), "The local directory path from which to serve charts.")
RootCommand.AddCommand(serveCmd) RootCommand.AddCommand(serveCmd)
} }
@ -35,9 +37,19 @@ var serveCmd = &cobra.Command{
Use: "serve", Use: "serve",
Short: "start a local http web server", Short: "start a local http web server",
Long: serveDesc, Long: serveDesc,
Run: serve, RunE: serve,
} }
func serve(cmd *cobra.Command, args []string) { func serve(cmd *cobra.Command, args []string) error {
repo.StartLocalRepo(localRepoDirectory())
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return err
}
if _, err := os.Stat(repoPath); os.IsNotExist(err) {
return err
}
repo.StartLocalRepo(repoPath)
return nil
} }

@ -0,0 +1,6 @@
description: Deploy a basic Alpine Linux pod
home: https://k8s.io/helm
name: novals
sources:
- https://github.com/kubernetes/helm
version: 0.2.0

@ -0,0 +1,13 @@
#Alpine: A simple Helm chart
Run a single pod of Alpine Linux.
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.yaml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install docs/examples/alpine`.

@ -0,0 +1,26 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-{{.Values.Name}}"
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
# is responsible for.
heritage: {{.Release.Service | quote }}
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{.Release.Name | quote }}
# This makes it easy to audit chart usage.
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:
"helm.sh/created": {{.Release.Time.Seconds | quote }}
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.
# {{default "Never" .restartPolicy}} is a slightly optimized version of the
# more conventional syntax: {{.restartPolicy | default "Never"}}
restartPolicy: {{default "Never" .Values.restartPolicy}}
containers:
- name: waiter
image: "alpine:3.3"
command: ["/bin/sleep","9000"]

@ -34,12 +34,13 @@ argument can be a relative path to a packaged or unpackaged chart.
` `
type upgradeCmd struct { type upgradeCmd struct {
release string release string
chart string chart string
out io.Writer out io.Writer
client helm.Interface client helm.Interface
dryRun bool dryRun bool
valuesFile string disableHooks bool
valuesFile string
} }
func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
@ -70,6 +71,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file") f.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks")
return cmd return cmd
} }
@ -88,12 +90,13 @@ func (u *upgradeCmd) run() error {
} }
} }
_, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun)) _, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun), helm.UpgradeDisableHooks(u.disableHooks))
if err != nil { if err != nil {
return prettyError(err) return prettyError(err)
} }
fmt.Fprintf(u.out, "It's not you. It's me\nYour upgrade looks valid but this command is still under active development.\nHang tight.\n") success := u.release + " has been upgraded. Happy Helming!\n"
fmt.Fprintf(u.out, success)
return nil return nil

@ -52,18 +52,22 @@ func TestUpgradeCmd(t *testing.T) {
Description: "A Helm chart for Kubernetes", Description: "A Helm chart for Kubernetes",
Version: "0.1.2", Version: "0.1.2",
} }
chartPath, err = chartutil.Create(cfile, tmpChart) chartPath, err = chartutil.Create(cfile, tmpChart)
if err != nil { if err != nil {
t.Errorf("Error creating chart: %v", err) t.Errorf("Error creating chart: %v", err)
} }
ch, _ = chartutil.Load(chartPath) ch, err = chartutil.Load(chartPath)
if err != nil {
t.Errorf("Error loading updated chart: %v", err)
}
tests := []releaseCase{ tests := []releaseCase{
{ {
name: "upgrade a release", name: "upgrade a release",
args: []string{"funny-bunny", chartPath}, args: []string{"funny-bunny", chartPath},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}), resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}),
expected: "It's not you. It's me\nYour upgrade looks valid but this command is still under active development.\nHang tight.\n", expected: "funny-bunny has been upgraded. Happy Helming!\n",
}, },
} }

@ -117,6 +117,15 @@ type KubeClient interface {
// For all other kinds, it means the kind was created or modified without // For all other kinds, it means the kind was created or modified without
// error. // error.
WatchUntilReady(namespace string, reader io.Reader) error WatchUntilReady(namespace string, reader io.Reader) error
// Update updates one or more resources or creates the resource
// if it doesn't exist
//
// namespace must contain a valid existing namespace
//
// reader must contain a YAML stream (one or more YAML documents separated
// by "\n---\n").
Update(namespace string, originalReader, modifiedReader io.Reader) error
} }
// PrintingKubeClient implements KubeClient, but simply prints the reader to // PrintingKubeClient implements KubeClient, but simply prints the reader to
@ -145,6 +154,12 @@ func (p *PrintingKubeClient) WatchUntilReady(ns string, r io.Reader) error {
return err return err
} }
// Update implements KubeClient Update.
func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader) error {
_, err := io.Copy(p.Out, modifiedReader)
return err
}
// Environment provides the context for executing a client request. // Environment provides the context for executing a client request.
// //
// All services in a context are concurrency safe. // All services in a context are concurrency safe.

@ -87,6 +87,9 @@ func (k *mockKubeClient) Create(ns string, r io.Reader) error {
func (k *mockKubeClient) Delete(ns string, r io.Reader) error { func (k *mockKubeClient) Delete(ns string, r io.Reader) error {
return nil return nil
} }
func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Reader) error {
return nil
}
func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader) error { func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader) error {
return nil return nil
} }

@ -24,7 +24,6 @@ import (
"regexp" "regexp"
"sort" "sort"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
ctx "golang.org/x/net/context" ctx "golang.org/x/net/context"
@ -171,65 +170,107 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas
} }
func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
rel, err := s.prepareUpdate(req) currentRelease, updatedRelease, err := s.prepareUpdate(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: perform update res, err := s.performUpdate(currentRelease, updatedRelease, req)
if err != nil {
return nil, err
}
if err := s.env.Releases.Update(updatedRelease); err != nil {
return nil, err
}
return res, nil
}
func (s *releaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
res := &services.UpdateReleaseResponse{Release: updatedRelease}
if req.DryRun {
log.Printf("Dry run for %s", updatedRelease.Name)
return res, nil
}
// pre-ugrade hooks
if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, preUpgrade); err != nil {
return res, err
}
}
kubeCli := s.env.KubeClient
original := bytes.NewBufferString(originalRelease.Manifest)
modified := bytes.NewBufferString(updatedRelease.Manifest)
if err := kubeCli.Update(updatedRelease.Namespace, original, modified); err != nil {
return nil, fmt.Errorf("Update of %s failed: %s", updatedRelease.Name, err)
}
// post-upgrade hooks
if !req.DisableHooks {
if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, postUpgrade); err != nil {
return res, err
}
}
return &services.UpdateReleaseResponse{Release: rel}, nil updatedRelease.Info.Status.Code = release.Status_DEPLOYED
return res, nil
} }
// prepareUpdate builds a release for an update operation. // prepareUpdate builds an updated release for an update operation.
func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, error) { func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) {
if req.Name == "" { if req.Name == "" {
return nil, errMissingRelease return nil, nil, errMissingRelease
} }
if req.Chart == nil { if req.Chart == nil {
return nil, errMissingChart return nil, nil, errMissingChart
} }
// finds the non-deleted release with the given name // finds the non-deleted release with the given name
rel, err := s.env.Releases.Get(req.Name) currentRelease, err := s.env.Releases.Get(req.Name)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
//validate chart name is same as previous release ts := timeconv.Now()
givenChart := req.Chart.Metadata.Name options := chartutil.ReleaseOptions{
releasedChart := rel.Chart.Metadata.Name Name: req.Name,
if givenChart != releasedChart { Time: ts,
return nil, fmt.Errorf("Given chart, %s, does not match chart originally released, %s", givenChart, releasedChart) Namespace: currentRelease.Namespace,
} }
// validate new chart version is higher than old valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options)
givenChartVersion := req.Chart.Metadata.Version
releasedChartVersion := rel.Chart.Metadata.Version
c, err := semver.NewConstraint("> " + releasedChartVersion)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
v, err := semver.NewVersion(givenChartVersion) hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender)
if err != nil { if err != nil {
return nil, err return nil, nil, err
}
if a := c.Check(v); !a {
return nil, fmt.Errorf("Given chart (%s-%v) must be a higher version than released chart (%s-%v)", givenChart, givenChartVersion, releasedChart, releasedChartVersion)
} }
// Store an updated release. // Store an updated release.
updatedRelease := &release.Release{ updatedRelease := &release.Release{
Name: req.Name, Name: req.Name,
Chart: req.Chart, Namespace: currentRelease.Namespace,
Config: req.Values, Chart: req.Chart,
Version: rel.Version + 1, Config: req.Values,
Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
},
Version: currentRelease.Version + 1,
Manifest: manifestDoc.String(),
Hooks: hooks,
} }
return updatedRelease, nil
return currentRelease, updatedRelease, nil
} }
func (s *releaseServer) uniqName(start string, reuse bool) (string, error) { func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
@ -240,10 +281,12 @@ func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
if start != "" { if start != "" {
if rel, err := s.env.Releases.Get(start); err == driver.ErrReleaseNotFound { if rel, err := s.env.Releases.Get(start); err == driver.ErrReleaseNotFound {
return start, nil return start, nil
} else if reuse && rel.Info.Status.Code == release.Status_DELETED { } else if st := rel.Info.Status.Code; reuse && (st == release.Status_DELETED || st == release.Status_FAILED) {
// Allowe re-use of names if the previous release is marked deleted. // Allowe re-use of names if the previous release is marked deleted.
log.Printf("reusing name %q", start) log.Printf("reusing name %q", start)
return start, nil return start, nil
} else if reuse {
return "", errors.New("cannot re-use a name that is still in use")
} }
return "", fmt.Errorf("a release named %q already exists", start) return "", fmt.Errorf("a release named %q already exists", start)
@ -306,12 +349,36 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return nil, err return nil, err
} }
renderer := s.engine(req.Chart) hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender)
files, err := renderer.Render(req.Chart, valuesToRender)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Store a release.
rel := &release.Release{
Name: name,
Namespace: req.Namespace,
Chart: req.Chart,
Config: req.Values,
Info: &release.Info{
FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
},
Manifest: manifestDoc.String(),
Hooks: hooks,
Version: 1,
}
return rel, nil
}
func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values) ([]*release.Hook, *bytes.Buffer, error) {
renderer := s.engine(ch)
files, err := renderer.Render(ch, values)
if err != nil {
return nil, nil, err
}
// Sort hooks, manifests, and partials. Only hooks and manifests are returned, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also // as partials are not used after renderer.Render. Empty manifests are also
// removed here. // removed here.
@ -319,7 +386,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
if err != nil { if err != nil {
// By catching parse errors here, we can prevent bogus releases from going // By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes. // to Kubernetes.
return nil, err return nil, nil, err
} }
// Aggregate all valid manifests into one big doc. // Aggregate all valid manifests into one big doc.
@ -329,22 +396,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
b.WriteString(file) b.WriteString(file)
} }
// Store a release. return hooks, b, nil
rel := &release.Release{
Name: name,
Namespace: req.Namespace,
Chart: req.Chart,
Config: req.Values,
Info: &release.Info{
FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
},
Manifest: b.String(),
Hooks: hooks,
Version: 1,
}
return rel, nil
} }
// validateYAML checks to see if YAML is well-formed. // validateYAML checks to see if YAML is well-formed.

@ -45,6 +45,16 @@ data:
name: value name: value
` `
var manifestWithUpgradeHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-upgrade,pre-upgrade
data:
name: value
`
func rsFixture() *releaseServer { func rsFixture() *releaseServer {
return &releaseServer{ return &releaseServer{
env: mockEnvironment(), env: mockEnvironment(),
@ -80,8 +90,9 @@ func releaseStub() *release.Release {
LastDeployed: &date, LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED}, Status: &release.Status{Code: release.Status_DEPLOYED},
}, },
Chart: chartStub(), Chart: chartStub(),
Config: &chart.Config{Raw: `name = "value"`}, Config: &chart.Config{Raw: `name = "value"`},
Version: 1,
Hooks: []*release.Hook{ Hooks: []*release.Hook{
{ {
Name: "test-cm", Name: "test-cm",
@ -290,6 +301,105 @@ func TestInstallReleaseReuseName(t *testing.T) {
} }
} }
func TestUpdateRelease(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Errorf("Failed updated: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
if res.Release.Name != rel.Name {
t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name)
}
if res.Release.Namespace != rel.Namespace {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace)
}
updated, err := rs.env.Releases.Get(res.Release.Name)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithUpgradeHooks {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE {
t.Errorf("Expected event 0 to be post upgrade")
}
if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE {
t.Errorf("Expected event 0 to be pre upgrade")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if len(updated.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(updated.Manifest, "---\n# Source: hello/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
if res.Release.Version != 2 {
t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version)
}
}
func TestUpdateReleaseNoHooks(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
DisableHooks: true,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Errorf("Failed updated: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl)
}
}
func TestUninstallRelease(t *testing.T) { func TestUninstallRelease(t *testing.T) {
c := context.Background() c := context.Background()
rs := rsFixture() rs := rsFixture()

@ -75,9 +75,8 @@ The Go files generated from the `proto` definitions are stored in
Docker images are built by cross-compiling Linux binaries and then Docker images are built by cross-compiling Linux binaries and then
building a Docker image from the files in `rootfs`. building a Docker image from the files in `rootfs`.
The `scripts/` directory contains a number of utility scripts, including The `scripts/` directory contains a number of utility scripts. Most of these
`local-cluster.sh`, which can start a full Kubernetes instance inside of are used by the CI/CD pipeline.
a Docker container.
Go dependencies are managed with Go dependencies are managed with
[Glide](https://github.com/Masterminds/glide) and stored in the [Glide](https://github.com/Masterminds/glide) and stored in the

@ -202,6 +202,11 @@ sensitive_.
- `Chart`: The contents of the `Chart.yaml`. Thus, the chart version is - `Chart`: The contents of the `Chart.yaml`. Thus, the chart version is
obtainable as `Chart.Version` and the maintainers are in obtainable as `Chart.Version` and the maintainers are in
`Chart.Maintainers`. `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. Note that
file data is returned as a `[]byte` unless `{{.Files.GetString}}` is used.
**NOTE:** Any unknown Chart.yaml fields will be dropped. They will not **NOTE:** Any unknown Chart.yaml fields will be dropped. They will not
be accessible inside of the `Chart` object. Thus, Chart.yaml cannot be be accessible inside of the `Chart` object. Thus, Chart.yaml cannot be

@ -66,11 +66,31 @@ GCR registry.
## Running a Local Cluster ## Running a Local Cluster
You can run tests locally using the `scripts/local-cluster.sh` script to For development, we highly recommend using the
start Kubernetes inside of a Docker container. For OS X, you will need [Kubernetes Minikube](https://github.com/kubernetes/minikube)
to be running `docker-machine`. developer-oriented distribution. Once this is installed, you can use
`helm init` to install into the cluster.
Tiller should run on any >= 1.2 Kubernetes cluster with beta extensions. For developing on Tiller, it is sometimes more expedient to run Tiller locally
instead of packaging it into an image and running it in-cluster. You can do
this by telling the Helm client to us a local instance.
```console
$ make build
$ bin/tiller
```
And to configure the Helm client, use the `--host` flag or export the `HELM_HOST`
environment variable:
```console
$ export HELM_HOST=localhost:44134
$ helm install foo
```
(Note that you do not need to use `helm init` when you are running Tiller directly)
Tiller should run on any >= 1.3 Kubernetes cluster.
## Contribution Guidelines ## Contribution Guidelines

154
glide.lock generated

@ -1,10 +1,10 @@
hash: 141ef5b9c491c91b026ab4007e48502c9a6df9f173c40e1406233dd44f065190 hash: d3f3df18316dca3703f5d073e8f9b1e6bfdb27e8d7fc9c5d742afeddebb022db
updated: 2016-07-05T16:51:52.631048739-07:00 updated: 2016-07-30T23:52:42.581826208-07:00
imports: imports:
- name: github.com/aokoli/goutils - name: github.com/aokoli/goutils
version: 9c37978a95bd5c709a15883b6242714ea6709e64 version: 9c37978a95bd5c709a15883b6242714ea6709e64
- name: github.com/asaskevich/govalidator - name: github.com/asaskevich/govalidator
version: df81827fdd59d8b4fb93d8910b286ab7a3919520 version: 7664702784775e51966f0885f5cd27435916517b
- name: github.com/beorn7/perks - name: github.com/beorn7/perks
version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 version: 3ac7bf7a47d159a033b107610db8a1b6575507a4
subpackages: subpackages:
@ -133,7 +133,7 @@ imports:
- ptypes/any - ptypes/any
- ptypes/timestamp - ptypes/timestamp
- name: github.com/google/cadvisor - name: github.com/google/cadvisor
version: 4dbefc9b671b81257973a33211fb12370c1a526e version: c2ea32971ae033041f0fb0f309b1dee94fd1d55f
subpackages: subpackages:
- api - api
- cache/memory - cache/memory
@ -267,15 +267,15 @@ imports:
- name: google.golang.org/appengine - name: google.golang.org/appengine
version: 12d5545dc1cfa6047a286d5e853841b6471f4c19 version: 12d5545dc1cfa6047a286d5e853841b6471f4c19
subpackages: subpackages:
- urlfetch
- internal - internal
- internal/urlfetch
- internal/app_identity - internal/app_identity
- internal/modules
- internal/base - internal/base
- internal/datastore - internal/datastore
- internal/log - internal/log
- internal/modules
- internal/remote_api - internal/remote_api
- urlfetch
- internal/urlfetch
- name: google.golang.org/cloud - name: google.golang.org/cloud
version: eb47ba841d53d93506cfbfbc03927daf9cc48f88 version: eb47ba841d53d93506cfbfbc03927daf9cc48f88
subpackages: subpackages:
@ -297,40 +297,67 @@ imports:
- name: gopkg.in/yaml.v2 - name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4 version: a83829b6f1293c91addabc89d0571c246397bbf4
- name: k8s.io/kubernetes - name: k8s.io/kubernetes
version: 283137936a498aed572ee22af6774b6fb6e9fd94 version: e7f022c926583ed8e755a52f23abc4cf8b532d12
subpackages: subpackages:
- pkg/api - pkg/api
- pkg/api/meta - pkg/api/meta
- pkg/api/error
- pkg/client/restclient - pkg/client/restclient
- pkg/client/unversioned - pkg/client/unversioned
- pkg/apis/batch
- pkg/client/unversioned/clientcmd - pkg/client/unversioned/clientcmd
- pkg/client/unversioned/fake - pkg/client/unversioned/fake
- pkg/client/unversioned/portforward - pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand - pkg/client/unversioned/remotecommand
- pkg/kubectl
- pkg/kubectl/cmd/util - pkg/kubectl/cmd/util
- pkg/kubectl/resource - pkg/kubectl/resource
- pkg/labels - pkg/labels
- pkg/runtime
- pkg/watch
- pkg/api/errors - pkg/api/errors
- pkg/client/unversioned/testclient
- pkg/api/meta/metatypes - pkg/api/meta/metatypes
- pkg/api/resource - pkg/api/resource
- pkg/api/unversioned - pkg/api/unversioned
- pkg/auth/user - pkg/auth/user
- pkg/conversion - pkg/conversion
- pkg/fields - pkg/fields
- pkg/runtime
- pkg/runtime/serializer - pkg/runtime/serializer
- pkg/types - pkg/types
- pkg/util - pkg/util
- pkg/util/intstr - pkg/util/intstr
- pkg/util/rand - pkg/util/rand
- pkg/util/sets - pkg/util/sets
- pkg/api/install
- pkg/apimachinery/registered
- pkg/apis/apps
- pkg/apis/apps/install
- pkg/apis/authentication.k8s.io/install
- pkg/apis/authorization/install
- pkg/apis/autoscaling
- pkg/apis/autoscaling/install
- pkg/apis/batch/install
- pkg/apis/batch/v2alpha1
- pkg/apis/componentconfig/install
- pkg/apis/extensions
- pkg/apis/extensions/install
- pkg/apis/policy
- pkg/apis/policy/install
- pkg/apis/rbac
- pkg/apis/rbac/install
- pkg/client/typed/discovery
- pkg/util/net
- pkg/util/wait
- pkg/version
- plugin/pkg/client/auth
- pkg/util/validation
- pkg/util/validation/field
- pkg/client/unversioned/auth - pkg/client/unversioned/auth
- pkg/client/unversioned/clientcmd/api - pkg/client/unversioned/clientcmd/api
- pkg/client/unversioned/clientcmd/api/latest - pkg/client/unversioned/clientcmd/api/latest
- pkg/util/errors - pkg/util/errors
- pkg/util/homedir - pkg/util/homedir
- pkg/util/validation
- pkg/util/validation/field
- pkg/kubelet/server/portforward - pkg/kubelet/server/portforward
- pkg/util/httpstream - pkg/util/httpstream
- pkg/util/runtime - pkg/util/runtime
@ -339,42 +366,53 @@ imports:
- pkg/util/httpstream/spdy - pkg/util/httpstream/spdy
- federation/apis/federation - federation/apis/federation
- federation/client/clientset_generated/federation_internalclientset - federation/client/clientset_generated/federation_internalclientset
- pkg/api/service - pkg/api/annotations
- pkg/api/util
- pkg/api/v1
- pkg/api/validation - pkg/api/validation
- pkg/apimachinery - pkg/apis/batch/v1
- pkg/apimachinery/registered - pkg/client/clientset_generated/internalclientset
- pkg/apis/apps
- pkg/apis/autoscaling
- pkg/apis/batch
- pkg/apis/extensions
- pkg/apis/policy
- pkg/apis/rbac
- pkg/client/typed/discovery
- pkg/client/unversioned/adapters/internalclientset - pkg/client/unversioned/adapters/internalclientset
- pkg/credentialprovider
- pkg/fieldpath
- pkg/kubelet/qos/util
- pkg/util/deployment
- pkg/util/integer
- pkg/util/jsonpath
- pkg/util/slice
- pkg/api/service
- pkg/apimachinery
- pkg/controller - pkg/controller
- pkg/kubectl
- pkg/registry/thirdpartyresourcedata - pkg/registry/thirdpartyresourcedata
- pkg/runtime/serializer/json - pkg/runtime/serializer/json
- pkg/util/flag - pkg/util/flag
- pkg/util/strategicpatch - pkg/util/strategicpatch
- pkg/watch
- pkg/util/yaml - pkg/util/yaml
- pkg/api/testapi
- third_party/forked/reflect
- pkg/conversion/queryparams - pkg/conversion/queryparams
- pkg/util/json - pkg/util/json
- pkg/api/testapi
- third_party/forked/reflect
- pkg/runtime/serializer/protobuf - pkg/runtime/serializer/protobuf
- pkg/runtime/serializer/recognizer - pkg/runtime/serializer/recognizer
- pkg/runtime/serializer/versioning - pkg/runtime/serializer/versioning
- pkg/util/wait - pkg/watch/versioned
- pkg/api/v1 - pkg/apis/apps/v1alpha1
- pkg/apis/authentication.k8s.io
- pkg/apis/authentication.k8s.io/v1beta1
- pkg/apis/authorization
- pkg/apis/authorization/v1beta1
- pkg/apis/autoscaling/v1
- pkg/apis/componentconfig
- pkg/apis/componentconfig/v1alpha1
- pkg/apis/extensions/v1beta1
- pkg/apis/policy/v1alpha1
- pkg/apis/rbac/v1alpha1
- pkg/client/metrics - pkg/client/metrics
- pkg/runtime/serializer/streaming - pkg/runtime/serializer/streaming
- pkg/util/crypto - pkg/util/crypto
- pkg/util/flowcontrol - pkg/util/flowcontrol
- pkg/util/net - plugin/pkg/client/auth/gcp
- pkg/version - plugin/pkg/client/auth/oidc
- pkg/watch/versioned
- pkg/client/unversioned/clientcmd/api/v1 - pkg/client/unversioned/clientcmd/api/v1
- pkg/httplog - pkg/httplog
- pkg/util/wsstream - pkg/util/wsstream
@ -382,71 +420,35 @@ imports:
- federation/apis/federation/install - federation/apis/federation/install
- federation/client/clientset_generated/federation_internalclientset/typed/core/unversioned - federation/client/clientset_generated/federation_internalclientset/typed/core/unversioned
- federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned - federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned
- pkg/util/net/sets - pkg/util/parsers
- pkg/api/endpoints - pkg/api/endpoints
- pkg/api/pod - pkg/api/pod
- pkg/api/unversioned/validation - pkg/api/unversioned/validation
- pkg/api/util
- pkg/capabilities - pkg/capabilities
- pkg/api/install - pkg/client/clientset_generated/internalclientset/typed/autoscaling/unversioned
- pkg/apis/apps/install
- pkg/apis/authentication.k8s.io/install
- pkg/apis/authorization/install
- pkg/apis/autoscaling/install
- pkg/apis/batch/install
- pkg/apis/batch/v2alpha1
- pkg/apis/componentconfig/install
- pkg/apis/extensions/install
- pkg/apis/policy/install
- pkg/apis/rbac/install
- plugin/pkg/client/auth
- pkg/client/clientset_generated/internalclientset
- pkg/client/clientset_generated/internalclientset/typed/batch/unversioned - pkg/client/clientset_generated/internalclientset/typed/batch/unversioned
- pkg/client/clientset_generated/internalclientset/typed/core/unversioned - pkg/client/clientset_generated/internalclientset/typed/core/unversioned
- pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned - pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned
- pkg/client/clientset_generated/internalclientset/typed/rbac/unversioned
- pkg/util/labels
- pkg/util/pod
- pkg/util/replicaset
- third_party/golang/template
- pkg/util/net/sets
- pkg/client/cache - pkg/client/cache
- pkg/client/record - pkg/client/record
- pkg/controller/framework - pkg/controller/framework
- pkg/util/hash - pkg/util/hash
- pkg/util/integer
- pkg/api/annotations
- pkg/apis/batch/v1
- pkg/credentialprovider
- pkg/fieldpath
- pkg/kubelet/qos/util
- pkg/util/deployment
- pkg/util/jsonpath
- pkg/util/slice
- pkg/api/rest - pkg/api/rest
- pkg/apis/extensions/v1beta1
- pkg/apis/extensions/validation - pkg/apis/extensions/validation
- pkg/registry/generic - pkg/registry/generic
- pkg/util/framer - pkg/util/framer
- third_party/forked/json - third_party/forked/json
- pkg/util/parsers - pkg/kubelet/qos
- pkg/master/ports
- federation/apis/federation/v1beta1 - federation/apis/federation/v1beta1
- pkg/apis/apps/v1alpha1
- pkg/apis/authentication.k8s.io
- pkg/apis/authentication.k8s.io/v1beta1
- pkg/apis/authorization
- pkg/apis/authorization/v1beta1
- pkg/apis/autoscaling/v1
- pkg/apis/componentconfig
- pkg/apis/componentconfig/v1alpha1
- pkg/apis/policy/v1alpha1
- pkg/apis/rbac/v1alpha1
- plugin/pkg/client/auth/gcp
- plugin/pkg/client/auth/oidc
- pkg/client/clientset_generated/internalclientset/typed/autoscaling/unversioned
- pkg/client/clientset_generated/internalclientset/typed/rbac/unversioned
- pkg/util/labels
- pkg/util/pod
- pkg/util/replicaset
- third_party/golang/template
- pkg/security/podsecuritypolicy/util - pkg/security/podsecuritypolicy/util
- pkg/storage - pkg/storage
- pkg/kubelet/qos
- pkg/master/ports
- name: speter.net/go/exp/math/dec/inf - name: speter.net/go/exp/math/dec/inf
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
repo: https://github.com/go-inf/inf.git repo: https://github.com/go-inf/inf.git

@ -22,22 +22,32 @@ import:
- package: google.golang.org/grpc - package: google.golang.org/grpc
version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 version: dec33edc378cf4971a2741cfd86ed70a644d6ba3
- package: k8s.io/kubernetes - package: k8s.io/kubernetes
version: v1.3.0 version: ~1.3
subpackages: subpackages:
- pkg/api - pkg/api
- pkg/api/meta - pkg/api/meta
- pkg/api/error
- pkg/api/unversioned
- pkg/apimachinery/registered
- pkg/client/restclient - pkg/client/restclient
- pkg/client/unversioned - pkg/client/unversioned
- pkg/apis/batch
- pkg/client/unversioned/clientcmd - pkg/client/unversioned/clientcmd
- pkg/client/unversioned/fake - pkg/client/unversioned/fake
- pkg/client/unversioned/portforward - pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand - pkg/client/unversioned/remotecommand
- pkg/kubectl
- pkg/kubectl/cmd/util - pkg/kubectl/cmd/util
- pkg/kubectl/resource - pkg/kubectl/resource
- pkg/labels - pkg/labels
- pkg/runtime
- pkg/watch
- pkg/util/strategicpatch
- pkg/util/yaml
- package: github.com/gosuri/uitable - package: github.com/gosuri/uitable
- package: speter.net/go/exp/math/dec/inf - package: speter.net/go/exp/math/dec/inf
version: ^0.9.0
repo: https://github.com/go-inf/inf.git repo: https://github.com/go-inf/inf.git
vcs: git vcs: git
- package: github.com/asaskevich/govalidator - package: github.com/asaskevich/govalidator
- package: github.com/satori/go.uuid version: ^4.0.0

@ -48,7 +48,23 @@ const defaultIgnore = `# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and # This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line. # negation (prefixed with !). Only one pattern per line.
.DS_Store .DS_Store
.git # Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
` `
// Create creates a new chart in a directory. // Create creates a new chart in a directory.

@ -32,11 +32,14 @@ func NewFiles(from []*any.Any) Files {
return files return files
} }
// Get a file by path. // GetBytes gets a file by path.
//
// The returned data is raw. In a template context, this is identical to calling
// {{index .Files $path}}.
// //
// This is intended to be accessed from within a template, so a missed key returns // This is intended to be accessed from within a template, so a missed key returns
// an empty []byte. // an empty []byte.
func (f Files) Get(name string) []byte { func (f Files) GetBytes(name string) []byte {
v, ok := f[name] v, ok := f[name]
if !ok { if !ok {
return []byte{} return []byte{}
@ -44,10 +47,12 @@ func (f Files) Get(name string) []byte {
return v return v
} }
// GetString returns a string representation of the given file. // Get returns a string representation of the given file.
//
// Fetch the contents of a file as a string. It is designed to be called in a
// template.
// //
// This is a convenience for the otherwise cumbersome template logic // {{.Files.Get "foo"}}
// for '{{.Files.Get "foo" | printf "%s"}}'. func (f Files) Get(name string) string {
func (f Files) GetString(name string) string { return string(f.GetBytes(name))
return string(f.Get(name))
} }

@ -43,10 +43,10 @@ func TestNewFiles(t *testing.T) {
} }
for i, f := range cases { for i, f := range cases {
if got := string(files.Get(f.path)); got != f.data { if got := string(files.GetBytes(f.path)); got != f.data {
t.Errorf("%d: expected %q, got %q", i, f.data, got) t.Errorf("%d: expected %q, got %q", i, f.data, got)
} }
if got := files.GetString(f.path); got != f.data { if got := files.Get(f.path); got != f.data {
t.Errorf("%d: expected %q, got %q", i, f.data, got) t.Errorf("%d: expected %q, got %q", i, f.data, got)
} }
} }

@ -218,6 +218,11 @@ func LoadDir(dir string) (*chart.Chart, error) {
return err return err
} }
if fi.IsDir() { if fi.IsDir() {
// Directory-based ignore rules should involve skipping the entire
// contents of that directory.
if rules.Ignore(n, fi) {
return filepath.SkipDir
}
return nil return nil
} }

@ -49,8 +49,9 @@ func verifyChart(t *testing.T, c *chart.Chart) {
t.Errorf("Expected 1 template, got %d", len(c.Templates)) t.Errorf("Expected 1 template, got %d", len(c.Templates))
} }
if len(c.Files) != 5 { numfiles := 6
t.Errorf("Expected 5 extra files, got %d", len(c.Files)) if len(c.Files) != numfiles {
t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files))
for _, n := range c.Files { for _, n := range c.Files {
t.Logf("\t%s", n.TypeUrl) t.Logf("\t%s", n.TypeUrl)
} }

Binary file not shown.

@ -9,4 +9,4 @@ tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner
# Pack the frobnitz chart. # Pack the frobnitz chart.
echo "Packing frobnitz" echo "Packing frobnitz"
tar -zcvf frobnitz-1.2.3.tgz frobnitz tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz

@ -66,19 +66,13 @@ func Install(namespace, image string, verbose bool) error {
// InstallYAML is the installation YAML for DM. // InstallYAML is the installation YAML for DM.
const InstallYAML = ` const InstallYAML = `
--- ---
apiVersion: v1 apiVersion: extensions/v1beta1
kind: ReplicationController kind: Deployment
metadata: metadata:
labels: name: tiller-deploy
app: helm
name: tiller
name: tiller-rc
namespace: {{ .Namespace }} namespace: {{ .Namespace }}
spec: spec:
replicas: 1 replicas: 1
selector:
app: helm
name: tiller
template: template:
metadata: metadata:
labels: labels:

@ -88,6 +88,28 @@ type renderable struct {
vals chartutil.Values vals chartutil.Values
} }
// alterFuncMap takes the Engine's FuncMap and adds context-specific functions.
//
// The resulting FuncMap is only valid for the passed-in template.
func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap {
// Clone the func map because we are adding context-specific functions.
var funcMap template.FuncMap = map[string]interface{}{}
for k, v := range e.FuncMap {
funcMap[k] = v
}
// Add the 'include' function here so we can close over t.
funcMap["include"] = func(name string, data interface{}) string {
buf := bytes.NewBuffer(nil)
if err := t.ExecuteTemplate(buf, name, data); err != nil {
buf.WriteString(err.Error())
}
return buf.String()
}
return funcMap
}
// render takes a map of templates/values and renders them. // render takes a map of templates/values and renders them.
func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) { func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) {
// Basically, what we do here is start with an empty parent template and then // Basically, what we do here is start with an empty parent template and then
@ -105,10 +127,13 @@ func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) {
// but will still emit <no value> for others. We mitigate that later. // but will still emit <no value> for others. We mitigate that later.
t.Option("missingkey=zero") t.Option("missingkey=zero")
} }
funcMap := e.alterFuncMap(t)
files := []string{} files := []string{}
for fname, r := range tpls { for fname, r := range tpls {
log.Printf("Preparing template %s", fname) log.Printf("Preparing template %s", fname)
t = t.New(fname).Funcs(e.FuncMap) t = t.New(fname).Funcs(funcMap)
if _, err := t.Parse(r.tpl); err != nil { if _, err := t.Parse(r.tpl); err != nil {
return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err)
} }

@ -312,7 +312,7 @@ func TestRenderBuiltinValues(t *testing.T) {
Metadata: &chart.Metadata{Name: "Latium"}, Metadata: &chart.Metadata{Name: "Latium"},
Templates: []*chart.Template{ Templates: []*chart.Template{
{Name: "Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, {Name: "Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
{Name: "From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.GetString "book/title.txt"}}`)}, {Name: "From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)},
}, },
Values: &chart.Config{Raw: ``}, Values: &chart.Config{Raw: ``},
Dependencies: []*chart.Chart{}, Dependencies: []*chart.Chart{},
@ -358,3 +358,33 @@ func TestRenderBuiltinValues(t *testing.T) {
} }
} }
func TestAlterFuncMap(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{Name: "conrad"},
Templates: []*chart.Template{
{Name: "quote", Data: []byte(`{{include "conrad/_partial" . | indent 2}} dead.`)},
{Name: "_partial", Data: []byte(`{{.Release.Name}} - he`)},
},
Values: &chart.Config{Raw: ``},
Dependencies: []*chart.Chart{},
}
v := chartutil.Values{
"Values": &chart.Config{Raw: ""},
"Chart": c.Metadata,
"Release": chartutil.Values{
"Name": "Mistah Kurtz",
},
}
out, err := New().Render(c, v)
if err != nil {
t.Fatal(err)
}
expect := " Mistah Kurtz - he dead."
if got := out["conrad/quote"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expect, got, out)
}
}

@ -143,6 +143,13 @@ func DeleteDryRun(dry bool) DeleteOption {
} }
} }
// UpgradeDisableHooks will disable hooks for an upgrade operation.
func UpgradeDisableHooks(disable bool) UpdateOption {
return func(opts *options) {
opts.disableHooks = disable
}
}
// UpgradeDryRun will (if true) execute an upgrade as a dry run. // UpgradeDryRun will (if true) execute an upgrade as a dry run.
func UpgradeDryRun(dry bool) UpdateOption { func UpgradeDryRun(dry bool) UpdateOption {
return func(opts *options) { return func(opts *options) {
@ -237,9 +244,15 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient,
// Executes tiller.UpdateRelease RPC. // Executes tiller.UpdateRelease RPC.
func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
//TODO: handle dryRun for _, opt := range opts {
opt(o)
}
o.updateReq.Chart = chr
o.updateReq.DryRun = o.dryRun
o.updateReq.Name = rlsName
return rlc.UpdateRelease(context.TODO(), &rls.UpdateReleaseRequest{Name: rlsName, Chart: chr}) return rlc.UpdateRelease(context.TODO(), &o.updateReq)
} }
// Executes tiller.GetReleaseStatus RPC. // Executes tiller.GetReleaseStatus RPC.

@ -65,7 +65,6 @@ func Parse(file io.Reader) (*Rules, error) {
if err := s.Err(); err != nil { if err := s.Err(); err != nil {
return r, err return r, err
} }
return r, nil return r, nil
} }
@ -97,8 +96,10 @@ func (r *Rules) Ignore(path string, fi os.FileInfo) bool {
continue continue
} }
// If the rule is looking for directories, and this is not a directory,
// skip it.
if p.mustDir && !fi.IsDir() { if p.mustDir && !fi.IsDir() {
return false continue
} }
if p.match(path, fi) { if p.match(path, fi) {
return true return true

@ -20,15 +20,21 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"reflect"
"time" "time"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
) )
@ -57,6 +63,77 @@ func (c *Client) Create(namespace string, reader io.Reader) error {
return perform(c, namespace, reader, createResource) return perform(c, namespace, reader, createResource)
} }
// Update reads in the current configuration and a modified configuration from io.reader
// and creates resources that don't already exists, updates resources that have been modified
// and deletes resources from the current configuration that are not present in the
// modified configuration
//
// Namespace will set the namespaces
func (c *Client) Update(namespace string, currentReader, modifiedReader io.Reader) error {
current := c.NewBuilder(includeThirdPartyAPIs).
ContinueOnError().
NamespaceParam(namespace).
DefaultNamespace().
Stream(currentReader, "").
Flatten().
Do()
modified := c.NewBuilder(includeThirdPartyAPIs).
ContinueOnError().
NamespaceParam(namespace).
DefaultNamespace().
Stream(modifiedReader, "").
Flatten().
Do()
currentInfos, err := current.Infos()
if err != nil {
return err
}
modifiedInfos := []*resource.Info{}
modified.Visit(func(info *resource.Info, err error) error {
modifiedInfos = append(modifiedInfos, info)
if err != nil {
return err
}
resourceName := info.Name
helper := resource.NewHelper(info.Client, info.Mapping)
if _, err := helper.Get(info.Namespace, resourceName, info.Export); err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("Could not get information about the resource: err: %s", err)
}
// Since the resource does not exist, create it.
if err := createResource(info); err != nil {
return err
}
kind := info.Mapping.GroupVersionKind.Kind
log.Printf("Created a new %s called %s\n", kind, resourceName)
return nil
}
currentObj, err := getCurrentObject(resourceName, currentInfos)
if err != nil {
return err
}
if err := updateResource(info, currentObj); err != nil {
log.Printf("error updating the resource %s:\n\t %v", resourceName, err)
return err
}
return err
})
deleteUnwantedResources(currentInfos, modifiedInfos)
return nil
}
// Delete deletes kubernetes resources from an io.reader // Delete deletes kubernetes resources from an io.reader
// //
// Namespace will set the namespace // Namespace will set the namespace
@ -136,6 +213,51 @@ func createResource(info *resource.Info) error {
return err return err
} }
func deleteResource(info *resource.Info) error {
return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name)
}
func updateResource(modified *resource.Info, currentObj runtime.Object) error {
encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
originalSerialization, err := runtime.Encode(encoder, currentObj)
if err != nil {
return err
}
editedSerialization, err := runtime.Encode(encoder, modified.Object)
if err != nil {
return err
}
originalJS, err := yaml.ToJSON(originalSerialization)
if err != nil {
return err
}
editedJS, err := yaml.ToJSON(editedSerialization)
if err != nil {
return err
}
if reflect.DeepEqual(originalJS, editedJS) {
return fmt.Errorf("Looks like there are no changes for %s", modified.Name)
}
patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, currentObj)
if err != nil {
return err
}
// send patch to server
helper := resource.NewHelper(modified.Client, modified.Mapping)
if _, err = helper.Patch(modified.Namespace, modified.Name, api.StrategicMergePatchType, patch); err != nil {
return err
}
return nil
}
func watchUntilReady(info *resource.Info) error { func watchUntilReady(info *resource.Info) error {
w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
if err != nil { if err != nil {
@ -213,3 +335,37 @@ func (c *Client) ensureNamespace(namespace string) error {
} }
return nil return nil
} }
func deleteUnwantedResources(currentInfos, modifiedInfos []*resource.Info) {
for _, cInfo := range currentInfos {
found := false
for _, m := range modifiedInfos {
if m.Name == cInfo.Name {
found = true
}
}
if !found {
log.Printf("Deleting %s...", cInfo.Name)
if err := deleteResource(cInfo); err != nil {
log.Printf("Failed to delete %s, err: %s", cInfo.Name, err)
}
}
}
}
func getCurrentObject(targetName string, infos []*resource.Info) (runtime.Object, error) {
var curr *resource.Info
for _, currInfo := range infos {
if currInfo.Name == targetName {
curr = currInfo
}
}
if curr == nil {
return nil, fmt.Errorf("No resource with the name %s found.", targetName)
}
encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
defaultVersion := unversioned.GroupVersion{}
return resource.AsVersionedObject([]*resource.Info{curr}, false, defaultVersion, encoder)
}

@ -17,15 +17,55 @@ limitations under the License.
package kube package kube
import ( import (
"bytes"
"encoding/json"
"io" "io"
"io/ioutil"
"net/http"
"strings" "strings"
"testing" "testing"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
api "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
) )
func TestUpdateResource(t *testing.T) {
tests := []struct {
name string
namespace string
modified *resource.Info
currentObj runtime.Object
err bool
errMessage string
}{
{
name: "no changes when updating resources",
modified: createFakeInfo("nginx", nil),
currentObj: createFakePod("nginx", nil),
err: true,
errMessage: "Looks like there are no changes for nginx",
},
//{
//name: "valid update input",
//modified: createFakeInfo("nginx", map[string]string{"app": "nginx"}),
//currentObj: createFakePod("nginx", nil),
//},
}
for _, tt := range tests {
err := updateResource(tt.modified, tt.currentObj)
if err != nil && err.Error() != tt.errMessage {
t.Errorf("%q. expected error message: %v, got %v", tt.name, tt.errMessage, err)
}
}
}
func TestPerform(t *testing.T) { func TestPerform(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -214,3 +254,53 @@ spec:
ports: ports:
- containerPort: 80 - containerPort: 80
` `
func createFakePod(name string, labels map[string]string) runtime.Object {
objectMeta := createObjectMeta(name, labels)
object := &api.Pod{
ObjectMeta: objectMeta,
}
return object
}
func createFakeInfo(name string, labels map[string]string) *resource.Info {
pod := createFakePod(name, labels)
marshaledObj, _ := json.Marshal(pod)
mapping := &meta.RESTMapping{
Resource: name,
Scope: meta.RESTScopeNamespace,
GroupVersionKind: unversioned.GroupVersionKind{
Kind: "Pod",
Version: "v1",
}}
client := &fake.RESTClient{
Codec: testapi.Default.Codec(),
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return &http.Response{
StatusCode: 200,
Header: header,
Body: ioutil.NopCloser(bytes.NewReader(marshaledObj)),
}, nil
})}
info := resource.NewInfo(client, mapping, "default", "nginx", false)
info.Object = pod
return info
}
func createObjectMeta(name string, labels map[string]string) api.ObjectMeta {
objectMeta := api.ObjectMeta{Name: name, Namespace: "default"}
if labels != nil {
objectMeta.Labels = labels
}
return objectMeta
}

@ -51,7 +51,7 @@ func (c *Client) ForwardPort(namespace, podName string, remote int) (*Tunnel, er
} }
// Build a url to the portforward endpoing // Build a url to the portforward endpoing
// example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-rc-9itlq/portforward // example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-deploy-9itlq/portforward
u := client.RESTClient.Post(). u := client.RESTClient.Post().
Resource("pods"). Resource("pods").
Namespace(namespace). Namespace(namespace).

@ -226,6 +226,8 @@ type UpdateReleaseRequest struct {
Values *hapi_chart.Config `protobuf:"bytes,3,opt,name=values" json:"values,omitempty"` Values *hapi_chart.Config `protobuf:"bytes,3,opt,name=values" json:"values,omitempty"`
// dry_run, if true, will run through the release logic, but neither create // dry_run, if true, will run through the release logic, but neither create
DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"`
// DisableHooks causes the server to skip running any hooks for the upgrade.
DisableHooks bool `protobuf:"varint,5,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"`
} }
func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} }
@ -624,54 +626,54 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
} }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 769 bytes of a gzipped FileDescriptorProto // 774 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdb, 0x6e, 0xd3, 0x4a, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x56, 0xdb, 0x6e, 0xd3, 0x4c,
0x14, 0xad, 0x93, 0xd4, 0x49, 0x76, 0x2f, 0x4a, 0xe7, 0xb4, 0x8d, 0x8f, 0x75, 0x0e, 0x42, 0x46, 0x10, 0xae, 0x93, 0x34, 0x87, 0xe9, 0x41, 0xe9, 0xfe, 0x6d, 0x93, 0xdf, 0xfa, 0x7f, 0x84, 0x8c,
0x40, 0x29, 0xd4, 0x81, 0xf0, 0x8e, 0x94, 0xb6, 0x51, 0x5b, 0x35, 0xa4, 0xd2, 0x84, 0x82, 0xc4, 0x80, 0x52, 0xa8, 0x03, 0xe1, 0x1e, 0x29, 0x6d, 0xa3, 0xb6, 0x6a, 0x48, 0xa5, 0x0d, 0x05, 0x89,
0x03, 0x91, 0x9b, 0x4c, 0xa8, 0xc1, 0xb5, 0x83, 0x67, 0x52, 0xd1, 0x4f, 0xe0, 0x0f, 0xf8, 0x14, 0x0b, 0x22, 0x37, 0xd9, 0x50, 0x83, 0x6b, 0x07, 0xef, 0xa6, 0xa2, 0x8f, 0xc0, 0x1b, 0x71, 0xc3,
0x3e, 0x88, 0xbf, 0xe0, 0x85, 0xb9, 0xd8, 0x26, 0x17, 0x1b, 0x4c, 0x5f, 0x9c, 0x99, 0xd9, 0x6b, 0xdb, 0xf0, 0x16, 0xdc, 0xb0, 0x07, 0xaf, 0xc9, 0xc1, 0x06, 0xd3, 0x1b, 0x67, 0x77, 0xe7, 0xdb,
0xaf, 0x7d, 0xdf, 0x0a, 0x98, 0x97, 0xce, 0xd8, 0x6d, 0x50, 0x12, 0x5e, 0xbb, 0x03, 0x42, 0x1b, 0x6f, 0x66, 0xbe, 0x99, 0x9d, 0x16, 0xcc, 0x4b, 0x67, 0xec, 0x36, 0x28, 0x09, 0xaf, 0xdd, 0x01,
0xcc, 0xf5, 0x3c, 0x12, 0xda, 0xe3, 0x30, 0x60, 0x01, 0xda, 0x14, 0x32, 0x3b, 0x96, 0xd9, 0x4a, 0xa1, 0x0d, 0xe6, 0x7a, 0x1e, 0x09, 0xed, 0x71, 0x18, 0xb0, 0x00, 0x6d, 0x0a, 0x9b, 0xad, 0x6d,
0x66, 0x6e, 0x4b, 0x8d, 0xc1, 0xa5, 0x13, 0x32, 0xf5, 0x55, 0x68, 0xb3, 0x3e, 0xfd, 0x1e, 0xf8, 0xb6, 0xb2, 0x99, 0xdb, 0xf2, 0xc6, 0xe0, 0xd2, 0x09, 0x99, 0xfa, 0x2a, 0xb4, 0x59, 0x9b, 0x3e,
0x23, 0xf7, 0x7d, 0x24, 0x50, 0x26, 0x42, 0xe2, 0x11, 0x87, 0x92, 0xf8, 0x77, 0x46, 0x29, 0x96, 0x0f, 0xfc, 0x91, 0xfb, 0x3e, 0x32, 0x28, 0x17, 0x21, 0xf1, 0x88, 0x43, 0x89, 0xfe, 0x9d, 0xb9,
0xb9, 0xfe, 0x28, 0x50, 0x02, 0xeb, 0xbb, 0x06, 0xff, 0x74, 0x5c, 0xca, 0xb0, 0x12, 0x51, 0x4c, 0xa4, 0x6d, 0xae, 0x3f, 0x0a, 0x94, 0xc1, 0xfa, 0x6e, 0xc0, 0x3f, 0x1d, 0x97, 0x32, 0xac, 0x4c,
0x3e, 0x4d, 0x08, 0x65, 0x68, 0x13, 0x96, 0x3d, 0xf7, 0xca, 0x65, 0x86, 0x76, 0x57, 0xdb, 0x29, 0x14, 0x93, 0x4f, 0x13, 0x42, 0x19, 0xda, 0x84, 0x65, 0xcf, 0xbd, 0x72, 0x59, 0xdd, 0xb8, 0x6b,
0x62, 0x75, 0x41, 0xdb, 0xa0, 0x07, 0xa3, 0x11, 0x25, 0xcc, 0x28, 0xf0, 0xe7, 0x2a, 0x8e, 0x6e, 0xec, 0xe4, 0xb1, 0xda, 0xa0, 0x6d, 0x28, 0x06, 0xa3, 0x11, 0x25, 0xac, 0x9e, 0xe3, 0xc7, 0x15,
0xe8, 0x05, 0x94, 0x69, 0x10, 0xb2, 0xfe, 0xc5, 0x8d, 0x51, 0xe4, 0x82, 0xf5, 0xe6, 0x7d, 0x3b, 0x1c, 0xed, 0xd0, 0x0b, 0x28, 0xd1, 0x20, 0x64, 0xfd, 0x8b, 0x9b, 0x7a, 0x9e, 0x1b, 0xd6, 0x9b,
0x2d, 0x26, 0x5b, 0x58, 0xea, 0x71, 0xa0, 0x2d, 0x3e, 0xfb, 0x37, 0x58, 0xa7, 0xf2, 0x57, 0xf0, 0xf7, 0xed, 0xa4, 0x9c, 0x6c, 0xe1, 0xa9, 0xc7, 0x81, 0xb6, 0xf8, 0xec, 0xdf, 0xe0, 0x22, 0x95,
0x8e, 0x5c, 0x8f, 0x91, 0xd0, 0x28, 0x29, 0x5e, 0x75, 0x43, 0x47, 0x00, 0x92, 0x37, 0x08, 0x87, 0xbf, 0x82, 0x77, 0xe4, 0x7a, 0x8c, 0x84, 0xf5, 0x82, 0xe2, 0x55, 0x3b, 0x74, 0x04, 0x20, 0x79,
0x5c, 0xb6, 0x2c, 0xa9, 0x77, 0x72, 0x50, 0x9f, 0x09, 0x3c, 0xae, 0xd2, 0xf8, 0x68, 0xbd, 0x83, 0x83, 0x70, 0xc8, 0x6d, 0xcb, 0x92, 0x7a, 0x27, 0x03, 0xf5, 0x99, 0xc0, 0xe3, 0x0a, 0xd5, 0x4b,
0x4a, 0x0c, 0xb0, 0x9a, 0xa0, 0x2b, 0xf3, 0x68, 0x05, 0xca, 0xe7, 0xdd, 0xd3, 0xee, 0xd9, 0x9b, 0xeb, 0x1d, 0x94, 0x35, 0xc0, 0x6a, 0x42, 0x51, 0xb9, 0x47, 0x2b, 0x50, 0x3a, 0xef, 0x9e, 0x76,
0x6e, 0x6d, 0x09, 0x55, 0xa0, 0xd4, 0x6d, 0xbd, 0x6c, 0xd7, 0x34, 0xb4, 0x01, 0x6b, 0x9d, 0x56, 0xcf, 0xde, 0x74, 0xab, 0x4b, 0xa8, 0x0c, 0x85, 0x6e, 0xeb, 0x65, 0xbb, 0x6a, 0xa0, 0x0d, 0x58,
0xef, 0x55, 0x1f, 0xb7, 0x3b, 0xed, 0x56, 0xaf, 0x7d, 0x58, 0x2b, 0x58, 0x77, 0xa0, 0x9a, 0xf0, 0xeb, 0xb4, 0x7a, 0xaf, 0xfa, 0xb8, 0xdd, 0x69, 0xb7, 0x7a, 0xed, 0xc3, 0x6a, 0xce, 0xba, 0x03,
0xa2, 0x32, 0x14, 0x5b, 0xbd, 0x03, 0xa5, 0x72, 0xd8, 0xe6, 0x27, 0xcd, 0xfa, 0xa2, 0xc1, 0xe6, 0x95, 0x98, 0x17, 0x95, 0x20, 0xdf, 0xea, 0x1d, 0xa8, 0x2b, 0x87, 0x6d, 0xbe, 0x32, 0xac, 0x2f,
0x6c, 0x1a, 0xe9, 0x38, 0xf0, 0x29, 0x11, 0x79, 0x1c, 0x04, 0x13, 0x3f, 0xc9, 0xa3, 0xbc, 0x20, 0x06, 0x6c, 0xce, 0xca, 0x48, 0xc7, 0x81, 0x4f, 0x89, 0xd0, 0x71, 0x10, 0x4c, 0xfc, 0x58, 0x47,
0x04, 0x25, 0x9f, 0x7c, 0x8e, 0xb3, 0x28, 0xcf, 0x02, 0xc9, 0x02, 0xe6, 0x78, 0x32, 0x83, 0x1c, 0xb9, 0x41, 0x08, 0x0a, 0x3e, 0xf9, 0xac, 0x55, 0x94, 0x6b, 0x81, 0x64, 0x01, 0x73, 0x3c, 0xa9,
0x29, 0x2f, 0xe8, 0x19, 0x54, 0xa2, 0xaa, 0x51, 0x9e, 0x9b, 0xe2, 0xce, 0x4a, 0x73, 0x4b, 0xc5, 0x20, 0x47, 0xca, 0x0d, 0x7a, 0x06, 0xe5, 0xa8, 0x6a, 0x94, 0x6b, 0x93, 0xdf, 0x59, 0x69, 0x6e,
0x1f, 0xd7, 0x37, 0xb2, 0x88, 0x13, 0x98, 0xb5, 0x07, 0xf5, 0x23, 0x12, 0x7b, 0xd2, 0x63, 0x0e, 0xa9, 0xfc, 0x75, 0x7d, 0x23, 0x8f, 0x38, 0x86, 0x59, 0x7b, 0x50, 0x3b, 0x22, 0x3a, 0x92, 0x1e,
0x9b, 0x24, 0x55, 0x15, 0x76, 0x9d, 0x2b, 0x22, 0x9d, 0x11, 0x76, 0xf9, 0xd9, 0x7a, 0x0d, 0xc6, 0x73, 0xd8, 0x24, 0xae, 0xaa, 0xf0, 0xeb, 0x5c, 0x11, 0x19, 0x8c, 0xf0, 0xcb, 0xd7, 0xd6, 0x6b,
0x22, 0x3c, 0xf2, 0x3e, 0x05, 0x8f, 0x1e, 0x40, 0x49, 0xf4, 0x8f, 0xf4, 0x7d, 0xa5, 0x89, 0x66, 0xa8, 0x2f, 0xc2, 0xa3, 0xe8, 0x13, 0xf0, 0xe8, 0x01, 0x14, 0x44, 0xff, 0xc8, 0xd8, 0x57, 0x9a,
0xbd, 0x39, 0xe1, 0x12, 0x2c, 0xe5, 0x96, 0x3d, 0xcd, 0x7b, 0x10, 0xf8, 0x8c, 0xf8, 0xec, 0x77, 0x68, 0x36, 0x9a, 0x13, 0x6e, 0xc1, 0xd2, 0x6e, 0xd9, 0xd3, 0xbc, 0x07, 0x81, 0xcf, 0x88, 0xcf,
0x7e, 0x74, 0xe0, 0xdf, 0x14, 0x7c, 0xe4, 0x48, 0x03, 0xca, 0x91, 0x09, 0xa9, 0x93, 0x99, 0x85, 0x7e, 0x17, 0x47, 0x07, 0xfe, 0x4d, 0xc0, 0x47, 0x81, 0x34, 0xa0, 0x14, 0xb9, 0x90, 0x77, 0x52,
0x18, 0x65, 0x7d, 0xe5, 0x05, 0x39, 0x1f, 0x0f, 0x1d, 0x46, 0x62, 0x51, 0xb6, 0x69, 0xf4, 0x90, 0x55, 0xd0, 0x28, 0xeb, 0x1b, 0x2f, 0xc8, 0xf9, 0x78, 0xe8, 0x30, 0xa2, 0x4d, 0xe9, 0xae, 0xd1,
0x17, 0x49, 0xcc, 0x53, 0x14, 0xd3, 0x86, 0xe2, 0x56, 0x43, 0x77, 0x20, 0xbe, 0x58, 0xc9, 0xd1, 0x43, 0x5e, 0x24, 0xf1, 0x9e, 0xa2, 0x9c, 0x36, 0x14, 0xb7, 0x7a, 0x74, 0x07, 0xe2, 0x8b, 0x95,
0x2e, 0xe8, 0xd7, 0x8e, 0xc7, 0x79, 0x64, 0x91, 0x92, 0xe8, 0x23, 0xa4, 0x1c, 0x46, 0x1c, 0x21, 0x1d, 0xed, 0x42, 0xf1, 0xda, 0xf1, 0x38, 0x8f, 0x2c, 0x52, 0x9c, 0x7d, 0x84, 0x94, 0x8f, 0x11,
0x50, 0x1d, 0xca, 0xc3, 0xf0, 0xa6, 0x1f, 0x4e, 0x7c, 0xd9, 0xd4, 0x15, 0xac, 0xf3, 0x2b, 0x9e, 0x47, 0x08, 0x54, 0x83, 0xd2, 0x30, 0xbc, 0xe9, 0x87, 0x13, 0x5f, 0x36, 0x75, 0x19, 0x17, 0xf9,
0xf8, 0xd6, 0x31, 0x6c, 0xcd, 0x79, 0x76, 0xdb, 0x20, 0x7f, 0x68, 0xb0, 0x75, 0xe2, 0x53, 0xde, 0x16, 0x4f, 0x7c, 0x74, 0x0f, 0xd6, 0x86, 0x2e, 0x75, 0x2e, 0x3c, 0xd2, 0xbf, 0x0c, 0x82, 0x8f,
0x27, 0xde, 0x5c, 0x94, 0x49, 0x44, 0x5a, 0xee, 0x88, 0x0a, 0x7f, 0x13, 0x51, 0x71, 0x3a, 0xa2, 0x54, 0xf6, 0x75, 0x19, 0xaf, 0x46, 0x87, 0xc7, 0xe2, 0xcc, 0x3a, 0x86, 0xad, 0xb9, 0xf0, 0x6f,
0x24, 0xa7, 0xa5, 0xa9, 0x9c, 0xde, 0x83, 0xb5, 0xa1, 0x4b, 0x9d, 0x0b, 0x8f, 0xf4, 0x2f, 0x83, 0xab, 0xc4, 0x0f, 0x03, 0xb6, 0x4e, 0x7c, 0xca, 0x9b, 0xc9, 0x9b, 0x93, 0x22, 0x4e, 0xdb, 0xc8,
0xe0, 0x23, 0x95, 0xd3, 0x5b, 0xc1, 0xab, 0xd1, 0xe3, 0xb1, 0x78, 0x43, 0xff, 0x41, 0x55, 0x80, 0x9c, 0x76, 0xee, 0x6f, 0xd2, 0xce, 0xcf, 0xa4, 0xad, 0x85, 0x2f, 0x4c, 0x09, 0x9f, 0x45, 0x0a,
0xe9, 0xd8, 0x19, 0x10, 0x43, 0x97, 0xda, 0xbf, 0x1e, 0xd0, 0xff, 0x00, 0x21, 0x99, 0x50, 0xd2, 0xf4, 0x1f, 0x54, 0x04, 0x98, 0x8e, 0x9d, 0x01, 0xa9, 0x17, 0xe5, 0xed, 0x5f, 0x07, 0xe8, 0x7f,
0x97, 0xe4, 0x65, 0xa9, 0x5f, 0x95, 0x2f, 0x5d, 0xd1, 0x30, 0x27, 0xb0, 0x3d, 0x1f, 0xfc, 0x6d, 0x80, 0x90, 0x4c, 0x28, 0xe9, 0x4b, 0xf2, 0x92, 0xbc, 0x5f, 0x91, 0x27, 0x5d, 0xd1, 0x55, 0x27,
0x13, 0x89, 0xa1, 0x7e, 0xee, 0xbb, 0xa9, 0x99, 0x4c, 0xeb, 0x97, 0x85, 0xd8, 0x0a, 0x8b, 0xb1, 0xb0, 0x3d, 0x9f, 0xfc, 0x6d, 0x85, 0xc4, 0x50, 0x3b, 0xf7, 0xdd, 0x44, 0x25, 0x93, 0x9a, 0x6a,
0x59, 0xa7, 0x60, 0x2c, 0x72, 0xde, 0xd2, 0xc1, 0xe6, 0xb7, 0x65, 0x58, 0x8f, 0x47, 0x54, 0x2d, 0x21, 0xb7, 0x5c, 0x42, 0x99, 0x4f, 0xa1, 0xbe, 0xc8, 0x79, 0xcb, 0x00, 0x9b, 0x5f, 0x97, 0x61,
0x3e, 0xe4, 0xc2, 0xea, 0xf4, 0xc6, 0x41, 0x8f, 0xb2, 0xf7, 0xe2, 0xdc, 0x72, 0x37, 0x77, 0xf3, 0x5d, 0xbf, 0x63, 0x35, 0x1d, 0x91, 0x0b, 0xab, 0xd3, 0x63, 0x09, 0x3d, 0x4a, 0x1f, 0x9e, 0x73,
0x40, 0x95, 0xab, 0xd6, 0xd2, 0x53, 0x0d, 0x51, 0xa8, 0xcd, 0xaf, 0x08, 0xb4, 0x97, 0xce, 0x91, 0x7f, 0x01, 0xcc, 0xdd, 0x2c, 0x50, 0x15, 0xaa, 0xb5, 0xf4, 0xd4, 0x40, 0x14, 0xaa, 0xf3, 0x73,
0xb1, 0x79, 0x4c, 0x3b, 0x2f, 0x3c, 0x36, 0x8b, 0xae, 0x61, 0x63, 0x61, 0x1f, 0xa0, 0x3f, 0xd2, 0x04, 0xed, 0x25, 0x73, 0xa4, 0x8c, 0x27, 0xd3, 0xce, 0x0a, 0xd7, 0x6e, 0xd1, 0x35, 0x6c, 0x2c,
0xcc, 0x2e, 0x1a, 0xb3, 0x91, 0x1b, 0x9f, 0xd8, 0xfd, 0x00, 0x6b, 0x33, 0xe3, 0x89, 0x32, 0xb2, 0x0c, 0x0d, 0xf4, 0x47, 0x9a, 0xd9, 0x69, 0x64, 0x36, 0x32, 0xe3, 0x63, 0xbf, 0x1f, 0x60, 0x6d,
0x95, 0xb6, 0x5d, 0xcc, 0xc7, 0xb9, 0xb0, 0x89, 0xad, 0x2b, 0x58, 0x9f, 0x6d, 0x61, 0x94, 0x41, 0xe6, 0x79, 0xa2, 0x14, 0xb5, 0x92, 0x46, 0x90, 0xf9, 0x38, 0x13, 0x36, 0xf6, 0x75, 0x05, 0xeb,
0x90, 0x3a, 0xe5, 0xe6, 0x93, 0x7c, 0xe0, 0xc4, 0x1c, 0xaf, 0xe3, 0x7c, 0x4b, 0x66, 0xd5, 0x31, 0xb3, 0x2d, 0x8c, 0x52, 0x08, 0x12, 0x5f, 0xb9, 0xf9, 0x24, 0x1b, 0x38, 0x76, 0xc7, 0xeb, 0x38,
0x63, 0x1c, 0xb2, 0xea, 0x98, 0xd5, 0xe9, 0xd6, 0xd2, 0x3e, 0xbc, 0xad, 0xc4, 0xe8, 0x0b, 0x5d, 0xdf, 0x92, 0x69, 0x75, 0x4c, 0x79, 0x0e, 0x69, 0x75, 0x4c, 0xeb, 0x74, 0x6b, 0x69, 0x1f, 0xde,
0xfe, 0xe9, 0x78, 0xfe, 0x33, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x56, 0x6f, 0xa5, 0x0e, 0x09, 0x00, 0x96, 0x35, 0xfa, 0xa2, 0x28, 0xff, 0x33, 0x79, 0xfe, 0x33, 0x00, 0x00, 0xff, 0xff, 0x97, 0xd7,
0x00, 0x36, 0xd6, 0x33, 0x09, 0x00, 0x00,
} }

@ -98,8 +98,7 @@ func LoadChartRepository(dir, url string) (*ChartRepository, error) {
return nil return nil
} }
r.IndexFile = i r.IndexFile = i
} else { } else if strings.HasSuffix(f.Name(), ".tgz") {
// TODO: check for tgz extension
r.ChartPaths = append(r.ChartPaths, path) r.ChartPaths = append(r.ChartPaths, path)
} }
} }

Loading…
Cancel
Save