Merge pull request #1 from kubernetes/master

Update
pull/3064/head
Neil Moore 8 years ago committed by GitHub
commit 86f34c67c1

@ -23,3 +23,11 @@ jobs:
- deploy: - deploy:
name: deploy name: deploy
command: .circleci/deploy.sh command: .circleci/deploy.sh
workflows:
version: 2
build:
jobs:
- build:
filters:
tags:
only: /.*/

@ -7,7 +7,7 @@
Helm is a tool for managing Kubernetes charts. Charts are packages of Helm is a tool for managing Kubernetes charts. Charts are packages of
pre-configured Kubernetes resources. pre-configured Kubernetes resources.
Use Helm to... Use Helm to:
- Find and use [popular software packaged as Kubernetes charts](https://github.com/kubernetes/charts) - Find and use [popular software packaged as Kubernetes charts](https://github.com/kubernetes/charts)
- Share your own applications as Kubernetes charts - Share your own applications as Kubernetes charts
@ -34,10 +34,10 @@ Think of it like apt/yum/homebrew for Kubernetes.
Binary downloads of the Helm client can be found at the following links: Binary downloads of the Helm client can be found at the following links:
- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-darwin-amd64.tar.gz) - [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-darwin-amd64.tar.gz)
- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-amd64.tar.gz) - [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz)
- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-386.tar.gz) - [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-386.tar.gz)
- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-windows-amd64.tar.gz) - [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-windows-amd64.tar.gz)
Unpack the `helm` binary and add it to your PATH and you are good to go! Unpack the `helm` binary and add it to your PATH and you are good to go!
macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`. macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`.
@ -53,7 +53,7 @@ Get started with the [Quick Start guide](https://docs.helm.sh/using_helm/#quicks
## Roadmap ## Roadmap
The [Helm roadmap is currently located on the wiki](https://github.com/kubernetes/helm/wiki/Roadmap). The [Helm roadmap uses Github milestones](https://github.com/kubernetes/helm/milestones) to track the progress of the project.
## Community, discussion, contribution, and support ## Community, discussion, contribution, and support

@ -96,7 +96,7 @@ func (d *dependencyUpdateCmd) run() error {
Getters: getter.All(settings), Getters: getter.All(settings),
} }
if d.verify { if d.verify {
man.Verify = downloader.VerifyIfPossible man.Verify = downloader.VerifyAlways
} }
if settings.Debug { if settings.Debug {
man.Debug = true man.Debug = true

@ -79,8 +79,8 @@ func newFetchCmd(out io.Writer) *cobra.Command {
} }
if fch.version == "" && fch.devel { if fch.version == "" && fch.devel {
debug("setting version to >0.0.0-a") debug("setting version to >0.0.0-0")
fch.version = ">0.0.0-a" fch.version = ">0.0.0-0"
} }
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
@ -105,7 +105,7 @@ func newFetchCmd(out io.Writer) *cobra.Command {
f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
return cmd return cmd
} }

@ -239,16 +239,16 @@ func getKubeClient(context string) (*rest.Config, kubernetes.Interface, error) {
// getInternalKubeClient creates a Kubernetes config and an "internal" client for a given kubeconfig context. // getInternalKubeClient creates a Kubernetes config and an "internal" client for a given kubeconfig context.
// //
// Prefer the similar getKubeClient if you don't need to use such an internal client. // Prefer the similar getKubeClient if you don't need to use such an internal client.
func getInternalKubeClient(context string) (*rest.Config, internalclientset.Interface, error) { func getInternalKubeClient(context string) (internalclientset.Interface, error) {
config, err := configForContext(context) config, err := configForContext(context)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
client, err := internalclientset.NewForConfig(config) client, err := internalclientset.NewForConfig(config)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err) return nil, fmt.Errorf("could not get Kubernetes client: %s", err)
} }
return config, client, nil return client, nil
} }
// ensureHelmClient returns a new helm client impl. if h is not nil. // ensureHelmClient returns a new helm client impl. if h is not nil.

@ -17,6 +17,8 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -26,6 +28,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/helm/cmd/helm/installer" "k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
@ -120,6 +123,10 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.StringVar(&i.serviceAccount, "service-account", "", "name of service account") f.StringVar(&i.serviceAccount, "service-account", "", "name of service account")
f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)")
f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)")
f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)")
return cmd return cmd
} }
@ -160,7 +167,6 @@ func (i *initCmd) run() error {
i.opts.ServiceAccount = i.serviceAccount i.opts.ServiceAccount = i.serviceAccount
i.opts.MaxHistory = i.maxHistory i.opts.MaxHistory = i.maxHistory
if settings.Debug {
writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error {
w := i.out w := i.out
if !first { if !first {
@ -185,6 +191,42 @@ func (i *initCmd) run() error {
_, err := fmt.Fprintln(w, "...") _, err := fmt.Fprintln(w, "...")
return err return err
} }
if len(i.opts.Output) > 0 {
var body string
var err error
const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",`
if body, err = installer.DeploymentManifest(&i.opts); err != nil {
return err
}
switch i.opts.Output.String() {
case "json":
var out bytes.Buffer
jsonb, err := yaml.ToJSON([]byte(body))
if err != nil {
return err
}
buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1))
buf.WriteString(tm)
// Drop the opening object delimiter ('{').
buf.Write(jsonb[1:])
if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil {
return err
}
if _, err = i.out.Write(out.Bytes()); err != nil {
return err
}
return nil
case "yaml":
if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil {
return err
}
return nil
default:
return fmt.Errorf("unknown output format: %q", i.opts.Output)
}
}
if settings.Debug {
var body string var body string
var err error var err error

@ -27,14 +27,16 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
testcore "k8s.io/client-go/testing" testcore "k8s.io/client-go/testing"
"encoding/json"
"k8s.io/helm/cmd/helm/installer" "k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
@ -303,3 +305,53 @@ func TestInitCmd_tlsOptions(t *testing.T) {
} }
} }
} }
// TestInitCmd_output tests that init -o formats are unmarshal-able
func TestInitCmd_output(t *testing.T) {
// This is purely defensive in this case.
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
dbg := settings.Debug
settings.Debug = true
defer func() {
os.Remove(home)
settings.Debug = dbg
}()
fc := fake.NewSimpleClientset()
tests := []struct {
expectF func([]byte, interface{}) error
expectName string
}{
{
json.Unmarshal,
"json",
},
{
yaml.Unmarshal,
"yaml",
},
}
for _, s := range tests {
var buf bytes.Buffer
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
opts: installer.Options{Output: installer.OutputFormat(s.expectName)},
namespace: v1.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Fatal(err)
}
if got := len(fc.Actions()); got != 0 {
t.Errorf("expected no server calls, got %d", got)
}
d := &v1beta1.Deployment{}
if err = s.expectF(buf.Bytes(), &d); err != nil {
t.Errorf("error unmarshalling init %s output %s %s", s.expectName, err, buf.String())
}
}
}

@ -41,13 +41,14 @@ import (
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/strvals" "k8s.io/helm/pkg/strvals"
"net/url"
) )
const installDesc = ` const installDesc = `
This command installs a chart archive. This command installs a chart archive.
The install argument must be either a relative path to a chart directory or the The install argument must be a chart reference, a path to a packaged chart,
name of a chart in the current working directory. a path to an unpacked chart directory or a URL.
To override values in a chart, use either the '--values' flag and pass in a file To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line. or use the '--set' flag and pass configuration from the command line.
@ -159,8 +160,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
debug("Original chart version: %q", inst.version) debug("Original chart version: %q", inst.version)
if inst.version == "" && inst.devel { if inst.version == "" && inst.devel {
debug("setting version to >0.0.0-a") debug("setting version to >0.0.0-0")
inst.version = ">0.0.0-a" inst.version = ">0.0.0-0"
} }
cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring, cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring,
@ -175,7 +176,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)")
f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you")
f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.") f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
@ -192,7 +193,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
return cmd return cmd
} }
@ -316,8 +317,9 @@ func vals(valueFiles valueFiles, values []string) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" { if strings.TrimSpace(filePath) == "-" {
bytes, err = ioutil.ReadAll(os.Stdin) bytes, err = ioutil.ReadAll(os.Stdin)
} else { } else {
bytes, err = ioutil.ReadFile(filePath) bytes, err = readFile(filePath)
} }
if err != nil { if err != nil {
return []byte{}, err return []byte{}, err
} }
@ -469,3 +471,23 @@ func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error {
} }
return nil return nil
} }
//readFile load a file from the local directory or a remote file with a url.
func readFile(filePath string) ([]byte, error) {
u, _ := url.Parse(filePath)
p := getter.All(settings)
// FIXME: maybe someone handle other protocols like ftp.
getterConstructor, err := p.ByScheme(u.Scheme)
if err != nil {
return ioutil.ReadFile(filePath)
} else {
getter, err := getterConstructor(filePath, "", "", "")
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err
}
}

