Commit ae2d6c50 authored by fibonacci1729's avatar fibonacci1729

Merge branch 'master' into feat/storage-memory

parents 9d3a1ed2 ae4ff5cd
.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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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 { ...@@ -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) { ...@@ -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. ...@@ -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 ( ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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 ...@@ -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) { ...@@ -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 ...@@ -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{ ...@@ -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 { ...@@ -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 { ...@@ -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. ...@@ -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{ ...@@ -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
} }
description: Deploy a basic Alpine Linux pod
home: https://k8s.io/helm
name: novals
sources:
- https://github.com/kubernetes/helm
version: 0.2.0
#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`.
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. ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ( ...@@ -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 ...@@ -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) { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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: ...@@ -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 { ...@@ -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) { ...@@ -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 ...@@ -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_. ...@@ -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. ...@@ -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
......
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: ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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. ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)
} }
......
...@@ -9,4 +9,4 @@ tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 { ...@@ -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, ...@@ -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) { ...@@ -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 { ...@@ -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 ( ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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. ...@@ -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: ...@@ -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 ...@@ -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).
......
This diff is collapsed.
...@@ -98,8 +98,7 @@ func LoadChartRepository(dir, url string) (*ChartRepository, error) { ...@@ -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)
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment