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/
.vimrc
_dist/
_proto/*.pb.go
bin/
rootfs/tiller
vendor/
_proto/*.pb.go
.vimrc
.DS_Store

@ -162,6 +162,9 @@ message UpdateReleaseRequest {
hapi.chart.Config values = 3;
// dry_run, if true, will run through the release logic, but neither create
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.

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

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

@ -130,11 +130,10 @@ func (i *inspectCmd) run() error {
fmt.Fprintln(i.out, string(cf))
}
if i.output == both {
fmt.Fprintln(i.out, "---")
}
if i.output == valuesOnly || i.output == both {
if (i.output == valuesOnly || i.output == both) && chrt.Values != nil {
if i.output == both {
fmt.Fprintln(i.out, "---")
}
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)
}
}
// 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
import (
"bytes"
"fmt"
"io"
"io/ioutil"
@ -24,6 +25,9 @@ import (
"path/filepath"
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
@ -59,10 +63,11 @@ type installCmd struct {
chartPath string
dryRun bool
disableHooks bool
reuseName bool
replace bool
out io.Writer
client helm.Interface
values *values
nameTemplate string
}
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.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.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.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
return cmd
}
@ -113,13 +119,23 @@ func (i *installCmd) run() error {
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(
i.chartPath,
i.namespace,
helm.ValueOverrides(rawVals),
helm.ReleaseName(i.name),
helm.InstallDryRun(i.dryRun),
helm.InstallReuseName(i.reuseName),
helm.InstallReuseName(i.replace),
helm.InstallDisableHooks(i.disableHooks))
if err != nil {
return prettyError(err)
@ -249,3 +265,16 @@ func locateChartPath(name string) (string, error) {
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 (
"fmt"
"io"
"regexp"
"strings"
"testing"
@ -59,12 +60,20 @@ func TestInstall(t *testing.T) {
},
// Install, re-use name
{
name: "install and reuse name",
name: "install and replace release",
args: []string{"testdata/testcharts/alpine"},
flags: strings.Split("--name aeneas --reuse-name", " "),
flags: strings.Split("--name aeneas --replace", " "),
expected: "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 {
@ -113,3 +122,78 @@ sailor: sinbad
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,
}
var flagStrict bool
func init() {
lintCommand.Flags().BoolVarP(&flagStrict, "strict", "", false, "fail on lint warnings")
RootCommand.AddCommand(lintCommand)
}
@ -59,6 +62,13 @@ func lintCmd(cmd *cobra.Command, args []string) error {
paths = args
}
var lowestTolerance int
if flagStrict {
lowestTolerance = support.WarningSev
} else {
lowestTolerance = support.ErrorSev
}
var total int
var failures int
for _, path := range paths {
@ -77,7 +87,7 @@ func lintCmd(cmd *cobra.Command, args []string) error {
}
total = total + 1
if linter.HighestSeverity >= support.ErrorSev {
if linter.HighestSeverity >= lowestTolerance {
failures = failures + 1
}
}

@ -17,17 +17,19 @@ limitations under the License.
package main
import (
"os"
"path/filepath"
"github.com/spf13/cobra"
"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.`
//TODO: add repoPath flag to be passed in in case you want
// to serve charts from a different local dir
var serveDesc = `This command starts a local chart repository server that serves charts from a local directory.`
var repoPath string
func init() {
serveCmd.Flags().StringVar(&repoPath, "repo-path", localRepoDirectory(), "The local directory path from which to serve charts.")
RootCommand.AddCommand(serveCmd)
}
@ -35,9 +37,19 @@ var serveCmd = &cobra.Command{
Use: "serve",
Short: "start a local http web server",
Long: serveDesc,
Run: serve,
RunE: serve,
}
func serve(cmd *cobra.Command, args []string) {
repo.StartLocalRepo(localRepoDirectory())
func serve(cmd *cobra.Command, args []string) error {
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 {
release string
chart string
out io.Writer
client helm.Interface
dryRun bool
valuesFile string
release string
chart string
out io.Writer
client helm.Interface
dryRun bool
disableHooks bool
valuesFile string
}
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.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks")
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 {
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

@ -52,18 +52,22 @@ func TestUpgradeCmd(t *testing.T) {
Description: "A Helm chart for Kubernetes",
Version: "0.1.2",
}
chartPath, err = chartutil.Create(cfile, tmpChart)
if err != nil {
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{
{
name: "upgrade a release",
args: []string{"funny-bunny", chartPath},
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
// 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
@ -145,6 +154,12 @@ func (p *PrintingKubeClient) WatchUntilReady(ns string, r io.Reader) error {
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.
//
// 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 {
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 {
return nil
}

@ -24,7 +24,6 @@ import (
"regexp"
"sort"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml"
"github.com/technosophos/moniker"
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) {
rel, err := s.prepareUpdate(req)
currentRelease, updatedRelease, err := s.prepareUpdate(req)
if err != nil {
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.
func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, error) {
// prepareUpdate builds an updated release for an update operation.
func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) {
if req.Name == "" {
return nil, errMissingRelease
return nil, nil, errMissingRelease
}
if req.Chart == nil {
return nil, errMissingChart
return nil, nil, errMissingChart
}
// 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 {
return nil, err
return nil, nil, err
}
//validate chart name is same as previous release
givenChart := req.Chart.Metadata.Name
releasedChart := rel.Chart.Metadata.Name
if givenChart != releasedChart {
return nil, fmt.Errorf("Given chart, %s, does not match chart originally released, %s", givenChart, releasedChart)
ts := timeconv.Now()
options := chartutil.ReleaseOptions{
Name: req.Name,
Time: ts,
Namespace: currentRelease.Namespace,
}
// validate new chart version is higher than old
givenChartVersion := req.Chart.Metadata.Version
releasedChartVersion := rel.Chart.Metadata.Version
c, err := semver.NewConstraint("> " + releasedChartVersion)
valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options)
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 {
return 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)
return nil, nil, err
}
// Store an updated release.
updatedRelease := &release.Release{
Name: req.Name,
Chart: req.Chart,
Config: req.Values,
Version: rel.Version + 1,
Name: req.Name,
Namespace: currentRelease.Namespace,
Chart: req.Chart,
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) {
@ -240,10 +281,12 @@ func (s *releaseServer) uniqName(start string, reuse bool) (string, error) {
if start != "" {
if rel, err := s.env.Releases.Get(start); err == driver.ErrReleaseNotFound {
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.
log.Printf("reusing name %q", start)
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)
@ -306,12 +349,36 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return nil, err
}
renderer := s.engine(req.Chart)
files, err := renderer.Render(req.Chart, valuesToRender)
hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender)
if err != nil {
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,
// as partials are not used after renderer.Render. Empty manifests are also
// removed here.
@ -319,7 +386,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
if err != nil {
// By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes.
return nil, err
return nil, nil, err
}
// Aggregate all valid manifests into one big doc.
@ -329,22 +396,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
b.WriteString(file)
}
// 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: b.String(),
Hooks: hooks,
Version: 1,
}
return rel, nil
return hooks, b, nil
}
// validateYAML checks to see if YAML is well-formed.

@ -45,6 +45,16 @@ data:
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 {
return &releaseServer{
env: mockEnvironment(),
@ -80,8 +90,9 @@ func releaseStub() *release.Release {
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
},
Chart: chartStub(),
Config: &chart.Config{Raw: `name = "value"`},
Chart: chartStub(),
Config: &chart.Config{Raw: `name = "value"`},
Version: 1,
Hooks: []*release.Hook{
{
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) {
c := context.Background()
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
building a Docker image from the files in `rootfs`.
The `scripts/` directory contains a number of utility scripts, including
`local-cluster.sh`, which can start a full Kubernetes instance inside of
a Docker container.
The `scripts/` directory contains a number of utility scripts. Most of these
are used by the CI/CD pipeline.
Go dependencies are managed with
[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
obtainable as `Chart.Version` and the maintainers are in
`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
be accessible inside of the `Chart` object. Thus, Chart.yaml cannot be

@ -66,11 +66,31 @@ GCR registry.
## Running a Local Cluster
You can run tests locally using the `scripts/local-cluster.sh` script to
start Kubernetes inside of a Docker container. For OS X, you will need
to be running `docker-machine`.
For development, we highly recommend using the
[Kubernetes Minikube](https://github.com/kubernetes/minikube)
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

154
glide.lock generated

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

@ -22,22 +22,32 @@ import:
- package: google.golang.org/grpc
version: dec33edc378cf4971a2741cfd86ed70a644d6ba3
- package: k8s.io/kubernetes
version: v1.3.0
version: ~1.3
subpackages:
- pkg/api
- pkg/api/meta
- pkg/api/error
- pkg/api/unversioned
- pkg/apimachinery/registered
- pkg/client/restclient
- pkg/client/unversioned
- pkg/apis/batch
- pkg/client/unversioned/clientcmd
- pkg/client/unversioned/fake
- pkg/client/unversioned/portforward
- pkg/client/unversioned/remotecommand
- pkg/kubectl
- pkg/kubectl/cmd/util
- pkg/kubectl/resource
- pkg/labels
- pkg/runtime
- pkg/watch
- pkg/util/strategicpatch
- pkg/util/yaml
- package: github.com/gosuri/uitable
- package: speter.net/go/exp/math/dec/inf
version: ^0.9.0
repo: https://github.com/go-inf/inf.git
vcs: git
- 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
# negation (prefixed with !). Only one pattern per line.
.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.

@ -32,11 +32,14 @@ func NewFiles(from []*any.Any) 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
// an empty []byte.
func (f Files) Get(name string) []byte {
func (f Files) GetBytes(name string) []byte {
v, ok := f[name]
if !ok {
return []byte{}
@ -44,10 +47,12 @@ func (f Files) Get(name string) []byte {
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
// for '{{.Files.Get "foo" | printf "%s"}}'.
func (f Files) GetString(name string) string {
return string(f.Get(name))
// {{.Files.Get "foo"}}
func (f Files) Get(name string) string {
return string(f.GetBytes(name))
}

@ -43,10 +43,10 @@ func TestNewFiles(t *testing.T) {
}
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)
}
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)
}
}

@ -218,6 +218,11 @@ func LoadDir(dir string) (*chart.Chart, error) {
return err
}
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
}

@ -49,8 +49,9 @@ func verifyChart(t *testing.T, c *chart.Chart) {
t.Errorf("Expected 1 template, got %d", len(c.Templates))
}
if len(c.Files) != 5 {
t.Errorf("Expected 5 extra files, got %d", len(c.Files))
numfiles := 6
if len(c.Files) != numfiles {
t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files))
for _, n := range c.Files {
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.
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.
const InstallYAML = `
---
apiVersion: v1
kind: ReplicationController
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: helm
name: tiller
name: tiller-rc
name: tiller-deploy
namespace: {{ .Namespace }}
spec:
replicas: 1
selector:
app: helm
name: tiller
template:
metadata:
labels:

@ -88,6 +88,28 @@ type renderable struct {
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.
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
@ -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.
t.Option("missingkey=zero")
}
funcMap := e.alterFuncMap(t)
files := []string{}
for fname, r := range tpls {
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 {
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"},
Templates: []*chart.Template{
{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: ``},
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.
func UpgradeDryRun(dry bool) UpdateOption {
return func(opts *options) {
@ -237,9 +244,15 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient,
// Executes tiller.UpdateRelease RPC.
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.

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

@ -20,15 +20,21 @@ import (
"fmt"
"io"
"log"
"reflect"
"time"
"k8s.io/kubernetes/pkg/api"
"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/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"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"
)
@ -57,6 +63,77 @@ func (c *Client) Create(namespace string, reader io.Reader) error {
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
//
// Namespace will set the namespace
@ -136,6 +213,51 @@ func createResource(info *resource.Info) error {
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 {
w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
if err != nil {
@ -213,3 +335,37 @@ func (c *Client) ensureNamespace(namespace string) error {
}
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
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"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/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) {
tests := []struct {
name string
@ -214,3 +254,53 @@ spec:
ports:
- 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
// 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().
Resource("pods").
Namespace(namespace).

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

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

Loading…
Cancel
Save