@ -19,16 +19,18 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strings"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
"k8s.io/client-go/pkg/api/v1" "k8s.io/helm/pkg/chartutil"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
) )
// Install uses Kubernetes client to install Tiller. // Install uses Kubernetes client to install Tiller.
@ -74,13 +76,17 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
// createDeployment creates the Tiller Deployment resource. // createDeployment creates the Tiller Deployment resource.
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj := deployment(opts) obj, err := deployment(opts)
_, err := client.Deployments(obj.Namespace).Create(obj) if err != nil {
return err
}
_, err = client.Deployments(obj.Namespace).Create(obj)
return err return err
} }
// deployment gets the deployment object that installs Tiller. // deployment gets the deployment object that installs Tiller.
func deployment(opts *Options) *v1beta1.Deployment { func deployment(opts *Options) (*v1beta1.Deployment, error) {
return generateDeployment(opts) return generateDeployment(opts)
} }
@ -99,7 +105,10 @@ func service(namespace string) *v1.Service {
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource. // resource.
func DeploymentManifest(opts *Options) (string, error) { func DeploymentManifest(opts *Options) (string, error) {
obj := deployment(opts) obj, err := deployment(opts)
if err != nil {
return "", err
}
buf, err := yaml.Marshal(obj) buf, err := yaml.Marshal(obj)
return string(buf), err return string(buf), err
} }
@ -117,8 +126,28 @@ func generateLabels(labels map[string]string) map[string]string {
return labels return labels
} }
func generateDeployment(opts *Options) *v1beta1.Deployment { // parseNodeSelectors parses a comma delimited list of key=values pairs into a map.
func parseNodeSelectorsInto(labels string, m map[string]string) error {
kv := strings.Split(labels, ",")
for _, v := range kv {
el := strings.Split(v, "=")
if len(el) == 2 {
m[el[0]] = el[1]
} else {
return fmt.Errorf("invalid nodeSelector label: %q", kv)
}
}
return nil
}
func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
labels := generateLabels(map[string]string{"name": "tiller"}) labels := generateLabels(map[string]string{"name": "tiller"})
nodeSelectors := map[string]string{}
if len(opts.NodeSelectors) > 0 {
err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors)
if err != nil {
return nil, err
}
}
d := &v1beta1.Deployment{ d := &v1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Namespace: opts.Namespace, Namespace: opts.Namespace,
@ -167,9 +196,7 @@ func generateDeployment(opts *Options) *v1beta1.Deployment {
}, },
}, },
HostNetwork: opts.EnableHostNetwork, HostNetwork: opts.EnableHostNetwork,
NodeSelector: map[string]string{ NodeSelector: nodeSelectors,
"beta.kubernetes.io/os": "linux",
},
}, },
}, },
}, },
@ -205,7 +232,40 @@ func generateDeployment(opts *Options) *v1beta1.Deployment {
}, },
}) })
} }
return d // if --override values were specified, ultimately convert values and deployment to maps,
// merge them and convert back to Deployment
if len(opts.Values) > 0 {
// base deployment struct
var dd v1beta1.Deployment
// get YAML from original deployment
dy, err := yaml.Marshal(d)
if err != nil {
return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err)
}
// convert deployment YAML to values
dv, err := chartutil.ReadValues(dy)
if err != nil {
return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err)
}
dm := dv.AsMap()
// merge --set values into our map
sm, err := opts.valuesMap(dm)
if err != nil {
return nil, fmt.Errorf("Error merging --set values into Deployment manifest")
}
finalY, err := yaml.Marshal(sm)
if err != nil {
return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err)
}
// convert merged values back into deployment
err = yaml.Unmarshal(finalY, &dd)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err)
}
d = &dd
}
return d, nil
} }
func generateService(namespace string) *v1.Service { func generateService(namespace string) *v1.Service {

@ -23,13 +23,14 @@ import (
"testing" "testing"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
testcore "k8s.io/client-go/testing" testcore "k8s.io/client-go/testing"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
@ -330,7 +331,7 @@ func TestInstall_canary(t *testing.T) {
func TestUpgrade(t *testing.T) { func TestUpgrade(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment := deployment(&Options{ existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace", ImageSpec: "imageToReplace",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
@ -371,7 +372,7 @@ func TestUpgrade(t *testing.T) {
func TestUpgrade_serviceNotFound(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(&Options{ existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace", ImageSpec: "imageToReplace",
UseCanary: false, UseCanary: false,
@ -419,3 +420,107 @@ func tlsTestFile(t *testing.T, path string) string {
} }
return path return path
} }
func TestDeploymentManifest_WithNodeSelectors(t *testing.T) {
tests := []struct {
opts Options
name string
expect map[string]interface{}
}{
{
Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller"},
"nodeSelector app=tiller",
map[string]interface{}{"app": "tiller"},
},
{
Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,helm=rocks"},
"nodeSelector app=tiller, helm=rocks",
map[string]interface{}{"app": "tiller", "helm": "rocks"},
},
// note: nodeSelector key and value are strings
{
Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,minCoolness=1"},
"nodeSelector app=tiller, helm=rocks",
map[string]interface{}{"app": "tiller", "minCoolness": "1"},
},
}
for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
// Verify that environment variables in Deployment reflect the use of TLS being enabled.
got := d.Spec.Template.Spec.NodeSelector
for k, v := range tt.expect {
if got[k] != v {
t.Errorf("%s: expected nodeSelector value %q, got %q", tt.name, tt.expect, got)
}
}
}
}
func TestDeploymentManifest_WithSetValues(t *testing.T) {
tests := []struct {
opts Options
name string
expectPath string
expect interface{}
}{
{
Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.nodeselector.app=tiller"}},
"setValues spec.template.spec.nodeSelector.app=tiller",
"spec.template.spec.nodeSelector.app",
"tiller",
},
{
Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.replicas=2"}},
"setValues spec.replicas=2",
"spec.replicas",
2,
},
{
Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.activedeadlineseconds=120"}},
"setValues spec.template.spec.activedeadlineseconds=120",
"spec.template.spec.activeDeadlineSeconds",
120,
},
}
for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
values, err := chartutil.ReadValues([]byte(o))
if err != nil {
t.Errorf("Error converting Deployment manifest to Values: %s", err)
}
// path value
pv, err := values.PathValue(tt.expectPath)
if err != nil {
t.Errorf("Error retrieving path value from Deployment Values: %s", err)
}
// convert our expected value to match the result type for comparison
ev := tt.expect
switch pvt := pv.(type) {
case float64:
floatType := reflect.TypeOf(float64(0))
v := reflect.ValueOf(ev)
v = reflect.Indirect(v)
if !v.Type().ConvertibleTo(floatType) {
t.Fatalf("Error converting expected value %v to float64", v.Type())
}
fv := v.Convert(floatType)
if fv.Float() != pvt {
t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv)
}
default:
if pv != tt.expect {
t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv)
}
}
}
}

@ -19,7 +19,8 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"fmt" "fmt"
"k8s.io/client-go/pkg/api/v1" "k8s.io/api/core/v1"
"k8s.io/helm/pkg/strvals"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
@ -76,6 +77,15 @@ type Options struct {
// //
// Less than or equal to zero means no limit. // Less than or equal to zero means no limit.
MaxHistory int MaxHistory int
// NodeSelectors determine which nodes Tiller can land on.
NodeSelectors string
// Output dumps the Tiller manifest in the specified format (e.g. JSON) but skips Helm/Tiller installation.
Output OutputFormat
// Set merges additional values into the Tiller Deployment manifest.
Values []string
} }
func (opts *Options) selectImage() string { func (opts *Options) selectImage() string {
@ -97,3 +107,42 @@ func (opts *Options) pullPolicy() v1.PullPolicy {
} }
func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS } func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS }
// valuesMap returns user set values in map format
func (opts *Options) valuesMap(m map[string]interface{}) (map[string]interface{}, error) {
for _, skv := range opts.Values {
if err := strvals.ParseInto(skv, m); err != nil {
return nil, err
}
}
return m, nil
}
// OutputFormat defines valid values for init output (json, yaml)
type OutputFormat string
// String returns the string value of the OutputFormat
func (f *OutputFormat) String() string {
return string(*f)
}
// Type returns the string value of the OutputFormat
func (f *OutputFormat) Type() string {
return "OutputFormat"
}
const (
fmtJSON OutputFormat = "json"
fmtYAML OutputFormat = "yaml"
)
// Set validates and sets the value of the OutputFormat
func (f *OutputFormat) Set(s string) error {
for _, of := range []OutputFormat{fmtJSON, fmtYAML} {
if s == string(of) {
*f = of
return nil
}
}
return fmt.Errorf("unknown output format %q", s)
}

@ -19,7 +19,7 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"

@ -86,7 +86,7 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
// runReset uninstalls tiller from Kubernetes Cluster and deletes local config // runReset uninstalls tiller from Kubernetes Cluster and deletes local config
func (d *resetCmd) run() error { func (d *resetCmd) run() error {
if d.kubeClient == nil { if d.kubeClient == nil {
_, c, err := getInternalKubeClient(settings.KubeContext) c, err := getInternalKubeClient(settings.KubeContext)
if err != nil { if err != nil {
return fmt.Errorf("could not get kubernetes client: %s", err) return fmt.Errorf("could not get kubernetes client: %s", err)
} }

@ -30,7 +30,6 @@ import (
"strings" "strings"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )
@ -147,6 +146,8 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result {
term = strings.ToLower(term) term = strings.ToLower(term)
buf := []*Result{} buf := []*Result{}
for k, v := range i.lines { for k, v := range i.lines {
k = strings.ToLower(k)
v = strings.ToLower(v)
res := strings.Index(v, term) res := strings.Index(v, term)
if score := i.calcScore(res, v); res != -1 && score < threshold { if score := i.calcScore(res, v); res != -1 && score < threshold {
parts := strings.Split(k, verSep) // Remove version, if it is there. parts := strings.Split(k, verSep) // Remove version, if it is there.
@ -231,5 +232,5 @@ func (s scoreSorter) Less(a, b int) bool {
func indstr(name string, ref *repo.ChartVersion) string { func indstr(name string, ref *repo.ChartVersion) string {
i := ref.Name + sep + name + "/" + ref.Name + sep + i := ref.Name + sep + name + "/" + ref.Name + sep +
ref.Description + sep + strings.Join(ref.Keywords, " ") ref.Description + sep + strings.Join(ref.Keywords, " ")
return strings.ToLower(i) return i
} }

@ -135,7 +135,7 @@ func TestAll(t *testing.T) {
func TestAddRepo_Sort(t *testing.T) { func TestAddRepo_Sort(t *testing.T) {
i := loadTestIndex(t, true) i := loadTestIndex(t, true)
sr, err := i.Search("testing/santa-maria", 100, false) sr, err := i.Search("TESTING/SANTA-MARIA", 100, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -202,6 +202,14 @@ func TestSearchByName(t *testing.T) {
{Name: "ztesting/pinta"}, {Name: "ztesting/pinta"},
}, },
}, },
{
name: "description upper search, two results",
query: "TWO",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
},
},
{ {
name: "nothing found", name: "nothing found",
query: "mayflower", query: "mayflower",
@ -209,7 +217,7 @@ func TestSearchByName(t *testing.T) {
}, },
{ {
name: "regexp, one result", name: "regexp, one result",
query: "th[ref]*", query: "Th[ref]*",
expect: []*Result{ expect: []*Result{
{Name: "testing/santa-maria"}, {Name: "testing/santa-maria"},
}, },

@ -98,8 +98,8 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
if upgrade.version == "" && upgrade.devel { if upgrade.version == "" && upgrade.devel {
debug("setting version to >0.0.0-a") debug("setting version to >0.0.0-0")
upgrade.version = ">0.0.0-a" upgrade.version = ">0.0.0-0"
} }
upgrade.release = args[0] upgrade.release = args[0]
@ -111,7 +111,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed") f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed")
@ -131,7 +131,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.MarkDeprecated("disable-hooks", "use --no-hooks instead") f.MarkDeprecated("disable-hooks", "use --no-hooks instead")

@ -57,6 +57,7 @@ const (
storageMemory = "memory" storageMemory = "memory"
storageConfigMap = "configmap" storageConfigMap = "configmap"
storageSecret = "secret"
probeAddr = ":44135" probeAddr = ":44135"
traceAddr = ":44136" traceAddr = ":44136"
@ -68,7 +69,7 @@ const (
var ( var (
grpcAddr = flag.String("listen", ":44134", "address:port to listen on") grpcAddr = flag.String("listen", ":44134", "address:port to listen on")
enableTracing = flag.Bool("trace", false, "enable rpc tracing") enableTracing = flag.Bool("trace", false, "enable rpc tracing")
store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap' or 'memory'") store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap', 'memory', or 'secret'")
remoteReleaseModules = flag.Bool("experimental-release", false, "enable experimental release modules") remoteReleaseModules = flag.Bool("experimental-release", false, "enable experimental release modules")
tlsEnable = flag.Bool("tls", tlsEnableEnvVarDefault(), "enable TLS") tlsEnable = flag.Bool("tls", tlsEnableEnvVarDefault(), "enable TLS")
tlsVerify = flag.Bool("tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate") tlsVerify = flag.Bool("tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate")
@ -117,6 +118,12 @@ func start() {
env.Releases = storage.Init(cfgmaps) env.Releases = storage.Init(cfgmaps)
env.Releases.Log = newLogger("storage").Printf env.Releases.Log = newLogger("storage").Printf
case storageSecret:
secrets := driver.NewSecrets(clientset.Core().Secrets(namespace()))
secrets.Log = newLogger("storage/driver").Printf
env.Releases = storage.Init(secrets)
env.Releases.Log = newLogger("storage").Printf
} }
if *maxHistory > 0 { if *maxHistory > 0 {

@ -36,6 +36,7 @@ Incorrect:
{{/* ... */}} {{/* ... */}}
{{ end -}} {{ end -}}
``` ```
It is highly recommended that new charts are created via `helm create` command as the template names are automatically defined as per this best practice.
## Formatting Templates ## Formatting Templates

@ -96,6 +96,34 @@ data:
food: "pizza" food: "pizza"
``` ```
Variables are not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block. Variables are normally not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block.
However, there is one variable that is always global - `$` - this
variable will always point to the root context. This can be very
useful when you are looping in a range need to know the chart's release
name.
An example illustrating this:
```yaml
{{- range .Values.tlsSecrets }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .name }}
labels:
# Many helm templates would use `.` below, but that will not work,
# however `$` will work here
app: {{ template "fullname" $ }}
# I cannot reference .Chart.Name, but I can do $.Chart.Name
chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"
release: "{{ $.Release.Name }}"
heritage: "{{ $.Release.Service }}"
type: kubernetes.io/tls
data:
tls.crt: {{ .certificate }}
tls.key: {{ .key }}
---
{{- end }}
```
So far we have looked at just one template declared in just one file. But one of the powerful features of the Helm template language is its ability to declare multiple templates and use them together. We'll turn to that in the next section. So far we have looked at just one template declared in just one file. But one of the powerful features of the Helm template language is its ability to declare multiple templates and use them together. We'll turn to that in the next section.

@ -319,7 +319,7 @@ refer to that value by reference. YAML refers to this as "anchoring":
```yaml ```yaml
coffee: "yes, please" coffee: "yes, please"
favorite: &favoriteCoffee "Cappucino" favorite: &favoriteCoffee "Cappucino"
coffess: coffees:
- Latte - Latte
- *favoriteCoffee - *favoriteCoffee
- Espresso - Espresso
@ -339,7 +339,7 @@ YAML would be:
```YAML ```YAML
coffee: yes, please coffee: yes, please
favorite: Cappucino favorite: Cappucino
coffess: coffees:
- Latte - Latte
- Cappucino - Cappucino
- Espresso - Espresso

@ -96,6 +96,35 @@ For example:
The above will render the template when .Values.foo is defined, but will fail The above will render the template when .Values.foo is defined, but will fail
to render and exit when .Values.foo is undefined. to render and exit when .Values.foo is undefined.
## Creating Image Pull Secrets
Image pull secrets are essentially a combination of _registry_, _username_, and _password_. You may need them in an application you are deploying, but to create them requires running _base64_ a couple of times. We can write a helper template to compose the Docker configuration file for use as the Secret's payload. Here is an example:
First, assume that the credentials are defined in the `values.yaml` file like so:
```
imageCredentials:
registry: quay.io
username: someone
password: sillyness
```
We then define our helper template as follows:
```
{{- define "imagePullSecret" }}
{{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.imageCredentials.registry (printf "%s:%s" .Values.imageCredentials.username .Values.imageCredentials.password | b64enc) | b64enc }}
{{- end }}
```
Finally, we use the helper template in a larger template to create the Secret manifest:
```
apiVersion: v1
kind: Secret
metadata:
name: myregistrykey
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: {{ template "imagePullSecret" . }}
```
## Automatically Roll Deployments When ConfigMaps or Secrets change ## Automatically Roll Deployments When ConfigMaps or Secrets change
Often times configmaps or secrets are injected as configuration Often times configmaps or secrets are injected as configuration

@ -21,7 +21,9 @@ We use Make to build our programs. The simplest way to get started is:
$ make bootstrap build $ make bootstrap build
``` ```
NOTE: This will fail if not run from the path: `$GOPATH/src/k8s.io/helm`. NOTE: This will fail if not running from the path `$GOPATH/src/k8s.io/helm`. The
directory `k8s.io` should not be a symlink or `build` will not find the relevant
packages.
This will build both Helm and Tiller. `make bootstrap` will attempt to This will build both Helm and Tiller. `make bootstrap` will attempt to
install certain tools if they are missing. install certain tools if they are missing.

@ -2,7 +2,7 @@
{{/* {{/*
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "name" -}} {{- define "alpine.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
@ -10,7 +10,7 @@ Expand the name of the chart.
Create a default fully qualified app name. Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}} */}}
{{- define "fullname" -}} {{- define "alpine.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}} {{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}

@ -1,7 +1,7 @@
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "alpine.fullname" . }}
labels: labels:
# The "heritage" label is used to track which tool deployed a given chart. # 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 # It is useful for admins who want to see what releases a particular tool
@ -12,7 +12,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
# This makes it easy to audit chart usage. # This makes it easy to audit chart usage.
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }} app: {{ template "alpine.name" . }}
spec: spec:
# This shows how to use a simple value. This will look for a passed-in value called restartPolicy. # This shows how to use a simple value. This will look for a passed-in value called restartPolicy.
restartPolicy: {{ .Values.restartPolicy }} restartPolicy: {{ .Values.restartPolicy }}

@ -2,7 +2,7 @@
{{/* {{/*
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "name" -}} {{- define "nginx.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
@ -10,7 +10,7 @@ Expand the name of the chart.
Create a default fully qualified app name. Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}} */}}
{{- define "fullname" -}} {{- define "nginx.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}} {{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}

@ -2,12 +2,12 @@
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}
labels: labels:
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
data: data:
# When the config map is mounted as a volume, these will be created as files. # When the config map is mounted as a volume, these will be created as files.
index.html: {{ .Values.index | quote }} index.html: {{ .Values.index | quote }}

@ -4,7 +4,7 @@ metadata:
# This uses a "fullname" template (see _helpers) # This uses a "fullname" template (see _helpers)
# Basing names on .Release.Name means that the same chart can be installed # Basing names on .Release.Name means that the same chart can be installed
# multiple times into the same namespace. # multiple times into the same namespace.
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}
labels: labels:
# The "heritage" label is used to track which tool deployed a given chart. # 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 # It is useful for admins who want to see what releases a particular tool
@ -15,7 +15,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
# This makes it easy to audit chart usage. # This makes it easy to audit chart usage.
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
spec: spec:
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.replicaCount }}
template: template:
@ -26,11 +26,11 @@ spec:
{{ toYaml .Values.podAnnotations | indent 8 }} {{ toYaml .Values.podAnnotations | indent 8 }}
{{- end }} {{- end }}
labels: labels:
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
spec: spec:
containers: containers:
- name: {{ template "name" . }} - name: {{ template "nginx.name" . }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:
@ -54,4 +54,4 @@ spec:
volumes: volumes:
- name: wwwdata-volume - name: wwwdata-volume
configMap: configMap:
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}

@ -1,7 +1,7 @@
apiVersion: batch/v1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}
labels: labels:
# The "heritage" label is used to track which tool deployed a given chart. # 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 # It is useful for admins who want to see what releases a particular tool
@ -12,7 +12,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
# This makes it easy to audit chart usage. # This makes it easy to audit chart usage.
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
annotations: annotations:
# This is what defines this resource as a hook. Without this line, the # This is what defines this resource as a hook. Without this line, the
# job is considered part of the release. # job is considered part of the release.
@ -20,10 +20,10 @@ metadata:
spec: spec:
template: template:
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}
labels: labels:
release: {{ .Release.Name }} release: {{ .Release.Name }}
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
spec: spec:
# This shows how to use a simple value. This will look for a passed-in value # 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. # called restartPolicy. If it is not found, it will use the default value.

@ -3,12 +3,12 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}
labels: labels:
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
# This declares the resource to be a hook. By convention, we also name the # This declares the resource to be a hook. By convention, we also name the
# file "pre-install-XXX.yaml", but Helm itself doesn't care about file names. # file "pre-install-XXX.yaml", but Helm itself doesn't care about file names.
annotations: annotations:

@ -1,12 +1,12 @@
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: "{{ template "fullname" . }}-service-test" name: "{{ template "nginx.fullname" . }}-service-test"
labels: labels:
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
annotations: annotations:
"helm.sh/hook": test-success "helm.sh/hook": test-success
spec: spec:
@ -14,5 +14,5 @@ spec:
- name: curl - name: curl
image: radial/busyboxplus:curl image: radial/busyboxplus:curl
command: ['curl'] command: ['curl']
args: ['{{ template "fullname" . }}:{{ .Values.service.port }}'] args: ['{{ template "nginx.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never restartPolicy: Never

@ -6,11 +6,11 @@ metadata:
{{ toYaml .Values.service.annotations | indent 4 }} {{ toYaml .Values.service.annotations | indent 4 }}
{{- end }} {{- end }}
labels: labels:
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }} chart: {{ .Chart.Name }}-{{ .Chart.Version }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
name: {{ template "fullname" . }} name: {{ template "nginx.fullname" . }}
spec: spec:
# Provides options for the service so chart users have the full choice # Provides options for the service so chart users have the full choice
type: "{{ .Values.service.type }}" type: "{{ .Values.service.type }}"
@ -35,5 +35,5 @@ spec:
nodePort: {{ .Values.service.nodePort }} nodePort: {{ .Values.service.nodePort }}
{{- end }} {{- end }}
selector: selector:
app: {{ template "name" . }} app: {{ template "nginx.name" . }}
release: {{ .Release.Name }} release: {{ .Release.Name }}

@ -30,7 +30,7 @@ helm fetch [flags] [chart URL | repo/chartname] [...]
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file --cert-file string identify HTTPS client using this SSL certificate file
-d, --destination string location to write the chart. If this and tardir are specified, tardir is appended to this (default ".") -d, --destination string location to write the chart. If this and tardir are specified, tardir is appended to this (default ".")
--devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
--key-file string identify HTTPS client using this SSL key file --key-file string identify HTTPS client using this SSL key file
--keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg")
--prov fetch the provenance file, but don't perform verification --prov fetch the provenance file, but don't perform verification

@ -39,6 +39,9 @@ helm init
--history-max int limit the maximum number of revisions saved per release. Use 0 for no limit. --history-max int limit the maximum number of revisions saved per release. Use 0 for no limit.
--local-repo-url string URL for local repository (default "http://127.0.0.1:8879/charts") --local-repo-url string URL for local repository (default "http://127.0.0.1:8879/charts")
--net-host install Tiller with net=host --net-host install Tiller with net=host
--node-selectors string labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)
-o, --output OutputFormat skip installation and output Tiller's manifest in specified format (json or yaml)
--override stringArray override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)
--service-account string name of service account --service-account string name of service account
--skip-refresh do not refresh (download) the local repository cache --skip-refresh do not refresh (download) the local repository cache
--stable-repo-url string URL for stable repository (default "https://kubernetes-charts.storage.googleapis.com") --stable-repo-url string URL for stable repository (default "https://kubernetes-charts.storage.googleapis.com")
@ -64,4 +67,4 @@ helm init
### SEE ALSO ### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 10-Aug-2017 ###### Auto generated by spf13/cobra on 10-Oct-2017

@ -8,8 +8,8 @@ install a chart archive
This command installs a chart archive. This command installs a chart archive.
The install argument must be either a relative path to a chart directory or the The install argument must be a chart reference, a path to a packaged chart,
name of a chart in the current working directory. a path to an unpacked chart directory or a URL.
To override values in a chart, use either the '--values' flag and pass in a file To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line. or use the '--set' flag and pass configuration from the command line.
@ -70,7 +70,7 @@ helm install [CHART]
``` ```
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file --cert-file string identify HTTPS client using this SSL certificate file
--devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
--dry-run simulate an install --dry-run simulate an install
--key-file string identify HTTPS client using this SSL key file --key-file string identify HTTPS client using this SSL key file
--keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg") --keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg")
@ -87,7 +87,7 @@ helm install [CHART]
--tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem")
--tls-key string path to TLS key file (default "$HELM_HOME/key.pem") --tls-key string path to TLS key file (default "$HELM_HOME/key.pem")
--tls-verify enable TLS for request and verify remote --tls-verify enable TLS for request and verify remote
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default [])
--verify verify the package before installing it --verify verify the package before installing it
--version string specify the exact chart version to install. If this is not specified, the latest version is installed --version string specify the exact chart version to install. If this is not specified, the latest version is installed
--wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout --wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout

@ -38,7 +38,7 @@ helm upgrade [RELEASE] [CHART]
``` ```
--ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle
--cert-file string identify HTTPS client using this SSL certificate file --cert-file string identify HTTPS client using this SSL certificate file
--devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.
--dry-run simulate an upgrade --dry-run simulate an upgrade
--force force resource update through delete/recreate if needed --force force resource update through delete/recreate if needed
-i, --install if a release by this name doesn't already exist, run an install -i, --install if a release by this name doesn't already exist, run an install
@ -57,7 +57,7 @@ helm upgrade [RELEASE] [CHART]
--tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem")
--tls-key string path to TLS key file (default "$HELM_HOME/key.pem") --tls-key string path to TLS key file (default "$HELM_HOME/key.pem")
--tls-verify enable TLS for request and verify remote --tls-verify enable TLS for request and verify remote
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default [])
--verify verify the provenance of the chart before upgrading --verify verify the provenance of the chart before upgrading
--version string specify the exact chart version to use. If this is not specified, the latest version is used --version string specify the exact chart version to use. If this is not specified, the latest version is used
--wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout --wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout

@ -197,6 +197,144 @@ Tiller can then be re-installed from the client with:
$ helm init $ helm init
``` ```
## Advanced Usage
`helm init` provides additional flags for modifying Tiller's deployment
manifest before it is installed.
### Using `--node-selectors`
The `--node-selectors` flag allows us to specify the node labels required
for scheduling the Tiller pod.
The example below will create the specified label under the nodeSelector
property.
```
helm init --node-selectors "beta.kubernetes.io/os"="linux"
```
The installed deployment manifest will contain our node selector label.
```
...
spec:
template:
spec:
nodeSelector:
beta.kubernetes.io/os: linux
...
```
### Using `--override`
`--override` allows you to specify properties of Tiller's
deployment manifest. Unlike the `--set` command used elsewhere in Helm,
`helm init --override` manipulates the specified properties of the final
manifest (there is no "values" file). Therefore you may specify any valid
value for any valid property in the deployment manifest.
#### Override annotation
In the example below we use `--override` to add the revision property and set
its value to 1.
```
helm init --set metadata.annotations."deployment\.kubernetes\.io/revision"="1"
```
Output:
```
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
...
```
#### Override affinity
In the example below we set properties for node affinity. Multiple
`--override` commands may be combined to modify different properties of the
same list item.
```
helm init --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight"="1" --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].key"="e2e-az-name"
```
The specified properties are combined into the
"preferredDuringSchedulingIgnoredDuringExecution" property's first
list item.
```
...
spec:
strategy: {}
template:
...
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: e2e-az-name
operator: ""
weight: 1
...
```
### Using `--output`
The `--output` flag allows us skip the installation of Tiller's deployment
manifest and simply output the deployment manifest to stdout in either
JSON or YAML format. The output may then be modified with tools like `jq`
and installed manually with `kubectl`.
In the example below we execute `helm init` with the `--output json` flag.
```
helm init --output json
```
The Tiller installation is skipped and the manifest is output to stdout
in JSON format.
```
"apiVersion": "extensions/v1beta1",
"kind": "Deployment",
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "helm",
"name": "tiller"
},
"name": "tiller-deploy",
"namespace": "kube-system"
},
...
```
### Storage backends
By default, `tiller` stores release information in `ConfigMaps` in the namespace
where it is running. As of Helm 2.7.0, there is now a beta storage backend that
uses `Secrets` for storing release information. This was added for additional
security in protecting charts in conjunction with the release of `Secret`
encryption in Kubernetes.
To enable the secrets backend, you'll need to init Tiller with the following
options:
```shell
helm init --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'
```
Currently, if you want to switch from the default backend to the secrets
backend, you'll have to do the migration for this on your own. When this backend
graduates from beta, there will be a more official path of migration
## Conclusion ## Conclusion
In most cases, installation is as simple as getting a pre-built `helm` binary In most cases, installation is as simple as getting a pre-built `helm` binary

@ -27,7 +27,7 @@ Kubernetes bootstrapped with `kubeadm` is known to work on the following Linux
distributions: distributions:
- Ubuntu 16.04 - Ubuntu 16.04
- CAN SOMEONE CONFIRM ON FEDORA? - Fedora release 25
Some versions of Helm (v2.0.0-beta2) require you to `export KUBECONFIG=/etc/kubernetes/admin.conf` Some versions of Helm (v2.0.0-beta2) require you to `export KUBECONFIG=/etc/kubernetes/admin.conf`
or create a `~/.kube/config`. or create a `~/.kube/config`.
@ -35,3 +35,7 @@ or create a `~/.kube/config`.
## Container Linux by CoreOS ## Container Linux by CoreOS
Helm requires that kubelet have access to a copy of the `socat` program to proxy connections to the Tiller API. On Container Linux the Kubelet runs inside of a [hyperkube](https://github.com/kubernetes/kubernetes/tree/master/cluster/images/hyperkube) container image that has socat. So, even though Container Linux doesn't ship `socat` the container filesystem running kubelet does have socat. To learn more read the [Kubelet Wrapper](https://coreos.com/kubernetes/docs/latest/kubelet-wrapper.html) docs. Helm requires that kubelet have access to a copy of the `socat` program to proxy connections to the Tiller API. On Container Linux the Kubelet runs inside of a [hyperkube](https://github.com/kubernetes/kubernetes/tree/master/cluster/images/hyperkube) container image that has socat. So, even though Container Linux doesn't ship `socat` the container filesystem running kubelet does have socat. To learn more read the [Kubelet Wrapper](https://coreos.com/kubernetes/docs/latest/kubelet-wrapper.html) docs.
## Openshift
Helm works straightforward on OpenShift Online, OpenShift Dedicated, OpenShift Container Platform (version >= 3.6) or OpenShift Origin (version >= 3.6). To learn more read [this blog](https://blog.openshift.com/getting-started-helm-openshift/) post.

@ -18,8 +18,8 @@ helm\-install \- install a chart archive
This command installs a chart archive. This command installs a chart archive.
.PP .PP
The install argument must be either a relative path to a chart directory or the The install argument must be a chart reference, a path to a packaged chart,
name of a chart in the current working directory. a path to an unpacked chart directory or a URL.
.PP .PP
To override values in a chart, use either the '\-\-values' flag and pass in a file To override values in a chart, use either the '\-\-values' flag and pass in a file
@ -192,7 +192,7 @@ charts in a repository, use 'helm search'.
.PP .PP
\fB\-f\fP, \fB\-\-values\fP=[] \fB\-f\fP, \fB\-\-values\fP=[]
specify values in a YAML file (can specify multiple) specify values in a YAML file or a URL(can specify multiple)
.PP .PP
\fB\-\-verify\fP[=false] \fB\-\-verify\fP[=false]

@ -143,7 +143,7 @@ $ helm upgrade \-\-set foo=bar \-\-set foo=newbar redis ./redis
.PP .PP
\fB\-f\fP, \fB\-\-values\fP=[] \fB\-f\fP, \fB\-\-values\fP=[]
specify values in a YAML file (can specify multiple) specify values in a YAML file or a URL(can specify multiple)
.PP .PP
\fB\-\-verify\fP[=false] \fB\-\-verify\fP[=false]

437
glide.lock generated

@ -1,14 +1,22 @@
hash: 91eba16992e639203a273b247807bb990901cc0ef7e744991722106fc9db0956 hash: 650f1d4cd9e9dc5ba76480a5465923ce1bbd11b8fa956b644aaf975e8f7e1f33
updated: 2017-09-18T15:49:23.687863166-04:00 updated: 2017-10-12T13:08:50.435765-07:00
imports: imports:
- name: cloud.google.com/go - name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821 version: 3b1ae45394a234c385be014e9a488f2bb6eef821
subpackages:
- compute/metadata
- internal
- 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: 7664702784775e51966f0885f5cd27435916517b version: 7664702784775e51966f0885f5cd27435916517b
- name: github.com/Azure/go-autorest - name: github.com/Azure/go-autorest
version: d7c034a8af24eda120dd6460bfcd6d9ed14e43ca version: 58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d
subpackages:
- autorest
- autorest/adal
- autorest/azure
- autorest/date
- name: github.com/beorn7/perks - name: github.com/beorn7/perks
version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 version: 3ac7bf7a47d159a033b107610db8a1b6575507a4
subpackages: subpackages:
@ -22,42 +30,59 @@ imports:
subpackages: subpackages:
- md2man - md2man
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d version: 782f4967f2dc4564575ca782fe2d04090b5faca8
subpackages: subpackages:
- spew - spew
- name: github.com/dgrijalva/jwt-go - name: github.com/dgrijalva/jwt-go
version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: 03efb43768979f4d2ea5187bef995656441829e5 version: edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
subpackages: subpackages:
- digest - digestset
- reference - reference
- name: github.com/docker/engine-api - name: github.com/docker/docker
version: dea108d3aa0c67d7162a3fd8aa65f38a430019fd version: 4f3616fb1c112e206b88cb7a9922bf49067a7756
subpackages: subpackages:
- api
- api/types
- api/types/blkiodev
- api/types/container
- api/types/events
- api/types/filters
- api/types/image
- api/types/mount
- api/types/network
- api/types/registry
- api/types/strslice
- api/types/swarm
- api/types/swarm/runtime
- api/types/time
- api/types/versions
- api/types/volume
- client - client
- client/transport - pkg/ioutils
- client/transport/cancellable - pkg/jsonlog
- types - pkg/jsonmessage
- types/blkiodev - pkg/longpath
- types/container - pkg/mount
- types/filters - pkg/stdcopy
- types/network - pkg/symlink
- types/reference - pkg/system
- types/registry - pkg/term
- types/strslice - pkg/term/windows
- types/time - pkg/tlsconfig
- types/versions
- name: github.com/docker/go-connections - name: github.com/docker/go-connections
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d
subpackages: subpackages:
- nat - nat
- sockets - sockets
- tlsconfig - tlsconfig
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: e30f1e79f3cd72542f2026ceec18d3bd67ab859c version: 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
- name: github.com/docker/spdystream - name: github.com/docker/spdystream
version: 449fdfce4d962303d702fec724ef0ad181c92528 version: 449fdfce4d962303d702fec724ef0ad181c92528
subpackages:
- spdy
- name: github.com/emicklei/go-restful - name: github.com/emicklei/go-restful
version: ff4f55a206334ef123e4f79bbf348980da81ca46 version: ff4f55a206334ef123e4f79bbf348980da81ca46
subpackages: subpackages:
@ -65,31 +90,19 @@ imports:
- name: github.com/emicklei/go-restful-swagger12 - name: github.com/emicklei/go-restful-swagger12
version: dcef7f55730566d41eae5db10e7d6981829720f6 version: dcef7f55730566d41eae5db10e7d6981829720f6
- name: github.com/evanphx/json-patch - name: github.com/evanphx/json-patch
version: ba18e35c5c1b36ef6334cad706eb681153d2d379 version: 944e07253867aacae43c04b2e6a239005443f33a
- name: github.com/exponent-io/jsonpath - name: github.com/exponent-io/jsonpath
version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5 version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5
- name: github.com/facebookgo/atomicfile
version: 2de1f203e7d5e386a6833233882782932729f27e
- name: github.com/facebookgo/symwalk
version: 42004b9f322246749dd73ad71008b1f3160c0052
- name: github.com/fatih/camelcase - name: github.com/fatih/camelcase
version: f6a740d52f961c60348ebb109adde9f4635d7540 version: f6a740d52f961c60348ebb109adde9f4635d7540
- name: github.com/ghodss/yaml - name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-openapi/analysis
version: b44dc874b601d9e4e2f6e19140e794ba24bead3b
- name: github.com/go-openapi/errors
version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc
- name: github.com/go-openapi/jsonpointer - name: github.com/go-openapi/jsonpointer
version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 version: 46af16f9f7b149af66e5d1bd010e3574dc06de98
- name: github.com/go-openapi/jsonreference - name: github.com/go-openapi/jsonreference
version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
- name: github.com/go-openapi/loads
version: 18441dfa706d924a39a030ee2c3b1d8d81917b38
- name: github.com/go-openapi/spec - name: github.com/go-openapi/spec
version: 6aced65f8501fe1217321abf0749d354824ba2ff version: 6aced65f8501fe1217321abf0749d354824ba2ff
- name: github.com/go-openapi/strfmt
version: d65c7fdb29eca313476e529628176fe17e58c488
- name: github.com/go-openapi/swag - name: github.com/go-openapi/swag
version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72
- name: github.com/gobwas/glob - name: github.com/gobwas/glob
@ -105,31 +118,8 @@ imports:
- name: github.com/gogo/protobuf - name: github.com/gogo/protobuf
version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 version: c0656edd0d9eab7c66d1eb0c568f9039345796f7
subpackages: subpackages:
- gogoproto
- plugin/compare
- plugin/defaultcheck
- plugin/description
- plugin/embedcheck
- plugin/enumstringer
- plugin/equal
- plugin/face
- plugin/gostring
- plugin/marshalto
- plugin/oneofcheck
- plugin/populate
- plugin/size
- plugin/stringer
- plugin/testgen
- plugin/union
- plugin/unmarshal
- proto - proto
- protoc-gen-gogo/descriptor
- protoc-gen-gogo/generator
- protoc-gen-gogo/grpc
- protoc-gen-gogo/plugin
- sortkeys - sortkeys
- vanity
- vanity/command
- name: github.com/golang/glog - name: github.com/golang/glog
version: 44145f04b68cf362d9c4df2182967c2275eaefed version: 44145f04b68cf362d9c4df2182967c2275eaefed
- name: github.com/golang/groupcache - name: github.com/golang/groupcache
@ -137,30 +127,57 @@ imports:
subpackages: subpackages:
- lru - lru
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef version: 4bd1920723d7b7c925de087aa32e2187708897f7
subpackages: subpackages:
- proto - proto
- ptypes
- ptypes/any - ptypes/any
- ptypes/duration
- ptypes/timestamp - ptypes/timestamp
- name: github.com/google/btree
version: 7d79101e329e5a3adf994758c578dab82b90c017
- name: github.com/google/gofuzz - name: github.com/google/gofuzz
version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5 version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/googleapis/gnostic
version: 0c5108395e2debce0d731cf0287ddf7242066aba
subpackages:
- OpenAPIv2
- compiler
- extensions
- name: github.com/gophercloud/gophercloud
version: 2bf16b94fdd9b01557c4d076e567fe5cbbe5a961
subpackages:
- openstack
- openstack/identity/v2/tenants
- openstack/identity/v2/tokens
- openstack/identity/v3/tokens
- openstack/utils
- pagination
- name: github.com/gosuri/uitable - name: github.com/gosuri/uitable
version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
subpackages: subpackages:
- util/strutil - util/strutil
- util/wordwrap - util/wordwrap
- name: github.com/gregjones/httpcache
version: 787624de3eb7bd915c329cba748687a3b22666a6
subpackages:
- diskcache
- name: github.com/grpc-ecosystem/go-grpc-prometheus - name: github.com/grpc-ecosystem/go-grpc-prometheus
version: 0c1b191dbfe51efdabe3c14b9f6f3b96429e0722 version: 0c1b191dbfe51efdabe3c14b9f6f3b96429e0722
- name: github.com/hashicorp/golang-lru - name: github.com/hashicorp/golang-lru
version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4
subpackages:
- simplelru
- name: github.com/howeyc/gopass - name: github.com/howeyc/gopass
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d version: bf9dde6d0d2c004a008c27aaee91170c786f6db8
- name: github.com/huandu/xstrings - name: github.com/huandu/xstrings
version: 3959339b333561bf62a38b424fd41517c2c90f40 version: 3959339b333561bf62a38b424fd41517c2c90f40
- name: github.com/imdario/mergo - name: github.com/imdario/mergo
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- name: github.com/inconshreveable/mousetrap - name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/json-iterator/go
version: 36b14963da70d11297d313183d7e6388c8510e1e
- name: github.com/juju/ratelimit - name: github.com/juju/ratelimit
version: 5b9ff866471762aa2ab2dced63c9fb6f53921342 version: 5b9ff866471762aa2ab2dced63c9fb6f53921342
- name: github.com/mailru/easyjson - name: github.com/mailru/easyjson
@ -172,7 +189,7 @@ imports:
- name: github.com/Masterminds/semver - name: github.com/Masterminds/semver
version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd
- name: github.com/Masterminds/sprig - name: github.com/Masterminds/sprig
version: 4c164950cd0a8d3724ddb78982e2c56dc7f47112 version: efda631a76d70875162cdc25ffa0d0164bf69758
- name: github.com/Masterminds/vcs - name: github.com/Masterminds/vcs
version: 3084677c2c188840777bff30054f2b553729d329 version: 3084677c2c188840777bff30054f2b553729d329
- name: github.com/mattn/go-runewidth - name: github.com/mattn/go-runewidth
@ -181,12 +198,19 @@ imports:
version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a
subpackages: subpackages:
- pbutil - pbutil
- name: github.com/mitchellh/mapstructure
version: 740c764bc6149d3f1806231418adb9f52c11bcbf
- name: github.com/naoina/go-stringutil - name: github.com/naoina/go-stringutil
version: 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b version: 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b
- name: github.com/opencontainers/go-digest
version: a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
- name: github.com/opencontainers/image-spec
version: 372ad780f63454fbbbbcc7cf80e5b90245c13e13
subpackages:
- specs-go
- specs-go/v1
- name: github.com/pborman/uuid - name: github.com/pborman/uuid
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
- name: github.com/peterbourgon/diskv
version: 5f041e8faa004a95c88a202771f4cc3e991971e6
- name: github.com/prometheus/client_golang - name: github.com/prometheus/client_golang
version: c5b7fccd204277076155f10851dad72b76a49317 version: c5b7fccd204277076155f10851dad72b76a49317
subpackages: subpackages:
@ -228,9 +252,8 @@ imports:
version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74 version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74
subpackages: subpackages:
- codec - codec
- codec/codecgen
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: d172538b2cfce0c13cee31e647d0367aa8cd2486 version: 81e90905daefcd6fd217b62423c0908922eadb30
subpackages: subpackages:
- cast5 - cast5
- openpgp - openpgp
@ -244,9 +267,10 @@ imports:
- scrypt - scrypt
- ssh/terminal - ssh/terminal
- name: golang.org/x/net - name: golang.org/x/net
version: f2499483f923065a842d38eb4c7f1927e6fc6e6d version: 1c05540f6879653db88113bc4a2b70aec4bd491f
subpackages: subpackages:
- context - context
- context/ctxhttp
- http2 - http2
- http2/hpack - http2/hpack
- idna - idna
@ -254,19 +278,26 @@ imports:
- lex/httplex - lex/httplex
- trace - trace
- name: golang.org/x/oauth2 - name: golang.org/x/oauth2
version: 3c3a985cb79f52a3190fbc056984415ca6763d01 version: a6bd8cefa1811bd24b86f8902872e4e8225f74c4
subpackages:
- google
- internal
- jws
- jwt
- name: golang.org/x/sys - name: golang.org/x/sys
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 version: 43eea11bc92608addb41b8a406b0407495c106f6
subpackages: subpackages:
- unix - unix
- windows
- name: golang.org/x/text - name: golang.org/x/text
version: 2910a502d2bf9e43193af9d68ca516529614eed3 version: b19bf474d317b857955b12035d2c5acb57ce8b01
subpackages: subpackages:
- cases - cases
- encoding - encoding
- encoding/internal - encoding/internal
- encoding/internal/identifier - encoding/internal/identifier
- encoding/unicode - encoding/unicode
- internal
- internal/tag - internal/tag
- internal/utf8internal - internal/utf8internal
- language - language
@ -277,6 +308,18 @@ imports:
- unicode/bidi - unicode/bidi
- unicode/norm - unicode/norm
- width - width
- name: google.golang.org/appengine
version: 12d5545dc1cfa6047a286d5e853841b6471f4c19
subpackages:
- internal
- internal/app_identity
- internal/base
- internal/datastore
- internal/log
- internal/modules
- internal/remote_api
- internal/urlfetch
- urlfetch
- name: google.golang.org/grpc - name: google.golang.org/grpc
version: 8050b9cbc271307e5a716a9d782803d09b0d6f2d version: 8050b9cbc271307e5a716a9d782803d09b0d6f2d
subpackages: subpackages:
@ -293,37 +336,99 @@ imports:
- transport - transport
- name: gopkg.in/inf.v0 - name: gopkg.in/inf.v0
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/mgo.v2
version: 3f83fa5005286a7fe593b055f0d7771a7dce4655
subpackages:
- bson
- name: gopkg.in/yaml.v2 - name: gopkg.in/yaml.v2
version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 version: 53feefa2559fb8dfa8d81baad31be332c97d6c77
- name: k8s.io/api - name: k8s.io/api
version: 4fe9229aaa9d704f8a2a21cdcd50de2bbb6e1b57 version: cadaf100c0a3dd6b254f320d6d651df079ec8e0a
subpackages: subpackages:
- admission/v1alpha1
- admissionregistration/v1alpha1 - admissionregistration/v1alpha1
- apps/v1beta1 - apps/v1beta1
- apps/v1beta2
- authentication/v1 - authentication/v1
- authentication/v1beta1 - authentication/v1beta1
- authorization/v1 - authorization/v1
- authorization/v1beta1 - authorization/v1beta1
- autoscaling/v1 - autoscaling/v1
- autoscaling/v2alpha1 - autoscaling/v2beta1
- batch/v1 - batch/v1
- batch/v1beta1
- batch/v2alpha1 - batch/v2alpha1
- certificates/v1beta1 - certificates/v1beta1
- core/v1 - core/v1
- extensions/v1beta1 - extensions/v1beta1
- imagepolicy/v1alpha1
- networking/v1 - networking/v1
- policy/v1beta1 - policy/v1beta1
- rbac/v1
- rbac/v1alpha1 - rbac/v1alpha1
- rbac/v1beta1 - rbac/v1beta1
- scheduling/v1alpha1
- settings/v1alpha1 - settings/v1alpha1
- storage/v1 - storage/v1
- storage/v1beta1 - storage/v1beta1
- name: k8s.io/apiextensions-apiserver
version: a5bbfd114a9b122acd741c61d88c84812375d9e1
subpackages:
- pkg/features
- name: k8s.io/apimachinery
version: 3b05bbfa0a45413bfa184edbf9af617e277962fb
subpackages:
- pkg/api/equality
- pkg/api/errors
- pkg/api/meta
- pkg/api/resource
- pkg/api/validation
- pkg/apimachinery
- pkg/apimachinery/announced
- pkg/apimachinery/registered
- pkg/apis/meta/internalversion
- pkg/apis/meta/v1
- pkg/apis/meta/v1/unstructured
- pkg/apis/meta/v1/validation
- pkg/apis/meta/v1alpha1
- pkg/conversion
- pkg/conversion/queryparams
- pkg/conversion/unstructured
- pkg/fields
- pkg/labels
- pkg/runtime
- pkg/runtime/schema
- pkg/runtime/serializer
- pkg/runtime/serializer/json
- pkg/runtime/serializer/protobuf
- pkg/runtime/serializer/recognizer
- pkg/runtime/serializer/streaming
- pkg/runtime/serializer/versioning
- pkg/selection
- pkg/types
- pkg/util/cache
- pkg/util/clock
- pkg/util/diff
- pkg/util/errors
- pkg/util/framer
- pkg/util/httpstream
- pkg/util/httpstream/spdy
- pkg/util/intstr
- pkg/util/json
- pkg/util/mergepatch
- pkg/util/net
- pkg/util/rand
- pkg/util/runtime
- pkg/util/sets
- pkg/util/strategicpatch
- pkg/util/uuid
- pkg/util/validation
- pkg/util/validation/field
- pkg/util/wait
- pkg/util/yaml
- pkg/version
- pkg/watch
- third_party/forked/golang/json
- third_party/forked/golang/netutil
- third_party/forked/golang/reflect
- name: k8s.io/apiserver - name: k8s.io/apiserver
version: 087d1a2efeb6296f04bb0f54e7af9890052aa6d7 version: c1e53d745d0fe45bf7d5d44697e6eface25fceca
subpackages: subpackages:
- pkg/admission - pkg/admission
- pkg/apis/apiserver - pkg/apis/apiserver
@ -337,20 +442,118 @@ imports:
- pkg/features - pkg/features
- pkg/util/feature - pkg/util/feature
- pkg/util/flag - pkg/util/flag
- name: k8s.io/client-go
version: 82aa063804cf055e16e8911250f888bc216e8b61
subpackages:
- discovery
- discovery/fake
- dynamic
- informers/apps/v1beta1
- informers/core/v1
- informers/extensions/v1beta1
- informers/internalinterfaces
- kubernetes
- kubernetes/fake
- kubernetes/scheme
- kubernetes/typed/admissionregistration/v1alpha1
- kubernetes/typed/admissionregistration/v1alpha1/fake
- kubernetes/typed/apps/v1beta1
- kubernetes/typed/apps/v1beta1/fake
- kubernetes/typed/apps/v1beta2
- kubernetes/typed/apps/v1beta2/fake
- kubernetes/typed/authentication/v1
- kubernetes/typed/authentication/v1/fake
- kubernetes/typed/authentication/v1beta1
- kubernetes/typed/authentication/v1beta1/fake
- kubernetes/typed/authorization/v1
- kubernetes/typed/authorization/v1/fake
- kubernetes/typed/authorization/v1beta1
- kubernetes/typed/authorization/v1beta1/fake
- kubernetes/typed/autoscaling/v1
- kubernetes/typed/autoscaling/v1/fake
- kubernetes/typed/autoscaling/v2beta1
- kubernetes/typed/autoscaling/v2beta1/fake
- kubernetes/typed/batch/v1
- kubernetes/typed/batch/v1/fake
- kubernetes/typed/batch/v1beta1
- kubernetes/typed/batch/v1beta1/fake
- kubernetes/typed/batch/v2alpha1
- kubernetes/typed/batch/v2alpha1/fake
- kubernetes/typed/certificates/v1beta1
- kubernetes/typed/certificates/v1beta1/fake
- kubernetes/typed/core/v1
- kubernetes/typed/core/v1/fake
- kubernetes/typed/extensions/v1beta1
- kubernetes/typed/extensions/v1beta1/fake
- kubernetes/typed/networking/v1
- kubernetes/typed/networking/v1/fake
- kubernetes/typed/policy/v1beta1
- kubernetes/typed/policy/v1beta1/fake
- kubernetes/typed/rbac/v1
- kubernetes/typed/rbac/v1/fake
- kubernetes/typed/rbac/v1alpha1
- kubernetes/typed/rbac/v1alpha1/fake
- kubernetes/typed/rbac/v1beta1
- kubernetes/typed/rbac/v1beta1/fake
- kubernetes/typed/scheduling/v1alpha1
- kubernetes/typed/scheduling/v1alpha1/fake
- kubernetes/typed/settings/v1alpha1
- kubernetes/typed/settings/v1alpha1/fake
- kubernetes/typed/storage/v1
- kubernetes/typed/storage/v1/fake
- kubernetes/typed/storage/v1beta1
- kubernetes/typed/storage/v1beta1/fake
- listers/apps/v1beta1
- listers/core/v1
- listers/extensions/v1beta1
- pkg/version
- plugin/pkg/client/auth
- plugin/pkg/client/auth/azure
- plugin/pkg/client/auth/gcp
- plugin/pkg/client/auth/oidc
- plugin/pkg/client/auth/openstack
- rest
- rest/fake
- rest/watch
- testing
- third_party/forked/golang/template
- tools/auth
- tools/cache
- tools/clientcmd
- tools/clientcmd/api
- tools/clientcmd/api/latest
- tools/clientcmd/api/v1
- tools/metrics
- tools/pager
- tools/portforward
- tools/record
- tools/reference
- transport
- transport/spdy
- util/cert
- util/flowcontrol
- util/homedir
- util/integer
- util/jsonpath
- util/retry
- util/workqueue
- name: k8s.io/kube-openapi
version: 868f2f29720b192240e18284659231b440f9cda5
subpackages:
- pkg/common
- name: k8s.io/kubernetes - name: k8s.io/kubernetes
version: d3ada0119e776222f11ec7945e6d860061339aad version: 0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4
subpackages: subpackages:
- cmd/kubeadm/app/apis/kubeadm
- federation/apis/federation - federation/apis/federation
- federation/apis/federation/install - federation/apis/federation/install
- federation/apis/federation/v1beta1 - federation/apis/federation/v1beta1
- federation/client/clientset_generated/federation_internalclientset - federation/client/clientset_generated/federation_clientset
- federation/client/clientset_generated/federation_internalclientset/scheme - federation/client/clientset_generated/federation_clientset/scheme
- federation/client/clientset_generated/federation_internalclientset/typed/autoscaling/internalversion - federation/client/clientset_generated/federation_clientset/typed/autoscaling/v1
- federation/client/clientset_generated/federation_internalclientset/typed/batch/internalversion - federation/client/clientset_generated/federation_clientset/typed/batch/v1
- federation/client/clientset_generated/federation_internalclientset/typed/core/internalversion - federation/client/clientset_generated/federation_clientset/typed/core/v1
- federation/client/clientset_generated/federation_internalclientset/typed/extensions/internalversion - federation/client/clientset_generated/federation_clientset/typed/extensions/v1beta1
- federation/client/clientset_generated/federation_internalclientset/typed/federation/internalversion - federation/client/clientset_generated/federation_clientset/typed/federation/v1beta1
- pkg/api - pkg/api
- pkg/api/events - pkg/api/events
- pkg/api/helper - pkg/api/helper
@ -366,7 +569,6 @@ imports:
- pkg/api/v1/helper - pkg/api/v1/helper
- pkg/api/v1/helper/qos - pkg/api/v1/helper/qos
- pkg/api/v1/pod - pkg/api/v1/pod
- pkg/api/v1/ref
- pkg/api/validation - pkg/api/validation
- pkg/apis/admission - pkg/apis/admission
- pkg/apis/admission/install - pkg/apis/admission/install
@ -377,6 +579,7 @@ imports:
- pkg/apis/apps - pkg/apis/apps
- pkg/apis/apps/install - pkg/apis/apps/install
- pkg/apis/apps/v1beta1 - pkg/apis/apps/v1beta1
- pkg/apis/apps/v1beta2
- pkg/apis/authentication - pkg/apis/authentication
- pkg/apis/authentication/install - pkg/apis/authentication/install
- pkg/apis/authentication/v1 - pkg/apis/authentication/v1
@ -388,10 +591,11 @@ imports:
- pkg/apis/autoscaling - pkg/apis/autoscaling
- pkg/apis/autoscaling/install - pkg/apis/autoscaling/install
- pkg/apis/autoscaling/v1 - pkg/apis/autoscaling/v1
- pkg/apis/autoscaling/v2alpha1 - pkg/apis/autoscaling/v2beta1
- pkg/apis/batch - pkg/apis/batch
- pkg/apis/batch/install - pkg/apis/batch/install
- pkg/apis/batch/v1 - pkg/apis/batch/v1
- pkg/apis/batch/v1beta1
- pkg/apis/batch/v2alpha1 - pkg/apis/batch/v2alpha1
- pkg/apis/certificates - pkg/apis/certificates
- pkg/apis/certificates/install - pkg/apis/certificates/install
@ -413,8 +617,12 @@ imports:
- pkg/apis/policy/v1beta1 - pkg/apis/policy/v1beta1
- pkg/apis/rbac - pkg/apis/rbac
- pkg/apis/rbac/install - pkg/apis/rbac/install
- pkg/apis/rbac/v1
- pkg/apis/rbac/v1alpha1 - pkg/apis/rbac/v1alpha1
- pkg/apis/rbac/v1beta1 - pkg/apis/rbac/v1beta1
- pkg/apis/scheduling
- pkg/apis/scheduling/install
- pkg/apis/scheduling/v1alpha1
- pkg/apis/settings - pkg/apis/settings
- pkg/apis/settings/install - pkg/apis/settings/install
- pkg/apis/settings/v1alpha1 - pkg/apis/settings/v1alpha1
@ -424,28 +632,6 @@ imports:
- pkg/apis/storage/v1 - pkg/apis/storage/v1
- pkg/apis/storage/v1beta1 - pkg/apis/storage/v1beta1
- pkg/capabilities - pkg/capabilities
- pkg/client/clientset_generated/clientset
- pkg/client/clientset_generated/clientset/scheme
- pkg/client/clientset_generated/clientset/typed/admissionregistration/v1alpha1
- pkg/client/clientset_generated/clientset/typed/apps/v1beta1
- pkg/client/clientset_generated/clientset/typed/authentication/v1
- pkg/client/clientset_generated/clientset/typed/authentication/v1beta1
- pkg/client/clientset_generated/clientset/typed/authorization/v1
- pkg/client/clientset_generated/clientset/typed/authorization/v1beta1
- pkg/client/clientset_generated/clientset/typed/autoscaling/v1
- pkg/client/clientset_generated/clientset/typed/autoscaling/v2alpha1
- pkg/client/clientset_generated/clientset/typed/batch/v1
- pkg/client/clientset_generated/clientset/typed/batch/v2alpha1
- pkg/client/clientset_generated/clientset/typed/certificates/v1beta1
- pkg/client/clientset_generated/clientset/typed/core/v1
- pkg/client/clientset_generated/clientset/typed/extensions/v1beta1
- pkg/client/clientset_generated/clientset/typed/networking/v1
- pkg/client/clientset_generated/clientset/typed/policy/v1beta1
- pkg/client/clientset_generated/clientset/typed/rbac/v1alpha1
- pkg/client/clientset_generated/clientset/typed/rbac/v1beta1
- pkg/client/clientset_generated/clientset/typed/settings/v1alpha1
- pkg/client/clientset_generated/clientset/typed/storage/v1
- pkg/client/clientset_generated/clientset/typed/storage/v1beta1
- pkg/client/clientset_generated/internalclientset - pkg/client/clientset_generated/internalclientset
- pkg/client/clientset_generated/internalclientset/fake - pkg/client/clientset_generated/internalclientset/fake
- pkg/client/clientset_generated/internalclientset/scheme - pkg/client/clientset_generated/internalclientset/scheme
@ -473,24 +659,19 @@ imports:
- pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion - pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion
- pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion
- pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/settings/internalversion - pkg/client/clientset_generated/internalclientset/typed/settings/internalversion
- pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake
- pkg/client/clientset_generated/internalclientset/typed/storage/internalversion - pkg/client/clientset_generated/internalclientset/typed/storage/internalversion
- pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake - pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake
- pkg/client/informers/informers_generated/externalversions/apps/v1beta1
- pkg/client/informers/informers_generated/externalversions/core/v1
- pkg/client/informers/informers_generated/externalversions/extensions/v1beta1
- pkg/client/informers/informers_generated/externalversions/internalinterfaces
- pkg/client/leaderelection/resourcelock
- pkg/client/listers/apps/v1beta1
- pkg/client/listers/core/v1
- pkg/client/listers/extensions/v1beta1
- pkg/client/retry
- pkg/client/unversioned - pkg/client/unversioned
- pkg/controller - pkg/controller
- pkg/controller/daemon - pkg/controller/daemon
- pkg/controller/daemon/util - pkg/controller/daemon/util
- pkg/controller/deployment/util - pkg/controller/deployment/util
- pkg/controller/history
- pkg/controller/statefulset
- pkg/credentialprovider - pkg/credentialprovider
- pkg/features - pkg/features
- pkg/fieldpath - pkg/fieldpath
@ -498,9 +679,13 @@ imports:
- pkg/kubectl/cmd/testing - pkg/kubectl/cmd/testing
- pkg/kubectl/cmd/util - pkg/kubectl/cmd/util
- pkg/kubectl/cmd/util/openapi - pkg/kubectl/cmd/util/openapi
- pkg/kubectl/cmd/util/openapi/validation
- pkg/kubectl/plugins - pkg/kubectl/plugins
- pkg/kubectl/resource - pkg/kubectl/resource
- pkg/kubectl/util - pkg/kubectl/util
- pkg/kubectl/util/hash
- pkg/kubectl/util/slice
- pkg/kubectl/validation
- pkg/kubelet/apis - pkg/kubelet/apis
- pkg/kubelet/qos - pkg/kubelet/qos
- pkg/kubelet/types - pkg/kubelet/types
@ -510,16 +695,18 @@ imports:
- pkg/registry/rbac/validation - pkg/registry/rbac/validation
- pkg/security/apparmor - pkg/security/apparmor
- pkg/serviceaccount - pkg/serviceaccount
- pkg/util - pkg/util/file
- pkg/util/exec
- pkg/util/hash - pkg/util/hash
- pkg/util/io
- pkg/util/labels - pkg/util/labels
- pkg/util/metrics - pkg/util/metrics
- pkg/util/mount - pkg/util/mount
- pkg/util/net/sets - pkg/util/net/sets
- pkg/util/node - pkg/util/node
- pkg/util/parsers - pkg/util/parsers
- pkg/util/pointer
- pkg/util/slice - pkg/util/slice
- pkg/util/taints
- pkg/version - pkg/version
- pkg/volume/util - pkg/volume/util
- pkg/watch/json - pkg/watch/json
@ -529,6 +716,7 @@ imports:
- plugin/pkg/scheduler/api - plugin/pkg/scheduler/api
- plugin/pkg/scheduler/schedulercache - plugin/pkg/scheduler/schedulercache
- plugin/pkg/scheduler/util - plugin/pkg/scheduler/util
- staging/src/k8s.io/apimachinery/pkg/util/rand
- name: k8s.io/metrics - name: k8s.io/metrics
version: 8efbc8e22d00b9c600afec5f1c14073fd2412fce version: 8efbc8e22d00b9c600afec5f1c14073fd2412fce
subpackages: subpackages:
@ -537,6 +725,11 @@ imports:
- pkg/client/clientset_generated/clientset - pkg/client/clientset_generated/clientset
- pkg/client/clientset_generated/clientset/scheme - pkg/client/clientset_generated/clientset/scheme
- pkg/client/clientset_generated/clientset/typed/metrics/v1alpha1 - pkg/client/clientset_generated/clientset/typed/metrics/v1alpha1
- name: k8s.io/utils
version: 9fdc871a36f37980dd85f96d576b20d564cc0784
subpackages:
- exec
- exec/testing
- name: vbom.ml/util - name: vbom.ml/util
version: db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394 version: db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394
repo: https://github.com/fvbommel/util.git repo: https://github.com/fvbommel/util.git

@ -14,32 +14,38 @@ import:
- package: github.com/imdario/mergo - package: github.com/imdario/mergo
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- package: github.com/Masterminds/sprig - package: github.com/Masterminds/sprig
version: ^2.13 version: ^2.14
- package: github.com/ghodss/yaml - package: github.com/ghodss/yaml
- package: github.com/Masterminds/semver - package: github.com/Masterminds/semver
version: ~1.3.1 version: ~1.3.1
- package: github.com/technosophos/moniker - package: github.com/technosophos/moniker
- package: github.com/golang/protobuf - package: github.com/golang/protobuf
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef version: 4bd1920723d7b7c925de087aa32e2187708897f7
subpackages: subpackages:
- proto - proto
- ptypes/any - ptypes/any
- ptypes/timestamp - ptypes/timestamp
- package: google.golang.org/grpc - package: google.golang.org/grpc
version: 1.2.1 version: 1.2.1
# 1.8.1 libs are hosed and need some manual intervention, so pinning to 1.8.0 for now
# so others aren't getting errors when running `glide up`
- package: k8s.io/kubernetes - package: k8s.io/kubernetes
version: ~1.7.0 version: 1.8.0
- package: github.com/gosuri/uitable - package: github.com/gosuri/uitable
- package: github.com/asaskevich/govalidator - package: github.com/asaskevich/govalidator
version: ^4.0.0 version: ^4.0.0
- package: golang.org/x/crypto - package: golang.org/x/crypto
subpackages: subpackages:
- openpgp - openpgp
# pin version of golang.org/x/sys that is compatible with golang.org/x/crypto
- package: golang.org/x/sys
version: 43eea11
subpackages:
- unix
- windows
- package: github.com/gobwas/glob - package: github.com/gobwas/glob
version: ^0.2.1 version: ^0.2.1
- package: github.com/evanphx/json-patch - package: github.com/evanphx/json-patch
- package: github.com/facebookgo/atomicfile
- package: github.com/facebookgo/symwalk
- package: github.com/BurntSushi/toml - package: github.com/BurntSushi/toml
version: ~0.3.0 version: ~0.3.0
- package: github.com/naoina/go-stringutil - package: github.com/naoina/go-stringutil
@ -50,37 +56,6 @@ import:
- package: vbom.ml/util - package: vbom.ml/util
repo: https://github.com/fvbommel/util.git repo: https://github.com/fvbommel/util.git
vcs: git vcs: git
- package: github.com/docker/distribution
version: ~2.4.0
# hacks for kubernetes v1.7
- package: cloud.google.com/go
- package: github.com/Azure/go-autorest
version: d7c034a8af24eda120dd6460bfcd6d9ed14e43ca
- package: github.com/dgrijalva/jwt-go
- package: github.com/docker/spdystream
- package: github.com/go-openapi/analysis
version: b44dc874b601d9e4e2f6e19140e794ba24bead3b
- package: github.com/go-openapi/errors
version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc
- package: github.com/go-openapi/loads
version: 18441dfa706d924a39a030ee2c3b1d8d81917b38
- package: github.com/go-openapi/spec
version: 6aced65f8501fe1217321abf0749d354824ba2ff
- package: github.com/google/gofuzz
- package: github.com/hashicorp/golang-lru
- package: github.com/howeyc/gopass
- package: github.com/juju/ratelimit
version: 5b9ff866471762aa2ab2dced63c9fb6f53921342
- package: github.com/pborman/uuid
- package: golang.org/x/oauth2
- package: gopkg.in/inf.v0
- package: github.com/go-openapi/strfmt
- package: github.com/mitchellh/mapstructure
- package: gopkg.in/mgo.v2/bson
ignore:
- k8s.io/client-go
- k8s.io/apimachinery
testImports: testImports:
- package: github.com/stretchr/testify - package: github.com/stretchr/testify

@ -21,6 +21,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
) )
@ -111,14 +112,14 @@ const defaultIgnore = `# Patterns to ignore when building packages.
` `
const defaultIngress = `{{- if .Values.ingress.enabled -}} const defaultIngress = `{{- if .Values.ingress.enabled -}}
{{- $serviceName := include "fullname" . -}} {{- $serviceName := include "<CHARTNAME>.fullname" . -}}
{{- $servicePort := .Values.service.externalPort -}} {{- $servicePort := .Values.service.externalPort -}}
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "<CHARTNAME>.fullname" . }}
labels: labels:
app: {{ template "name" . }} app: {{ template "<CHARTNAME>.name" . }}
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
@ -147,9 +148,9 @@ spec:
const defaultDeployment = `apiVersion: extensions/v1beta1 const defaultDeployment = `apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "<CHARTNAME>.fullname" . }}
labels: labels:
app: {{ template "name" . }} app: {{ template "<CHARTNAME>.name" . }}
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
@ -158,7 +159,7 @@ spec:
template: template:
metadata: metadata:
labels: labels:
app: {{ template "name" . }} app: {{ template "<CHARTNAME>.name" . }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
spec: spec:
containers: containers:
@ -186,9 +187,9 @@ spec:
const defaultService = `apiVersion: v1 const defaultService = `apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: {{ template "fullname" . }} name: {{ template "<CHARTNAME>.fullname" . }}
labels: labels:
app: {{ template "name" . }} app: {{ template "<CHARTNAME>.name" . }}
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
@ -200,7 +201,7 @@ spec:
protocol: TCP protocol: TCP
name: {{ .Values.service.name }} name: {{ .Values.service.name }}
selector: selector:
app: {{ template "name" . }} app: {{ template "<CHARTNAME>.name" . }}
release: {{ .Release.Name }} release: {{ .Release.Name }}
` `
@ -210,16 +211,16 @@ const defaultNotes = `1. Get the application URL by running these commands:
http://{{ . }} http://{{ . }}
{{- end }} {{- end }}
{{- else if contains "NodePort" .Values.service.type }} {{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "<CHARTNAME>.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }} {{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available. NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}' You can watch the status of by running 'kubectl get svc -w {{ template "<CHARTNAME>.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "<CHARTNAME>.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.externalPort }} echo http://$SERVICE_IP:{{ .Values.service.externalPort }}
{{- else if contains "ClusterIP" .Values.service.type }} {{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "<CHARTNAME>.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application" echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:{{ .Values.service.internalPort }} kubectl port-forward $POD_NAME 8080:{{ .Values.service.internalPort }}
{{- end }} {{- end }}
@ -229,7 +230,7 @@ const defaultHelpers = `{{/* vim: set filetype=mustache: */}}
{{/* {{/*
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "name" -}} {{- define "<CHARTNAME>.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
@ -237,7 +238,7 @@ Expand the name of the chart.
Create a default fully qualified app name. Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}} */}}
{{- define "fullname" -}} {{- define "<CHARTNAME>.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}} {{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
@ -318,27 +319,27 @@ func Create(chartfile *chart.Metadata, dir string) (string, error) {
{ {
// ingress.yaml // ingress.yaml
path: filepath.Join(cdir, TemplatesDir, IngressFileName), path: filepath.Join(cdir, TemplatesDir, IngressFileName),
content: []byte(defaultIngress), content: []byte(strings.Replace(defaultIngress, "<CHARTNAME>", chartfile.Name, -1)),
}, },
{ {
// deployment.yaml // deployment.yaml
path: filepath.Join(cdir, TemplatesDir, DeploymentName), path: filepath.Join(cdir, TemplatesDir, DeploymentName),
content: []byte(defaultDeployment), content: []byte(strings.Replace(defaultDeployment, "<CHARTNAME>", chartfile.Name, -1)),
}, },
{ {
// service.yaml // service.yaml
path: filepath.Join(cdir, TemplatesDir, ServiceName), path: filepath.Join(cdir, TemplatesDir, ServiceName),
content: []byte(defaultService), content: []byte(strings.Replace(defaultService, "<CHARTNAME>", chartfile.Name, -1)),
}, },
{ {
// NOTES.txt // NOTES.txt
path: filepath.Join(cdir, TemplatesDir, NotesName), path: filepath.Join(cdir, TemplatesDir, NotesName),
content: []byte(defaultNotes), content: []byte(strings.Replace(defaultNotes, "<CHARTNAME>", chartfile.Name, -1)),
}, },
{ {
// _helpers.tpl // _helpers.tpl
path: filepath.Join(cdir, TemplatesDir, HelpersName), path: filepath.Join(cdir, TemplatesDir, HelpersName),
content: []byte(defaultHelpers), content: []byte(strings.Replace(defaultHelpers, "<CHARTNAME>", chartfile.Name, -1)),
}, },
} }

@ -28,7 +28,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/facebookgo/symwalk"
"github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/any"
"k8s.io/helm/pkg/ignore" "k8s.io/helm/pkg/ignore"
@ -244,7 +243,7 @@ func LoadDir(dir string) (*chart.Chart, error) {
files := []*BufferedFile{} files := []*BufferedFile{}
topdir += string(filepath.Separator) topdir += string(filepath.Separator)
err = symwalk.Walk(topdir, func(name string, fi os.FileInfo, err error) error { err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error {
n := strings.TrimPrefix(name, topdir) n := strings.TrimPrefix(name, topdir)
// Normalize to / since it will also work on Windows // Normalize to / since it will also work on Windows

@ -23,6 +23,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/version"
) )
const ( const (
@ -230,7 +231,7 @@ func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Ch
if existingChart.Metadata.Name != aliasChart.Name { if existingChart.Metadata.Name != aliasChart.Name {
continue continue
} }
if existingChart.Metadata.Version != aliasChart.Version { if !version.IsCompatibleRange(aliasChart.Version, existingChart.Metadata.Version) {
continue continue
} }
chartFound = *existingChart chartFound = *existingChart
@ -266,7 +267,7 @@ func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error {
for _, existingDependency := range c.Dependencies { for _, existingDependency := range c.Dependencies {
var dependencyFound bool var dependencyFound bool
for _, req := range reqs.Dependencies { for _, req := range reqs.Dependencies {
if existingDependency.Metadata.Name == req.Name && existingDependency.Metadata.Version == req.Version { if existingDependency.Metadata.Name == req.Name && version.IsCompatibleRange(req.Version, existingDependency.Metadata.Version) {
dependencyFound = true dependencyFound = true
break break
} }

@ -21,6 +21,7 @@ import (
"strconv" "strconv"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/version"
) )
func TestLoadRequirements(t *testing.T) { func TestLoadRequirements(t *testing.T) {
@ -347,11 +348,24 @@ func TestGetAliasDependency(t *testing.T) {
t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.Name) t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.Name)
} }
if req.Dependencies[0].Version != "" {
if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) {
t.Fatalf("Dependency chart version is not in the compatible range")
}
}
// Failure case // Failure case
req.Dependencies[0].Name = "something-else" req.Dependencies[0].Name = "something-else"
if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil {
t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name)
} }
req.Dependencies[0].Version = "something else which is not in the compatible range"
if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) {
t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ")
}
} }
func TestDependentChartAliases(t *testing.T) { func TestDependentChartAliases(t *testing.T) {

@ -217,7 +217,12 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, ge
// If the URL is relative (no scheme), prepend the chart repo's base URL // If the URL is relative (no scheme), prepend the chart repo's base URL
if !u.IsAbs() { if !u.IsAbs() {
u, err = url.Parse(rc.URL + "/" + u.Path) path := u.Path
u, err = url.Parse(rc.URL)
if err != nil {
return u, r.Client, err
}
u.Path = u.Path + path
return u, r.Client, err return u, r.Client, err
} }

@ -43,6 +43,7 @@ func TestResolveChartRef(t *testing.T) {
{name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"}, {name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"},
{name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"}, {name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"},
{name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"}, {name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"},
{name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"},
{name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true}, {name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true},
{name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true}, {name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true},
{name: "invalid", ref: "invalid-1.2.3", fail: true}, {name: "invalid", ref: "invalid-1.2.3", fail: true},

@ -0,0 +1,16 @@
apiVersion: v1
entries:
alpine:
- name: alpine
urls:
- alpine-1.2.3.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
home: https://k8s.io/helm
sources:
- https://github.com/kubernetes/helm
version: 1.2.3
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
engine: ""
icon: ""

@ -10,3 +10,5 @@ repositories:
url: "http://example.com/charts" url: "http://example.com/charts"
- name: malformed - name: malformed
url: "http://dl.example.com" url: "http://dl.example.com"
- name: testing-querystring
url: "http://example.com?key=value"

@ -17,7 +17,7 @@ limitations under the License.
package portforwarder package portforwarder
import ( import (
"k8s.io/client-go/pkg/api/v1" "k8s.io/api/core/v1"
) )
// These functions are adapted from the "kubernetes" repository's file // These functions are adapted from the "kubernetes" repository's file

@ -19,11 +19,11 @@ package portforwarder
import ( import (
"fmt" "fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"

@ -19,9 +19,9 @@ package portforwarder
import ( import (
"testing" "testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/pkg/api/v1"
) )
func mockTillerPod() v1.Pod { func mockTillerPod() v1.Pod {

@ -27,6 +27,10 @@ import (
"time" "time"
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
apps "k8s.io/api/apps/v1beta2"
batch "k8s.io/api/batch/v1"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -40,15 +44,12 @@ import (
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/helper" "k8s.io/kubernetes/pkg/api/helper"
"k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
batchinternal "k8s.io/kubernetes/pkg/apis/batch" batchinternal "k8s.io/kubernetes/pkg/apis/batch"
batch "k8s.io/kubernetes/pkg/apis/batch/v1"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
conditions "k8s.io/kubernetes/pkg/client/unversioned" conditions "k8s.io/kubernetes/pkg/client/unversioned"
"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/kubectl/validation"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
) )
@ -103,13 +104,9 @@ func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shoul
} }
func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result {
schema, err := c.Validator(true, c.SchemaCacheDir)
if err != nil {
c.Log("warning: failed to load schema: %s", err)
}
return c.NewBuilder(true). return c.NewBuilder(true).
ContinueOnError(). ContinueOnError().
Schema(schema). Schema(c.validator()).
NamespaceParam(namespace). NamespaceParam(namespace).
DefaultNamespace(). DefaultNamespace().
Stream(reader, ""). Stream(reader, "").
@ -117,20 +114,25 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result
Do() Do()
} }
// BuildUnstructured validates for Kubernetes objects and returns unstructured infos. func (c *Client) validator() validation.Schema {
func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { const openapi = false // only works on v1.8 clusters
schema, err := c.Validator(true, c.SchemaCacheDir) schema, err := c.Validator(true, openapi, c.SchemaCacheDir)
if err != nil { if err != nil {
c.Log("warning: failed to load schema: %s", err) c.Log("warning: failed to load schema: %s", err)
} }
return schema
}
// BuildUnstructured validates for Kubernetes objects and returns unstructured infos.
func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) {
var result Result var result Result
b, err := c.NewUnstructuredBuilder(true) b, err := c.NewUnstructuredBuilder(true)
if err != nil { if err != nil {
return result, err return result, err
} }
result, err = b.ContinueOnError(). result, err = b.ContinueOnError().
Schema(schema). Schema(c.validator()).
NamespaceParam(namespace). NamespaceParam(namespace).
DefaultNamespace(). DefaultNamespace().
Stream(reader, ""). Stream(reader, "").
@ -157,6 +159,9 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
var objPods = make(map[string][]api.Pod)
missing := []string{} missing := []string{}
err = perform(infos, func(info *resource.Info) error { err = perform(infos, func(info *resource.Info) error {
c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name)
@ -171,12 +176,26 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
gvk := info.ResourceMapping().GroupVersionKind gvk := info.ResourceMapping().GroupVersionKind
vk := gvk.Version + "/" + gvk.Kind vk := gvk.Version + "/" + gvk.Kind
objs[vk] = append(objs[vk], info.Object) objs[vk] = append(objs[vk], info.Object)
//Get the relation pods
objPods, err = c.getSelectRelationPod(info, objPods)
if err != nil {
c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
}
return nil return nil
}) })
if err != nil { if err != nil {
return "", err return "", err
} }
//here, we will add the objPods to the objs
for key, podItems := range objPods {
for i := range podItems {
objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i])
}
}
// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so // Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
// spin through them and print them. Printer is cool since it prints the header only when // spin through them and print them. Printer is cool since it prints the header only when
// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep // an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
@ -628,3 +647,67 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf
return err return err
} }
//get an kubernetes resources's relation pods
// kubernetes resource used select labels to relate pods
func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]api.Pod) (map[string][]api.Pod, error) {
if info == nil {
return objPods, nil
}
c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
versioned, err := c.AsVersionedObject(info.Object)
if runtime.IsNotRegisteredError(err) {
return objPods, nil
}
if err != nil {
return objPods, err
}
// We can ignore this error because it will only error if it isn't a type that doesn't
// have pods. In that case, we don't care
selector, _ := getSelectorFromObject(versioned)
selectorString := labels.Set(selector).AsSelector().String()
// If we have an empty selector, this likely is a service or config map, so bail out now
if selectorString == "" {
return objPods, nil
}
client, _ := c.ClientSet()
pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{
FieldSelector: fields.Everything().String(),
LabelSelector: labels.Set(selector).AsSelector().String(),
})
if err != nil {
return objPods, err
}
for _, pod := range pods.Items {
if pod.APIVersion == "" {
pod.APIVersion = "v1"
}
if pod.Kind == "" {
pod.Kind = "Pod"
}
vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind
if !isFoundPod(objPods[vk], pod) {
objPods[vk] = append(objPods[vk], pod)
}
}
return objPods, nil
}
func isFoundPod(podItem []api.Pod, pod api.Pod) bool {
for _, value := range podItem {
if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) {
return true
}
}
return false
}

@ -35,11 +35,11 @@ import (
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
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/kubectl/validation"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
watchjson "k8s.io/kubernetes/pkg/watch/json" watchjson "k8s.io/kubernetes/pkg/watch/json"
) )

@ -83,5 +83,5 @@ func (r Result) Intersect(rs Result) Result {
// isMatchingInfo returns true if infos match on Name and GroupVersionKind. // isMatchingInfo returns true if infos match on Name and GroupVersionKind.
func isMatchingInfo(a, b *resource.Info) bool { func isMatchingInfo(a, b *resource.Info) bool {
return a.Name == b.Name && a.Mapping.GroupVersionKind == b.Mapping.GroupVersionKind return a.Name == b.Name && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind
} }

@ -21,11 +21,12 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http"
"strconv" "strconv"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward" "k8s.io/client-go/tools/portforward"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/transport/spdy"
) )
// Tunnel describes a ssh-like tunnel to a kubernetes pod // Tunnel describes a ssh-like tunnel to a kubernetes pod
@ -71,10 +72,11 @@ func (t *Tunnel) ForwardPort() error {
Name(t.PodName). Name(t.PodName).
SubResource("portforward").URL() SubResource("portforward").URL()
dialer, err := remotecommand.NewExecutor(t.config, "POST", u) transport, upgrader, err := spdy.RoundTripperFor(t.config)
if err != nil { if err != nil {
return err return err
} }
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", u)
local, err := getAvailablePort() local, err := getAvailablePort()
if err != nil { if err != nil {

@ -19,20 +19,18 @@ package kube // import "k8s.io/helm/pkg/kube"
import ( import (
"time" "time"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
"k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/api/v1/helper" "k8s.io/kubernetes/pkg/api/v1/helper"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1"
internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
) )
@ -47,11 +45,10 @@ type deployment struct {
func (c *Client) waitForResources(timeout time.Duration, created Result) error { func (c *Client) waitForResources(timeout time.Duration, created Result) error {
c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout) c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout)
cs, err := c.ClientSet() kcs, err := c.KubernetesClientSet()
if err != nil { if err != nil {
return err return err
} }
client := versionedClientsetForDeployment(cs)
return wait.Poll(2*time.Second, timeout, func() (bool, error) { return wait.Poll(2*time.Second, timeout, func() (bool, error) {
pods := []v1.Pod{} pods := []v1.Pod{}
services := []v1.Service{} services := []v1.Service{}
@ -64,24 +61,24 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error {
} }
switch value := obj.(type) { switch value := obj.(type) {
case (*v1.ReplicationController): case (*v1.ReplicationController):
list, err := getPods(client, value.Namespace, value.Spec.Selector) list, err := getPods(kcs, value.Namespace, value.Spec.Selector)
if err != nil { if err != nil {
return false, err return false, err
} }
pods = append(pods, list...) pods = append(pods, list...)
case (*v1.Pod): case (*v1.Pod):
pod, err := client.Core().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{}) pod, err := kcs.Core().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return false, err return false, err
} }
pods = append(pods, *pod) pods = append(pods, *pod)
case (*extensions.Deployment): case (*extensions.Deployment):
currentDeployment, err := client.Extensions().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) currentDeployment, err := kcs.Extensions().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return false, err return false, err
} }
// Find RS associated with deployment // Find RS associated with deployment
newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, client) newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
if err != nil || newReplicaSet == nil { if err != nil || newReplicaSet == nil {
return false, err return false, err
} }
@ -91,53 +88,59 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error {
} }
deployments = append(deployments, newDeployment) deployments = append(deployments, newDeployment)
case (*extensions.DaemonSet): case (*extensions.DaemonSet):
list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
if err != nil { if err != nil {
return false, err return false, err
} }
pods = append(pods, list...) pods = append(pods, list...)
case (*apps.StatefulSet): case (*appsv1beta1.StatefulSet):
list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
if err != nil {
return false, err
}
pods = append(pods, list...)
case (*appsv1beta2.StatefulSet):
list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
if err != nil { if err != nil {
return false, err return false, err
} }
pods = append(pods, list...) pods = append(pods, list...)
case (*extensions.ReplicaSet): case (*extensions.ReplicaSet):
list, err := getPods(client, value.Namespace, value.Spec.Selector.MatchLabels) list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
if err != nil { if err != nil {
return false, err return false, err
} }
pods = append(pods, list...) pods = append(pods, list...)
case (*v1.PersistentVolumeClaim): case (*v1.PersistentVolumeClaim):
claim, err := client.Core().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{}) claim, err := kcs.Core().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return false, err return false, err
} }
pvc = append(pvc, *claim) pvc = append(pvc, *claim)
case (*v1.Service): case (*v1.Service):
svc, err := client.Core().Services(value.Namespace).Get(value.Name, metav1.GetOptions{}) svc, err := kcs.Core().Services(value.Namespace).Get(value.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return false, err return false, err
} }
services = append(services, *svc) services = append(services, *svc)
} }
} }
isReady := podsReady(pods) && servicesReady(services) && volumesReady(pvc) && deploymentsReady(deployments) isReady := c.podsReady(pods) && c.servicesReady(services) && c.volumesReady(pvc) && c.deploymentsReady(deployments)
c.Log("resources ready: %v", isReady)
return isReady, nil return isReady, nil
}) })
} }
func podsReady(pods []v1.Pod) bool { func (c *Client) podsReady(pods []v1.Pod) bool {
for _, pod := range pods { for _, pod := range pods {
if !podutil.IsPodReady(&pod) { if !podutil.IsPodReady(&pod) {
c.Log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName())
return false return false
} }
} }
return true return true
} }
func servicesReady(svc []v1.Service) bool { func (c *Client) servicesReady(svc []v1.Service) bool {
for _, s := range svc { for _, s := range svc {
// ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set)
if s.Spec.Type == v1.ServiceTypeExternalName { if s.Spec.Type == v1.ServiceTypeExternalName {
@ -146,48 +149,42 @@ func servicesReady(svc []v1.Service) bool {
// Make sure the service is not explicitly set to "None" before checking the IP // Make sure the service is not explicitly set to "None" before checking the IP
if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) { if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) {
c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName())
return false return false
} }
// This checks if the service has a LoadBalancer and that balancer has an Ingress defined // This checks if the service has a LoadBalancer and that balancer has an Ingress defined
if s.Spec.Type == v1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil { if s.Spec.Type == v1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil {
c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName())
return false return false
} }
} }
return true return true
} }
func volumesReady(vols []v1.PersistentVolumeClaim) bool { func (c *Client) volumesReady(vols []v1.PersistentVolumeClaim) bool {
for _, v := range vols { for _, v := range vols {
if v.Status.Phase != v1.ClaimBound { if v.Status.Phase != v1.ClaimBound {
c.Log("PersistentVolumeClaim is not ready: %s/%s", v.GetNamespace(), v.GetName())
return false return false
} }
} }
return true return true
} }
func deploymentsReady(deployments []deployment) bool { func (c *Client) deploymentsReady(deployments []deployment) bool {
for _, v := range deployments { for _, v := range deployments {
if !(v.replicaSets.Status.ReadyReplicas >= *v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) { if !(v.replicaSets.Status.ReadyReplicas >= *v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) {
c.Log("Deployment is not ready: %s/%s", v.deployment.GetNamespace(), v.deployment.GetName())
return false return false
} }
} }
return true return true
} }
func getPods(client clientset.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) { func getPods(client kubernetes.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) {
list, err := client.Core().Pods(namespace).List(metav1.ListOptions{ list, err := client.Core().Pods(namespace).List(metav1.ListOptions{
FieldSelector: fields.Everything().String(), FieldSelector: fields.Everything().String(),
LabelSelector: labels.Set(selector).AsSelector().String(), LabelSelector: labels.Set(selector).AsSelector().String(),
}) })
return list.Items, err return list.Items, err
} }
func versionedClientsetForDeployment(internalClient internalclientset.Interface) clientset.Interface {
if internalClient == nil {
return &clientset.Clientset{}
}
return &clientset.Clientset{
CoreV1Client: core.New(internalClient.Core().RESTClient()),
ExtensionsV1beta1Client: extensionsclient.New(internalClient.Extensions().RESTClient()),
}
}

@ -68,7 +68,7 @@ func TestBadChart(t *testing.T) {
func TestInvalidYaml(t *testing.T) { func TestInvalidYaml(t *testing.T) {
m := All(badYamlFileDir).Messages m := All(badYamlFileDir).Messages
if len(m) != 1 { if len(m) != 1 {
t.Errorf("All didn't fail with expected errors, got %#v", m) t.Fatalf("All didn't fail with expected errors, got %#v", m)
} }
if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("All didn't have the error for deliberateSyntaxError") t.Errorf("All didn't have the error for deliberateSyntaxError")
@ -78,7 +78,7 @@ func TestInvalidYaml(t *testing.T) {
func TestBadValues(t *testing.T) { func TestBadValues(t *testing.T) {
m := All(badValuesFileDir).Messages m := All(badValuesFileDir).Messages
if len(m) != 1 { if len(m) != 1 {
t.Errorf("All didn't fail with expected errors, got %#v", m) t.Fatalf("All didn't fail with expected errors, got %#v", m)
} }
if !strings.Contains(m[0].Err.Error(), "cannot unmarshal") { if !strings.Contains(m[0].Err.Error(), "cannot unmarshal") {
t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err)

@ -62,7 +62,7 @@ func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository,
} }
client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile) client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not construct protocol handler for: %s", u.Scheme) return nil, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err)
} }
return &ChartRepository{ return &ChartRepository{
@ -110,8 +110,13 @@ func (r *ChartRepository) Load() error {
// is for pre-2.2.0 repo files. // is for pre-2.2.0 repo files.
func (r *ChartRepository) DownloadIndexFile(cachePath string) error { func (r *ChartRepository) DownloadIndexFile(cachePath string) error {
var indexURL string var indexURL string
parsedURL, err := url.Parse(r.Config.URL)
if err != nil {
return err
}
parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml"
indexURL = strings.TrimSuffix(r.Config.URL, "/") + "/index.yaml" indexURL = parsedURL.String()
resp, err := r.Client.Get(indexURL) resp, err := r.Client.Get(indexURL)
if err != nil { if err != nil {
return err return err

@ -23,7 +23,6 @@ import (
"os" "os"
"time" "time"
"github.com/facebookgo/atomicfile"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
) )
@ -135,20 +134,9 @@ func (r *RepoFile) Remove(name string) bool {
// WriteFile writes a repositories file to the given path. // WriteFile writes a repositories file to the given path.
func (r *RepoFile) WriteFile(path string, perm os.FileMode) error { func (r *RepoFile) WriteFile(path string, perm os.FileMode) error {
f, err := atomicfile.New(path, perm)
if err != nil {
return err
}
data, err := yaml.Marshal(r) data, err := yaml.Marshal(r)
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(path, data, perm)
_, err = f.File.Write(data)
if err != nil {
return err
}
return f.Close()
} }

@ -201,18 +201,10 @@ func TestWriteFile(t *testing.T) {
t.Errorf("failed to create test-file (%v)", err) t.Errorf("failed to create test-file (%v)", err)
} }
defer os.Remove(repoFile.Name()) defer os.Remove(repoFile.Name())
if err := sampleRepository.WriteFile(repoFile.Name(), 744); err != nil {
fileMode := os.FileMode(0744)
if err := sampleRepository.WriteFile(repoFile.Name(), fileMode); err != nil {
t.Errorf("failed to write file (%v)", err) t.Errorf("failed to write file (%v)", err)
} }
info, _ := os.Stat(repoFile.Name())
mode := info.Mode()
if mode != fileMode {
t.Errorf("incorrect file mode: %s (expected %s)", mode, fileMode)
}
repos, err := LoadRepositoriesFile(repoFile.Name()) repos, err := LoadRepositoriesFile(repoFile.Name())
if err != nil { if err != nil {
t.Errorf("failed to load file (%v)", err) t.Errorf("failed to load file (%v)", err)

@ -17,16 +17,11 @@ limitations under the License.
package driver // import "k8s.io/helm/pkg/storage/driver" package driver // import "k8s.io/helm/pkg/storage/driver"
import ( import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt" "fmt"
"io/ioutil"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/golang/protobuf/proto"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kblabels "k8s.io/apimachinery/pkg/labels" kblabels "k8s.io/apimachinery/pkg/labels"
@ -42,10 +37,6 @@ var _ Driver = (*ConfigMaps)(nil)
// ConfigMapsDriverName is the string name of the driver. // ConfigMapsDriverName is the string name of the driver.
const ConfigMapsDriverName = "ConfigMap" const ConfigMapsDriverName = "ConfigMap"
var b64 = base64.StdEncoding
var magicGzip = []byte{0x1f, 0x8b, 0x08}
// ConfigMaps is a wrapper around an implementation of a kubernetes // ConfigMaps is a wrapper around an implementation of a kubernetes
// ConfigMapsInterface. // ConfigMapsInterface.
type ConfigMaps struct { type ConfigMaps struct {
@ -265,57 +256,3 @@ func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*api.Config
Data: map[string]string{"release": s}, Data: map[string]string{"release": s},
}, nil }, nil
} }
// encodeRelease encodes a release returning a base64 encoded
// gzipped binary protobuf encoding representation, or error.
func encodeRelease(rls *rspb.Release) (string, error) {
b, err := proto.Marshal(rls)
if err != nil {
return "", err
}
var buf bytes.Buffer
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return "", err
}
if _, err = w.Write(b); err != nil {
return "", err
}
w.Close()
return b64.EncodeToString(buf.Bytes()), nil
}
// decodeRelease decodes the bytes in data into a release
// type. Data must contain a base64 encoded string of a
// valid protobuf encoding of a release, otherwise
// an error is returned.
func decodeRelease(data string) (*rspb.Release, error) {
// base64 decode string
b, err := b64.DecodeString(data)
if err != nil {
return nil, err
}
// For backwards compatibility with releases that were stored before
// compression was introduced we skip decompression if the
// gzip magic header is not found
if bytes.Equal(b[0:3], magicGzip) {
r, err := gzip.NewReader(bytes.NewReader(b))
if err != nil {
return nil, err
}
b2, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
b = b2
}
var rls rspb.Release
// unmarshal protobuf bytes
if err := proto.Unmarshal(b, &rls); err != nil {
return nil, err
}
return &rls, nil
}

@ -142,3 +142,81 @@ func (mock *MockConfigMapsInterface) Delete(name string, opts *metav1.DeleteOpti
delete(mock.objects, name) delete(mock.objects, name)
return nil return nil
} }
// newTestFixture initializes a MockSecretsInterface.
// Secrets are created for each release provided.
func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets {
var mock MockSecretsInterface
mock.Init(t, releases...)
return NewSecrets(&mock)
}
// MockSecretsInterface mocks a kubernetes SecretsInterface
type MockSecretsInterface struct {
internalversion.SecretInterface
objects map[string]*api.Secret
}
// Init initializes the MockSecretsInterface with the set of releases.
func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) {
mock.objects = map[string]*api.Secret{}
for _, rls := range releases {
objkey := testKey(rls.Name, rls.Version)
secret, err := newSecretsObject(objkey, rls, nil)
if err != nil {
t.Fatalf("Failed to create secret: %s", err)
}
mock.objects[objkey] = secret
}
}
// Get returns the Secret by name.
func (mock *MockSecretsInterface) Get(name string, options metav1.GetOptions) (*api.Secret, error) {
object, ok := mock.objects[name]
if !ok {
return nil, apierrors.NewNotFound(api.Resource("tests"), name)
}
return object, nil
}
// List returns the a of Secret.
func (mock *MockSecretsInterface) List(opts metav1.ListOptions) (*api.SecretList, error) {
var list api.SecretList
for _, secret := range mock.objects {
list.Items = append(list.Items, *secret)
}
return &list, nil
}
// Create creates a new Secret.
func (mock *MockSecretsInterface) Create(secret *api.Secret) (*api.Secret, error) {
name := secret.ObjectMeta.Name
if object, ok := mock.objects[name]; ok {
return object, apierrors.NewAlreadyExists(api.Resource("tests"), name)
}
mock.objects[name] = secret
return secret, nil
}
// Update updates a Secret.
func (mock *MockSecretsInterface) Update(secret *api.Secret) (*api.Secret, error) {
name := secret.ObjectMeta.Name
if _, ok := mock.objects[name]; !ok {
return nil, apierrors.NewNotFound(api.Resource("tests"), name)
}
mock.objects[name] = secret
return secret, nil
}
// Delete deletes a Secret by name.
func (mock *MockSecretsInterface) Delete(name string, opts *metav1.DeleteOptions) error {
if _, ok := mock.objects[name]; !ok {
return apierrors.NewNotFound(api.Resource("tests"), name)
}
delete(mock.objects, name)
return nil
}

@ -20,6 +20,8 @@ import (
"sort" "sort"
"strconv" "strconv"
"github.com/golang/protobuf/proto"
rspb "k8s.io/helm/pkg/proto/hapi/release" rspb "k8s.io/helm/pkg/proto/hapi/release"
) )
@ -129,5 +131,5 @@ func newRecord(key string, rls *rspb.Release) *record {
lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
lbs.set("VERSION", strconv.Itoa(int(rls.Version))) lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
return &record{key: key, lbs: lbs, rls: rls} return &record{key: key, lbs: lbs, rls: proto.Clone(rls).(*rspb.Release)}
} }

@ -0,0 +1,258 @@
/*
Copyright 2017 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
import (
"fmt"
"strconv"
"strings"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kblabels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
var _ Driver = (*Secrets)(nil)
// SecretsDriverName is the string name of the driver.
const SecretsDriverName = "Secret"
// Secrets is a wrapper around an implementation of a kubernetes
// SecretsInterface.
type Secrets struct {
impl internalversion.SecretInterface
Log func(string, ...interface{})
}
// NewSecrets initializes a new Secrets wrapping an implmenetation of
// the kubernetes SecretsInterface.
func NewSecrets(impl internalversion.SecretInterface) *Secrets {
return &Secrets{
impl: impl,
Log: func(_ string, _ ...interface{}) {},
}
}
// Name returns the name of the driver.
func (secrets *Secrets) Name() string {
return SecretsDriverName
}
// Get fetches the release named by key. The corresponding release is returned
// or error if not found.
func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
// fetch the secret holding the release named by key
obj, err := secrets.impl.Get(key, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseNotFound(key)
}
secrets.Log("get: failed to get %q: %s", key, err)
return nil, err
}
// found the secret, decode the base64 data string
r, err := decodeRelease(string(obj.Data["release"]))
if err != nil {
secrets.Log("get: failed to decode data %q: %s", key, err)
return nil, err
}
// return the release object
return r, nil
}
// List fetches all releases and returns the list releases such
// that filter(release) == true. An error is returned if the
// secret fails to retrieve the releases.
func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
lsel := kblabels.Set{"OWNER": "TILLER"}.AsSelector()
opts := metav1.ListOptions{LabelSelector: lsel.String()}
list, err := secrets.impl.List(opts)
if err != nil {
secrets.Log("list: failed to list: %s", err)
return nil, err
}
var results []*rspb.Release
// iterate over the secrets object list
// and decode each release
for _, item := range list.Items {
rls, err := decodeRelease(string(item.Data["release"]))
if err != nil {
secrets.Log("list: failed to decode release: %v: %s", item, err)
continue
}
if filter(rls) {
results = append(results, rls)
}
}
return results, nil
}
// Query fetches all releases that match the provided map of labels.
// An error is returned if the secret fails to retrieve the releases.
func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) {
ls := kblabels.Set{}
for k, v := range labels {
if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; "))
}
ls[k] = v
}
opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()}
list, err := secrets.impl.List(opts)
if err != nil {
secrets.Log("query: failed to query with labels: %s", err)
return nil, err
}
if len(list.Items) == 0 {
return nil, ErrReleaseNotFound(labels["NAME"])
}
var results []*rspb.Release
for _, item := range list.Items {
rls, err := decodeRelease(string(item.Data["release"]))
if err != nil {
secrets.Log("query: failed to decode release: %s", err)
continue
}
results = append(results, rls)
}
return results, nil
}
// Create creates a new Secret holding the release. If the
// Secret already exists, ErrReleaseExists is returned.
func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
// set labels for secrets object meta data
var lbs labels
lbs.init()
lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix())))
// create a new secret to hold the release
obj, err := newSecretsObject(key, rls, lbs)
if err != nil {
secrets.Log("create: failed to encode release %q: %s", rls.Name, err)
return err
}
// push the secret object out into the kubiverse
if _, err := secrets.impl.Create(obj); err != nil {
if apierrors.IsAlreadyExists(err) {
return ErrReleaseExists(rls.Name)
}
secrets.Log("create: failed to create: %s", err)
return err
}
return nil
}
// Update updates the Secret holding the release. If not found
// the Secret is created to hold the release.
func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
// set labels for secrets object meta data
var lbs labels
lbs.init()
lbs.set("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix())))
// create a new secret object to hold the release
obj, err := newSecretsObject(key, rls, lbs)
if err != nil {
secrets.Log("update: failed to encode release %q: %s", rls.Name, err)
return err
}
// push the secret object out into the kubiverse
_, err = secrets.impl.Update(obj)
if err != nil {
secrets.Log("update: failed to update: %s", err)
return err
}
return nil
}
// Delete deletes the Secret holding the release named by key.
func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) {
// fetch the release to check existence
if rls, err = secrets.Get(key); err != nil {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseExists(rls.Name)
}
secrets.Log("delete: failed to get release %q: %s", key, err)
return nil, err
}
// delete the release
if err = secrets.impl.Delete(key, &metav1.DeleteOptions{}); err != nil {
return rls, err
}
return rls, nil
}
// newSecretsObject constructs a kubernetes Secret object
// to store a release. Each secret data entry is the base64
// encoded string of a release's binary protobuf encoding.
//
// The following labels are used within each secret:
//
// "MODIFIED_AT" - timestamp indicating when this secret was last modified. (set in Update)
// "CREATED_AT" - timestamp indicating when this secret was created. (set in Create)
// "VERSION" - version of the release.
// "STATUS" - status of the release (see proto/hapi/release.status.pb.go for variants)
// "OWNER" - owner of the secret, currently "TILLER".
// "NAME" - name of the release.
//
func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*api.Secret, error) {
const owner = "TILLER"
// encode the release
s, err := encodeRelease(rls)
if err != nil {
return nil, err
}
if lbs == nil {
lbs.init()
}
// apply labels
lbs.set("NAME", rls.Name)
lbs.set("OWNER", owner)
lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)])
lbs.set("VERSION", strconv.Itoa(int(rls.Version)))
// create and return secret object
return &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: key,
Labels: lbs.toMap(),
},
Data: map[string][]byte{"release": []byte(s)},
}, nil
}

@ -0,0 +1,186 @@
/*
Copyright 2017 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver
import (
"encoding/base64"
"reflect"
"testing"
"github.com/gogo/protobuf/proto"
"k8s.io/kubernetes/pkg/api"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
func TestSecretName(t *testing.T) {
c := newTestFixtureSecrets(t)
if c.Name() != SecretsDriverName {
t.Errorf("Expected name to be %q, got %q", SecretsDriverName, c.Name())
}
}
func TestSecretGet(t *testing.T) {
vers := int32(1)
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
// get release with key
got, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release: %s", err)
}
// compare fetched release with original
if !reflect.DeepEqual(rel, got) {
t.Errorf("Expected {%q}, got {%q}", rel, got)
}
}
func TestUNcompressedSecretGet(t *testing.T) {
vers := int32(1)
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED)
// Create a test fixture which contains an uncompressed release
secret, err := newSecretsObject(key, rel, nil)
if err != nil {
t.Fatalf("Failed to create secret: %s", err)
}
b, err := proto.Marshal(rel)
if err != nil {
t.Fatalf("Failed to marshal release: %s", err)
}
secret.Data["release"] = []byte(base64.StdEncoding.EncodeToString(b))
var mock MockSecretsInterface
mock.objects = map[string]*api.Secret{key: secret}
secrets := NewSecrets(&mock)
// get release with key
got, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release: %s", err)
}
// compare fetched release with original
if !reflect.DeepEqual(rel, got) {
t.Errorf("Expected {%q}, got {%q}", rel, got)
}
}
func TestSecretList(t *testing.T) {
secrets := newTestFixtureSecrets(t, []*rspb.Release{
releaseStub("key-1", 1, "default", rspb.Status_DELETED),
releaseStub("key-2", 1, "default", rspb.Status_DELETED),
releaseStub("key-3", 1, "default", rspb.Status_DEPLOYED),
releaseStub("key-4", 1, "default", rspb.Status_DEPLOYED),
releaseStub("key-5", 1, "default", rspb.Status_SUPERSEDED),
releaseStub("key-6", 1, "default", rspb.Status_SUPERSEDED),
}...)
// list all deleted releases
del, err := secrets.List(func(rel *rspb.Release) bool {
return rel.Info.Status.Code == rspb.Status_DELETED
})
// check
if err != nil {
t.Errorf("Failed to list deleted: %s", err)
}
if len(del) != 2 {
t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del)
}
// list all deployed releases
dpl, err := secrets.List(func(rel *rspb.Release) bool {
return rel.Info.Status.Code == rspb.Status_DEPLOYED
})
// check
if err != nil {
t.Errorf("Failed to list deployed: %s", err)
}
if len(dpl) != 2 {
t.Errorf("Expected 2 deployed, got %d", len(dpl))
}
// list all superseded releases
ssd, err := secrets.List(func(rel *rspb.Release) bool {
return rel.Info.Status.Code == rspb.Status_SUPERSEDED
})
// check
if err != nil {
t.Errorf("Failed to list superseded: %s", err)
}
if len(ssd) != 2 {
t.Errorf("Expected 2 superseded, got %d", len(ssd))
}
}
func TestSecretCreate(t *testing.T) {
secrets := newTestFixtureSecrets(t)
vers := int32(1)
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED)
// store the release in a secret
if err := secrets.Create(key, rel); err != nil {
t.Fatalf("Failed to create release with key %q: %s", key, err)
}
// get the release back
got, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
// compare created release with original
if !reflect.DeepEqual(rel, got) {
t.Errorf("Expected {%q}, got {%q}", rel, got)
}
}
func TestSecretUpdate(t *testing.T) {
vers := int32(1)
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
// modify release status code
rel.Info.Status.Code = rspb.Status_SUPERSEDED
// perform the update
if err := secrets.Update(key, rel); err != nil {
t.Fatalf("Failed to update release: %s", err)
}
// fetch the updated release
got, err := secrets.Get(key)
if err != nil {
t.Fatalf("Failed to get release with key %q: %s", key, err)
}
// check release has actually been updated by comparing modified fields
if rel.Info.Status.Code != got.Info.Status.Code {
t.Errorf("Expected status %s, got status %s", rel.Info.Status.Code, got.Info.Status.Code)
}
}

@ -0,0 +1,85 @@
/*
Copyright 2017 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package driver // import "k8s.io/helm/pkg/storage/driver"
import (
"bytes"
"compress/gzip"
"encoding/base64"
"io/ioutil"
"github.com/golang/protobuf/proto"
rspb "k8s.io/helm/pkg/proto/hapi/release"
)
var b64 = base64.StdEncoding
var magicGzip = []byte{0x1f, 0x8b, 0x08}
// encodeRelease encodes a release returning a base64 encoded
// gzipped binary protobuf encoding representation, or error.
func encodeRelease(rls *rspb.Release) (string, error) {
b, err := proto.Marshal(rls)
if err != nil {
return "", err
}
var buf bytes.Buffer
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return "", err
}
if _, err = w.Write(b); err != nil {
return "", err
}
w.Close()
return b64.EncodeToString(buf.Bytes()), nil
}
// decodeRelease decodes the bytes in data into a release
// type. Data must contain a base64 encoded string of a
// valid protobuf encoding of a release, otherwise
// an error is returned.
func decodeRelease(data string) (*rspb.Release, error) {
// base64 decode string
b, err := b64.DecodeString(data)
if err != nil {
return nil, err
}
// For backwards compatibility with releases that were stored before
// compression was introduced we skip decompression if the
// gzip magic header is not found
if bytes.Equal(b[0:3], magicGzip) {
r, err := gzip.NewReader(bytes.NewReader(b))
if err != nil {
return nil, err
}
b2, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
b = b2
}
var rls rspb.Release
// unmarshal protobuf bytes
if err := proto.Unmarshal(b, &rls); err != nil {
return nil, err
}
return &rls, nil
}

@ -32,9 +32,11 @@ var InstallOrder SortOrder = []string{
"LimitRange", "LimitRange",
"Secret", "Secret",
"ConfigMap", "ConfigMap",
"StorageClass",
"PersistentVolume", "PersistentVolume",
"PersistentVolumeClaim", "PersistentVolumeClaim",
"ServiceAccount", "ServiceAccount",
"CustomResourceDefinition",
"ClusterRole", "ClusterRole",
"ClusterRoleBinding", "ClusterRoleBinding",
"Role", "Role",
@ -71,9 +73,11 @@ var UninstallOrder SortOrder = []string{
"Role", "Role",
"ClusterRoleBinding", "ClusterRoleBinding",
"ClusterRole", "ClusterRole",
"CustomResourceDefinition",
"ServiceAccount", "ServiceAccount",
"PersistentVolumeClaim", "PersistentVolumeClaim",
"PersistentVolume", "PersistentVolume",
"StorageClass",
"ConfigMap", "ConfigMap",
"Secret", "Secret",
"LimitRange", "LimitRange",
@ -116,8 +120,12 @@ func (k *kindSorter) Less(i, j int) bool {
b := k.manifests[j] b := k.manifests[j]
first, aok := k.ordering[a.Head.Kind] first, aok := k.ordering[a.Head.Kind]
second, bok := k.ordering[b.Head.Kind] second, bok := k.ordering[b.Head.Kind]
// if same kind (including unknown) sub sort alphanumeric
if first == second { if first == second {
// same kind (including unknown) so sub sort alphanumeric // if both are unknown and of different kind sort by kind alphabetically
if !aok && !bok && a.Head.Kind != b.Head.Kind {
return a.Head.Kind < b.Head.Kind
}
return a.Name < b.Name return a.Name < b.Name
} }
// unknown kind is last // unknown kind is last

@ -41,6 +41,10 @@ func TestKindSorter(t *testing.T) {
Name: "u", Name: "u",
Head: &util.SimpleHead{Kind: "CronJob"}, Head: &util.SimpleHead{Kind: "CronJob"},
}, },
{
Name: "2",
Head: &util.SimpleHead{Kind: "CustomResourceDefinition"},
},
{ {
Name: "n", Name: "n",
Head: &util.SimpleHead{Kind: "DaemonSet"}, Head: &util.SimpleHead{Kind: "DaemonSet"},
@ -117,6 +121,10 @@ func TestKindSorter(t *testing.T) {
Name: "s", Name: "s",
Head: &util.SimpleHead{Kind: "StatefulSet"}, Head: &util.SimpleHead{Kind: "StatefulSet"},
}, },
{
Name: "1",
Head: &util.SimpleHead{Kind: "StorageClass"},
},
{ {
Name: "w", Name: "w",
Head: &util.SimpleHead{Kind: "APIService"}, Head: &util.SimpleHead{Kind: "APIService"},
@ -128,8 +136,8 @@ func TestKindSorter(t *testing.T) {
order SortOrder order SortOrder
expected string expected string
}{ }{
{"install", InstallOrder, "abcdefghijklmnopqrstuvw!"}, {"install", InstallOrder, "abcde1fgh2ijklmnopqrstuvw!"},
{"uninstall", UninstallOrder, "wvmutsrqponlkjihgfedcba!"}, {"uninstall", UninstallOrder, "wvmutsrqponlkji2hgf1edcba!"},
} { } {
var buf bytes.Buffer var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {
@ -175,7 +183,7 @@ func TestKindSorterSubSort(t *testing.T) {
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
Name: "u3", Name: "u2",
Head: &util.SimpleHead{Kind: "Unknown"}, Head: &util.SimpleHead{Kind: "Unknown"},
}, },
{ {
@ -183,8 +191,8 @@ func TestKindSorterSubSort(t *testing.T) {
Head: &util.SimpleHead{Kind: "Unknown"}, Head: &util.SimpleHead{Kind: "Unknown"},
}, },
{ {
Name: "u2", Name: "t3",
Head: &util.SimpleHead{Kind: "Unknown"}, Head: &util.SimpleHead{Kind: "Unknown2"},
}, },
} }
for _, test := range []struct { for _, test := range []struct {
@ -193,7 +201,7 @@ func TestKindSorterSubSort(t *testing.T) {
expected string expected string
}{ }{
// expectation is sorted by kind (unknown is last) and then sub sorted alphabetically within each group // expectation is sorted by kind (unknown is last) and then sub sorted alphabetically within each group
{"cm,clusterRole,clusterRoleBinding,Unknown", InstallOrder, "01Aa!zu1u2u3"}, {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01Aa!zu1u2t3"},
} { } {
var buf bytes.Buffer var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {

@ -155,7 +155,7 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
updatedRelease.Info.Status.Code = release.Status_FAILED updatedRelease.Info.Status.Code = release.Status_FAILED
updatedRelease.Info.Description = msg updatedRelease.Info.Description = msg
s.recordRelease(originalRelease, true) s.recordRelease(originalRelease, true)
s.recordRelease(updatedRelease, false) s.recordRelease(updatedRelease, true)
return res, err return res, err
} }

@ -20,6 +20,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/golang/protobuf/proto"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
@ -59,10 +61,7 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace)
} }
updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) updated := compareStoredAndReturnedRelease(t, *rs, *res)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 { if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
@ -79,8 +78,8 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected event 0 to be pre upgrade") t.Errorf("Expected event 0 to be pre upgrade")
} }
if len(res.Release.Manifest) == 0 { if len(updated.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("Expected manifest in %v", res)
} }
if res.Release.Config == nil { if res.Release.Config == nil {
@ -89,12 +88,8 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw)
} }
if len(updated.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", updated.Manifest)
} }
if res.Release.Version != 2 { if res.Release.Version != 2 {
@ -167,6 +162,7 @@ func TestUpdateRelease_ReuseValues(t *testing.T) {
if res.Release.Config != nil && res.Release.Config.Raw != expect { if res.Release.Config != nil && res.Release.Config.Raw != expect {
t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw) t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw)
} }
compareStoredAndReturnedRelease(t, *rs, *res)
} }
func TestUpdateRelease_ResetReuseValues(t *testing.T) { func TestUpdateRelease_ResetReuseValues(t *testing.T) {
@ -196,6 +192,7 @@ func TestUpdateRelease_ResetReuseValues(t *testing.T) {
if res.Release.Config != nil && res.Release.Config.Raw != "" { if res.Release.Config != nil && res.Release.Config.Raw != "" {
t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw)
} }
compareStoredAndReturnedRelease(t, *rs, *res)
} }
func TestUpdateReleaseFailure(t *testing.T) { func TestUpdateReleaseFailure(t *testing.T) {
@ -204,6 +201,7 @@ func TestUpdateReleaseFailure(t *testing.T) {
rel := releaseStub() rel := releaseStub()
rs.env.Releases.Create(rel) rs.env.Releases.Create(rel)
rs.env.KubeClient = newUpdateFailingKubeClient() rs.env.KubeClient = newUpdateFailingKubeClient()
rs.Log = t.Logf
req := &services.UpdateReleaseRequest{ req := &services.UpdateReleaseRequest{
Name: rel.Name, Name: rel.Name,
@ -225,6 +223,8 @@ func TestUpdateReleaseFailure(t *testing.T) {
t.Errorf("Expected FAILED release. Got %d", updatedStatus) t.Errorf("Expected FAILED release. Got %d", updatedStatus)
} }
compareStoredAndReturnedRelease(t, *rs, *res)
edesc := "Upgrade \"angry-panda\" failed: Failed update in kube client" edesc := "Upgrade \"angry-panda\" failed: Failed update in kube client"
if got := res.Release.Info.Description; got != edesc { if got := res.Release.Info.Description; got != edesc {
t.Errorf("Expected description %q, got %q", edesc, got) t.Errorf("Expected description %q, got %q", edesc, got)
@ -285,3 +285,16 @@ func TestUpdateReleaseNoChanges(t *testing.T) {
t.Fatalf("Failed updated: %s", err) t.Fatalf("Failed updated: %s", err)
} }
} }
func compareStoredAndReturnedRelease(t *testing.T, rs ReleaseServer, res services.UpdateReleaseResponse) *release.Release {
storedRelease, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Fatalf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if !proto.Equal(storedRelease, res.Release) {
t.Errorf("Stored release doesn't match returned Release")
}
return storedRelease
}

@ -26,7 +26,7 @@ var (
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
// Increment patch number for critical fixes to existing releases. // Increment patch number for critical fixes to existing releases.
Version = "v2.6" Version = "v2.7"
// BuildMetadata is extra build time data // BuildMetadata is extra build time data
BuildMetadata = "unreleased" BuildMetadata = "unreleased"

Loading…
Cancel
Save