mirror of https://github.com/helm/helm
parent
6bca9686a1
commit
9409adfa8d
@ -1,374 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 installer // import "k8s.io/helm/cmd/helm/installer"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/ghodss/yaml"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
|
||||
"k8s.io/helm/pkg/version"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
)
|
||||
|
||||
// Install uses Kubernetes client to install Tiller.
|
||||
//
|
||||
// Returns an error if the command failed.
|
||||
func Install(client kubernetes.Interface, opts *Options) error {
|
||||
if err := createDeployment(client.ExtensionsV1beta1(), opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := createService(client.CoreV1(), opts.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.tls() {
|
||||
if err := createSecret(client.CoreV1(), opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upgrade uses Kubernetes client to upgrade Tiller to current version.
|
||||
//
|
||||
// Returns an error if the command failed.
|
||||
func Upgrade(client kubernetes.Interface, opts *Options) error {
|
||||
obj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tillerImage := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
|
||||
return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
|
||||
}
|
||||
obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
|
||||
obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
|
||||
obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
|
||||
if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
// If the service does not exists that would mean we are upgrading from a Tiller version
|
||||
// that didn't deploy the service, so install it.
|
||||
_, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return createService(client.CoreV1(), opts.Namespace)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
|
||||
func semverCompare(image string) int {
|
||||
split := strings.Split(image, ":")
|
||||
if len(split) < 2 {
|
||||
// If we don't know the version, we consider the client version newer.
|
||||
return 1
|
||||
}
|
||||
tillerVersion, err := semver.NewVersion(split[1])
|
||||
if err != nil {
|
||||
// same thing with unparsable tiller versions (e.g. canary releases).
|
||||
return 1
|
||||
}
|
||||
clientVersion, err := semver.NewVersion(version.Version)
|
||||
if err != nil {
|
||||
// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
|
||||
return 1
|
||||
}
|
||||
return clientVersion.Compare(tillerVersion)
|
||||
}
|
||||
|
||||
// createDeployment creates the Tiller Deployment resource.
|
||||
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
|
||||
obj, err := deployment(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.Deployments(obj.Namespace).Create(obj)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// deployment gets the deployment object that installs Tiller.
|
||||
func deployment(opts *Options) (*v1beta1.Deployment, error) {
|
||||
return generateDeployment(opts)
|
||||
}
|
||||
|
||||
// createService creates the Tiller service resource
|
||||
func createService(client corev1.ServicesGetter, namespace string) error {
|
||||
obj := service(namespace)
|
||||
_, err := client.Services(obj.Namespace).Create(obj)
|
||||
return err
|
||||
}
|
||||
|
||||
// service gets the service object that installs Tiller.
|
||||
func service(namespace string) *v1.Service {
|
||||
return generateService(namespace)
|
||||
}
|
||||
|
||||
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
|
||||
// resource.
|
||||
func DeploymentManifest(opts *Options) (string, error) {
|
||||
obj, err := deployment(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf, err := yaml.Marshal(obj)
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// ServiceManifest gets the manifest (as a string) that describes the Tiller Service
|
||||
// resource.
|
||||
func ServiceManifest(namespace string) (string, error) {
|
||||
obj := service(namespace)
|
||||
buf, err := yaml.Marshal(obj)
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
func generateLabels(labels map[string]string) map[string]string {
|
||||
labels["app"] = "helm"
|
||||
return labels
|
||||
}
|
||||
|
||||
// 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"})
|
||||
nodeSelectors := map[string]string{}
|
||||
if len(opts.NodeSelectors) > 0 {
|
||||
err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
automountServiceAccountToken := opts.ServiceAccount != ""
|
||||
d := &v1beta1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: opts.Namespace,
|
||||
Name: deploymentName,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1beta1.DeploymentSpec{
|
||||
Replicas: opts.getReplicas(),
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
ServiceAccountName: opts.ServiceAccount,
|
||||
AutomountServiceAccountToken: &automountServiceAccountToken,
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "tiller",
|
||||
Image: opts.selectImage(),
|
||||
ImagePullPolicy: opts.pullPolicy(),
|
||||
Ports: []v1.ContainerPort{
|
||||
{ContainerPort: 44134, Name: "tiller"},
|
||||
{ContainerPort: 44135, Name: "http"},
|
||||
},
|
||||
Env: []v1.EnvVar{
|
||||
{Name: "TILLER_NAMESPACE", Value: opts.Namespace},
|
||||
{Name: "TILLER_HISTORY_MAX", Value: fmt.Sprintf("%d", opts.MaxHistory)},
|
||||
},
|
||||
LivenessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
Path: "/liveness",
|
||||
Port: intstr.FromInt(44135),
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 1,
|
||||
TimeoutSeconds: 1,
|
||||
},
|
||||
ReadinessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
Path: "/readiness",
|
||||
Port: intstr.FromInt(44135),
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 1,
|
||||
TimeoutSeconds: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
HostNetwork: opts.EnableHostNetwork,
|
||||
NodeSelector: nodeSelectors,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if opts.tls() {
|
||||
const certsDir = "/etc/certs"
|
||||
|
||||
var tlsVerify, tlsEnable = "", "1"
|
||||
if opts.VerifyTLS {
|
||||
tlsVerify = "1"
|
||||
}
|
||||
|
||||
// Mount secret to "/etc/certs"
|
||||
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
|
||||
Name: "tiller-certs",
|
||||
ReadOnly: true,
|
||||
MountPath: certsDir,
|
||||
})
|
||||
// Add environment variable required for enabling TLS
|
||||
d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []v1.EnvVar{
|
||||
{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
|
||||
{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
|
||||
{Name: "TILLER_TLS_CERTS", Value: certsDir},
|
||||
}...)
|
||||
// Add secret volume to deployment
|
||||
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v1.Volume{
|
||||
Name: "tiller-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: "tiller-secret",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
// 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 {
|
||||
labels := generateLabels(map[string]string{"name": "tiller"})
|
||||
s := &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: serviceName,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeClusterIP,
|
||||
Ports: []v1.ServicePort{
|
||||
{
|
||||
Name: "tiller",
|
||||
Port: 44134,
|
||||
TargetPort: intstr.FromString("tiller"),
|
||||
},
|
||||
},
|
||||
Selector: labels,
|
||||
},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource.
|
||||
func SecretManifest(opts *Options) (string, error) {
|
||||
o, err := generateSecret(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf, err := yaml.Marshal(o)
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
// createSecret creates the Tiller secret resource.
|
||||
func createSecret(client corev1.SecretsGetter, opts *Options) error {
|
||||
o, err := generateSecret(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.Secrets(o.Namespace).Create(o)
|
||||
return err
|
||||
}
|
||||
|
||||
// generateSecret builds the secret object that hold Tiller secrets.
|
||||
func generateSecret(opts *Options) (*v1.Secret, error) {
|
||||
|
||||
labels := generateLabels(map[string]string{"name": "tiller"})
|
||||
secret := &v1.Secret{
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: make(map[string][]byte),
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Labels: labels,
|
||||
Namespace: opts.Namespace,
|
||||
},
|
||||
}
|
||||
var err error
|
||||
if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.VerifyTLS {
|
||||
if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }
|
@ -1,743 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 installer // import "k8s.io/helm/cmd/helm/installer"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
testcore "k8s.io/client-go/testing"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/version"
|
||||
)
|
||||
|
||||
func TestDeploymentManifest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
image string
|
||||
canary bool
|
||||
expect string
|
||||
imagePullPolicy v1.PullPolicy
|
||||
}{
|
||||
{"default", "", false, "gcr.io/kubernetes-helm/tiller:" + version.Version, "IfNotPresent"},
|
||||
{"canary", "example.com/tiller", true, "gcr.io/kubernetes-helm/tiller:canary", "Always"},
|
||||
{"custom", "example.com/tiller:latest", false, "example.com/tiller:latest", "IfNotPresent"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary})
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error %q", tt.name, err)
|
||||
}
|
||||
var dep v1beta1.Deployment
|
||||
if err := yaml.Unmarshal([]byte(o), &dep); err != nil {
|
||||
t.Fatalf("%s: error %q", tt.name, err)
|
||||
}
|
||||
|
||||
if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect {
|
||||
t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got)
|
||||
}
|
||||
|
||||
if got := dep.Spec.Template.Spec.Containers[0].ImagePullPolicy; got != tt.imagePullPolicy {
|
||||
t.Errorf("%s: expected imagePullPolicy %q, got %q", tt.name, tt.imagePullPolicy, got)
|
||||
}
|
||||
|
||||
if got := dep.Spec.Template.Spec.Containers[0].Env[0].Value; got != v1.NamespaceDefault {
|
||||
t.Errorf("%s: expected namespace %q, got %q", tt.name, v1.NamespaceDefault, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentManifestForServiceAccount(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
image string
|
||||
canary bool
|
||||
expect string
|
||||
imagePullPolicy v1.PullPolicy
|
||||
serviceAccount string
|
||||
}{
|
||||
{"withSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", "service-account"},
|
||||
{"withoutSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount})
|
||||
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)
|
||||
}
|
||||
if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount {
|
||||
t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got)
|
||||
}
|
||||
if got := *d.Spec.Template.Spec.AutomountServiceAccountToken; got != (tt.serviceAccount != "") {
|
||||
t.Errorf("%s: unexpected automountServiceAccountToken = %t for serviceAccount %q", tt.name, got, tt.serviceAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentManifest_WithTLS(t *testing.T) {
|
||||
tests := []struct {
|
||||
opts Options
|
||||
name string
|
||||
enable string
|
||||
verify string
|
||||
}{
|
||||
{
|
||||
Options{Namespace: v1.NamespaceDefault, EnableTLS: true, VerifyTLS: true},
|
||||
"tls enable (true), tls verify (true)",
|
||||
"1",
|
||||
"1",
|
||||
},
|
||||
{
|
||||
Options{Namespace: v1.NamespaceDefault, EnableTLS: true, VerifyTLS: false},
|
||||
"tls enable (true), tls verify (false)",
|
||||
"1",
|
||||
"",
|
||||
},
|
||||
{
|
||||
Options{Namespace: v1.NamespaceDefault, EnableTLS: false, VerifyTLS: true},
|
||||
"tls enable (false), tls verify (true)",
|
||||
"1",
|
||||
"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 environment variable in deployment reflect the use of tls being enabled.
|
||||
if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.verify {
|
||||
t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got)
|
||||
}
|
||||
if got := d.Spec.Template.Spec.Containers[0].Env[3].Value; got != tt.enable {
|
||||
t.Errorf("%s: expected tls enable env value %q, got %q", tt.name, tt.enable, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceManifest(t *testing.T) {
|
||||
o, err := ServiceManifest(v1.NamespaceDefault)
|
||||
if err != nil {
|
||||
t.Fatalf("error %q", err)
|
||||
}
|
||||
var svc v1.Service
|
||||
if err := yaml.Unmarshal([]byte(o), &svc); err != nil {
|
||||
t.Fatalf("error %q", err)
|
||||
}
|
||||
|
||||
if got := svc.ObjectMeta.Namespace; got != v1.NamespaceDefault {
|
||||
t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretManifest(t *testing.T) {
|
||||
o, err := SecretManifest(&Options{
|
||||
VerifyTLS: true,
|
||||
EnableTLS: true,
|
||||
Namespace: v1.NamespaceDefault,
|
||||
TLSKeyFile: tlsTestFile(t, "key.pem"),
|
||||
TLSCertFile: tlsTestFile(t, "crt.pem"),
|
||||
TLSCaCertFile: tlsTestFile(t, "ca.pem"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error %q", err)
|
||||
}
|
||||
|
||||
var obj v1.Secret
|
||||
if err := yaml.Unmarshal([]byte(o), &obj); err != nil {
|
||||
t.Fatalf("error %q", err)
|
||||
}
|
||||
|
||||
if got := obj.ObjectMeta.Namespace; got != v1.NamespaceDefault {
|
||||
t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got)
|
||||
}
|
||||
if _, ok := obj.Data["tls.key"]; !ok {
|
||||
t.Errorf("missing 'tls.key' in generated secret object")
|
||||
}
|
||||
if _, ok := obj.Data["tls.crt"]; !ok {
|
||||
t.Errorf("missing 'tls.crt' in generated secret object")
|
||||
}
|
||||
if _, ok := obj.Data["ca.crt"]; !ok {
|
||||
t.Errorf("missing 'ca.crt' in generated secret object")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
|
||||
l := obj.GetLabels()
|
||||
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
|
||||
t.Errorf("expected labels = '', got '%s'", l)
|
||||
}
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
ports := len(obj.Spec.Template.Spec.Containers[0].Ports)
|
||||
if ports != 2 {
|
||||
t.Errorf("expected ports = 2, got '%d'", ports)
|
||||
}
|
||||
replicas := obj.Spec.Replicas
|
||||
if int(*replicas) != 1 {
|
||||
t.Errorf("expected replicas = 1, got '%d'", replicas)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
|
||||
l := obj.GetLabels()
|
||||
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
|
||||
t.Errorf("expected labels = '', got '%s'", l)
|
||||
}
|
||||
n := obj.ObjectMeta.Namespace
|
||||
if n != v1.NamespaceDefault {
|
||||
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image}
|
||||
if err := Install(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 2 {
|
||||
t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallHA(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
|
||||
replicas := obj.Spec.Replicas
|
||||
if int(*replicas) != 2 {
|
||||
t.Errorf("expected replicas = 2, got '%d'", replicas)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
|
||||
opts := &Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: image,
|
||||
Replicas: 2,
|
||||
}
|
||||
if err := Install(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstall_WithTLS(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
name := "tiller-secret"
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
|
||||
l := obj.GetLabels()
|
||||
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
|
||||
t.Errorf("expected labels = '', got '%s'", l)
|
||||
}
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
|
||||
l := obj.GetLabels()
|
||||
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
|
||||
t.Errorf("expected labels = '', got '%s'", l)
|
||||
}
|
||||
n := obj.ObjectMeta.Namespace
|
||||
if n != v1.NamespaceDefault {
|
||||
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("create", "secrets", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1.Secret)
|
||||
if l := obj.GetLabels(); reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
|
||||
t.Errorf("expected labels = '', got '%s'", l)
|
||||
}
|
||||
if n := obj.ObjectMeta.Namespace; n != v1.NamespaceDefault {
|
||||
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
|
||||
}
|
||||
if s := obj.ObjectMeta.Name; s != name {
|
||||
t.Errorf("expected name = '%s', got '%s'", name, s)
|
||||
}
|
||||
if _, ok := obj.Data["tls.key"]; !ok {
|
||||
t.Errorf("missing 'tls.key' in generated secret object")
|
||||
}
|
||||
if _, ok := obj.Data["tls.crt"]; !ok {
|
||||
t.Errorf("missing 'tls.crt' in generated secret object")
|
||||
}
|
||||
if _, ok := obj.Data["ca.crt"]; !ok {
|
||||
t.Errorf("missing 'ca.crt' in generated secret object")
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
|
||||
opts := &Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: image,
|
||||
EnableTLS: true,
|
||||
VerifyTLS: true,
|
||||
TLSKeyFile: tlsTestFile(t, "key.pem"),
|
||||
TLSCertFile: tlsTestFile(t, "crt.pem"),
|
||||
TLSCaCertFile: tlsTestFile(t, "ca.pem"),
|
||||
}
|
||||
|
||||
if err := Install(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 3 {
|
||||
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstall_canary(t *testing.T) {
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != "gcr.io/kubernetes-helm/tiller:canary" {
|
||||
t.Errorf("expected canary image, got '%s'", i)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
|
||||
return true, obj, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, UseCanary: true}
|
||||
if err := Install(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 2 {
|
||||
t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgrade(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
serviceAccount := "newServiceAccount"
|
||||
existingDeployment, _ := deployment(&Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: "imageToReplace:v1.0.0",
|
||||
ServiceAccount: "serviceAccountToReplace",
|
||||
UseCanary: false,
|
||||
})
|
||||
existingService := service(v1.NamespaceDefault)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
sa := obj.Spec.Template.Spec.ServiceAccountName
|
||||
if sa != serviceAccount {
|
||||
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
|
||||
if err := Upgrade(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 3 {
|
||||
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgrade_serviceNotFound(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
|
||||
existingDeployment, _ := deployment(&Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: "imageToReplace",
|
||||
UseCanary: false,
|
||||
})
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(v1.Resource("services"), "1")
|
||||
})
|
||||
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
|
||||
n := obj.ObjectMeta.Namespace
|
||||
if n != v1.NamespaceDefault {
|
||||
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image}
|
||||
if err := Upgrade(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 4 {
|
||||
t.Errorf("unexpected actions: %v, expected 4 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUgrade_newerVersion(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
serviceAccount := "newServiceAccount"
|
||||
existingDeployment, _ := deployment(&Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: "imageToReplace:v100.5.0",
|
||||
ServiceAccount: "serviceAccountToReplace",
|
||||
UseCanary: false,
|
||||
})
|
||||
existingService := service(v1.NamespaceDefault)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
sa := obj.Spec.Template.Spec.ServiceAccountName
|
||||
if sa != serviceAccount {
|
||||
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
|
||||
opts := &Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: image,
|
||||
ServiceAccount: serviceAccount,
|
||||
ForceUpgrade: false,
|
||||
}
|
||||
if err := Upgrade(fc, opts); err == nil {
|
||||
t.Errorf("Expected error because the deployed version is newer")
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 1 {
|
||||
t.Errorf("unexpected actions: %v, expected 1 action got %d", actions, len(actions))
|
||||
}
|
||||
|
||||
opts = &Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: image,
|
||||
ServiceAccount: serviceAccount,
|
||||
ForceUpgrade: true,
|
||||
}
|
||||
if err := Upgrade(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 4 {
|
||||
t.Errorf("unexpected actions: %v, expected 4 action got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgrade_identical(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
serviceAccount := "newServiceAccount"
|
||||
existingDeployment, _ := deployment(&Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: "imageToReplace:v2.0.0",
|
||||
ServiceAccount: "serviceAccountToReplace",
|
||||
UseCanary: false,
|
||||
})
|
||||
existingService := service(v1.NamespaceDefault)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
sa := obj.Spec.Template.Spec.ServiceAccountName
|
||||
if sa != serviceAccount {
|
||||
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
|
||||
if err := Upgrade(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 3 {
|
||||
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgrade_canaryClient(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:canary"
|
||||
serviceAccount := "newServiceAccount"
|
||||
existingDeployment, _ := deployment(&Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: "imageToReplace:v1.0.0",
|
||||
ServiceAccount: "serviceAccountToReplace",
|
||||
UseCanary: false,
|
||||
})
|
||||
existingService := service(v1.NamespaceDefault)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
sa := obj.Spec.Template.Spec.ServiceAccountName
|
||||
if sa != serviceAccount {
|
||||
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
|
||||
if err := Upgrade(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 3 {
|
||||
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgrade_canaryServer(t *testing.T) {
|
||||
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
|
||||
serviceAccount := "newServiceAccount"
|
||||
existingDeployment, _ := deployment(&Options{
|
||||
Namespace: v1.NamespaceDefault,
|
||||
ImageSpec: "imageToReplace:canary",
|
||||
ServiceAccount: "serviceAccountToReplace",
|
||||
UseCanary: false,
|
||||
})
|
||||
existingService := service(v1.NamespaceDefault)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
|
||||
i := obj.Spec.Template.Spec.Containers[0].Image
|
||||
if i != image {
|
||||
t.Errorf("expected image = '%s', got '%s'", image, i)
|
||||
}
|
||||
sa := obj.Spec.Template.Spec.ServiceAccountName
|
||||
if sa != serviceAccount {
|
||||
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
|
||||
if err := Upgrade(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 3 {
|
||||
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func tlsTestFile(t *testing.T, path string) string {
|
||||
const tlsTestDir = "../../../testdata"
|
||||
path = filepath.Join(tlsTestDir, path)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
t.Fatalf("tls test file %s does not exist", 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 installer // import "k8s.io/helm/cmd/helm/installer"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/helm/pkg/strvals"
|
||||
"k8s.io/helm/pkg/version"
|
||||
)
|
||||
|
||||
const defaultImage = "gcr.io/kubernetes-helm/tiller"
|
||||
|
||||
// Options control how to install Tiller into a cluster, upgrade, and uninstall Tiller from a cluster.
|
||||
type Options struct {
|
||||
// EnableTLS instructs Tiller to serve with TLS enabled.
|
||||
//
|
||||
// Implied by VerifyTLS. If set the TLSKey and TLSCert are required.
|
||||
EnableTLS bool
|
||||
|
||||
// VerifyTLS instructs Tiller to serve with TLS enabled verify remote certificates.
|
||||
//
|
||||
// If set TLSKey, TLSCert, TLSCaCert are required.
|
||||
VerifyTLS bool
|
||||
|
||||
// UseCanary indicates that Tiller should deploy using the latest Tiller image.
|
||||
UseCanary bool
|
||||
|
||||
// Namespace is the Kubernetes namespace to use to deploy Tiller.
|
||||
Namespace string
|
||||
|
||||
// ServiceAccount is the Kubernetes service account to add to Tiller.
|
||||
ServiceAccount string
|
||||
|
||||
// Force allows to force upgrading tiller if deployed version is greater than current version
|
||||
ForceUpgrade bool
|
||||
|
||||
// ImageSpec indentifies the image Tiller will use when deployed.
|
||||
//
|
||||
// Valid if and only if UseCanary is false.
|
||||
ImageSpec string
|
||||
|
||||
// TLSKeyFile identifies the file containing the pem encoded TLS private
|
||||
// key Tiller should use.
|
||||
//
|
||||
// Required and valid if and only if EnableTLS or VerifyTLS is set.
|
||||
TLSKeyFile string
|
||||
|
||||
// TLSCertFile identifies the file containing the pem encoded TLS
|
||||
// certificate Tiller should use.
|
||||
//
|
||||
// Required and valid if and only if EnableTLS or VerifyTLS is set.
|
||||
TLSCertFile string
|
||||
|
||||
// TLSCaCertFile identifies the file containing the pem encoded TLS CA
|
||||
// certificate Tiller should use to verify remotes certificates.
|
||||
//
|
||||
// Required and valid if and only if VerifyTLS is set.
|
||||
TLSCaCertFile string
|
||||
|
||||
// EnableHostNetwork installs Tiller with net=host.
|
||||
EnableHostNetwork bool
|
||||
|
||||
// MaxHistory sets the maximum number of release versions stored per release.
|
||||
//
|
||||
// Less than or equal to zero means no limit.
|
||||
MaxHistory int
|
||||
|
||||
// Replicas sets the amount of Tiller replicas to start
|
||||
//
|
||||
// Less than or equals to 1 means 1.
|
||||
Replicas 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 {
|
||||
switch {
|
||||
case opts.UseCanary:
|
||||
return defaultImage + ":canary"
|
||||
case opts.ImageSpec == "":
|
||||
return fmt.Sprintf("%s:%s", defaultImage, version.Version)
|
||||
default:
|
||||
return opts.ImageSpec
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *Options) pullPolicy() v1.PullPolicy {
|
||||
if opts.UseCanary {
|
||||
return v1.PullAlways
|
||||
}
|
||||
return v1.PullIfNotPresent
|
||||
}
|
||||
|
||||
func (opts *Options) getReplicas() *int32 {
|
||||
replicas := int32(1)
|
||||
if opts.Replicas > 1 {
|
||||
replicas = int32(opts.Replicas)
|
||||
}
|
||||
return &replicas
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 installer // import "k8s.io/helm/cmd/helm/installer"
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
)
|
||||
|
||||
const (
|
||||
deploymentName = "tiller-deploy"
|
||||
serviceName = "tiller-deploy"
|
||||
secretName = "tiller-secret"
|
||||
)
|
||||
|
||||
// Uninstall uses Kubernetes client to uninstall Tiller.
|
||||
func Uninstall(client internalclientset.Interface, opts *Options) error {
|
||||
if err := deleteService(client.Core(), opts.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := deleteDeployment(client, opts.Namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
return deleteSecret(client.Core(), opts.Namespace)
|
||||
}
|
||||
|
||||
// deleteService deletes the Tiller Service resource
|
||||
func deleteService(client coreclient.ServicesGetter, namespace string) error {
|
||||
err := client.Services(namespace).Delete(serviceName, &metav1.DeleteOptions{})
|
||||
return ingoreNotFound(err)
|
||||
}
|
||||
|
||||
// deleteDeployment deletes the Tiller Deployment resource
|
||||
// We need to use the reaper instead of the kube API because GC for deployment dependents
|
||||
// is not yet supported at the k8s server level (<= 1.5)
|
||||
func deleteDeployment(client internalclientset.Interface, namespace string) error {
|
||||
reaper, _ := kubectl.ReaperFor(extensions.Kind("Deployment"), client)
|
||||
err := reaper.Stop(namespace, deploymentName, 0, nil)
|
||||
return ingoreNotFound(err)
|
||||
}
|
||||
|
||||
// deleteSecret deletes the Tiller Secret resource
|
||||
func deleteSecret(client coreclient.SecretsGetter, namespace string) error {
|
||||
err := client.Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{})
|
||||
return ingoreNotFound(err)
|
||||
}
|
||||
|
||||
func ingoreNotFound(err error) error {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 installer // import "k8s.io/helm/cmd/helm/installer"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
testcore "k8s.io/client-go/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
)
|
||||
|
||||
func TestUninstall(t *testing.T) {
|
||||
fc := &fake.Clientset{}
|
||||
opts := &Options{Namespace: core.NamespaceDefault}
|
||||
if err := Uninstall(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 7 {
|
||||
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstall_serviceNotFound(t *testing.T) {
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "services"}, "1")
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: core.NamespaceDefault}
|
||||
if err := Uninstall(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 7 {
|
||||
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstall_deploymentNotFound(t *testing.T) {
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("delete", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(core.Resource("deployments"), "1")
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: core.NamespaceDefault}
|
||||
if err := Uninstall(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 7 {
|
||||
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstall_secretNotFound(t *testing.T) {
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("delete", "secrets", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(core.Resource("secrets"), "1")
|
||||
})
|
||||
|
||||
opts := &Options{Namespace: core.NamespaceDefault}
|
||||
if err := Uninstall(fc, opts); err != nil {
|
||||
t.Errorf("unexpected error: %#+v", err)
|
||||
}
|
||||
|
||||
if actions := fc.Actions(); len(actions) != 7 {
|
||||
t.Errorf("unexpected actions: %v, expect 7 actions got %d", actions, len(actions))
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/helm/cmd/helm/installer"
|
||||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/helm/helmpath"
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
const resetDesc = `
|
||||
This command uninstalls Tiller (the Helm server-side component) from your
|
||||
Kubernetes Cluster and optionally deletes local configuration in
|
||||
$HELM_HOME (default ~/.helm/)
|
||||
`
|
||||
|
||||
type resetCmd struct {
|
||||
force bool
|
||||
removeHelmHome bool
|
||||
namespace string
|
||||
out io.Writer
|
||||
home helmpath.Home
|
||||
client helm.Interface
|
||||
kubeClient internalclientset.Interface
|
||||
}
|
||||
|
||||
func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
|
||||
d := &resetCmd{
|
||||
out: out,
|
||||
client: client,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "uninstalls Tiller from a cluster",
|
||||
Long: resetDesc,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := setupConnection(); !d.force && err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return errors.New("This command does not accept arguments")
|
||||
}
|
||||
|
||||
d.namespace = settings.TillerNamespace
|
||||
d.home = settings.Home
|
||||
d.client = ensureHelmClient(d.client)
|
||||
|
||||
return d.run()
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.)")
|
||||
f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// runReset uninstalls tiller from Kubernetes Cluster and deletes local config
|
||||
func (d *resetCmd) run() error {
|
||||
if d.kubeClient == nil {
|
||||
c, err := getInternalKubeClient(settings.KubeContext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get kubernetes client: %s", err)
|
||||
}
|
||||
d.kubeClient = c
|
||||
}
|
||||
|
||||
res, err := d.client.ListReleases(
|
||||
helm.ReleaseListStatuses([]release.Status_Code{release.Status_DEPLOYED}),
|
||||
)
|
||||
if !d.force && err != nil {
|
||||
return prettyError(err)
|
||||
}
|
||||
|
||||
if !d.force && res != nil && len(res.Releases) > 0 {
|
||||
return fmt.Errorf("there are still %d deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)", len(res.Releases))
|
||||
}
|
||||
|
||||
if err := installer.Uninstall(d.kubeClient, &installer.Options{Namespace: d.namespace}); err != nil {
|
||||
return fmt.Errorf("error unstalling Tiller: %s", err)
|
||||
}
|
||||
|
||||
if d.removeHelmHome {
|
||||
if err := deleteDirectories(d.home, d.out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(d.out, "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteDirectories deletes $HELM_HOME
|
||||
func deleteDirectories(home helmpath.Home, out io.Writer) error {
|
||||
if _, err := os.Stat(home.String()); err == nil {
|
||||
fmt.Fprintf(out, "Deleting %s \n", home.String())
|
||||
if err := os.RemoveAll(home.String()); err != nil {
|
||||
return fmt.Errorf("Could not remove %s: %s", home.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
|
||||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/helm/helmpath"
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
type resetCase struct {
|
||||
name string
|
||||
err bool
|
||||
resp []*release.Release
|
||||
removeHelmHome bool
|
||||
force bool
|
||||
expectedActions int
|
||||
expectedOutput string
|
||||
}
|
||||
|
||||
func TestResetCmd(t *testing.T) {
|
||||
|
||||
verifyResetCmd(t, resetCase{
|
||||
name: "test reset command",
|
||||
expectedActions: 3,
|
||||
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
|
||||
})
|
||||
}
|
||||
|
||||
func TestResetCmd_removeHelmHome(t *testing.T) {
|
||||
verifyResetCmd(t, resetCase{
|
||||
name: "test reset command - remove helm home",
|
||||
removeHelmHome: true,
|
||||
expectedActions: 3,
|
||||
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
|
||||
})
|
||||
}
|
||||
|
||||
func TestReset_deployedReleases(t *testing.T) {
|
||||
verifyResetCmd(t, resetCase{
|
||||
name: "test reset command - deployed releases",
|
||||
resp: []*release.Release{
|
||||
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
|
||||
},
|
||||
err: true,
|
||||
expectedOutput: "there are still 1 deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)",
|
||||
})
|
||||
}
|
||||
|
||||
func TestReset_forceFlag(t *testing.T) {
|
||||
verifyResetCmd(t, resetCase{
|
||||
name: "test reset command - force flag",
|
||||
force: true,
|
||||
resp: []*release.Release{
|
||||
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
|
||||
},
|
||||
expectedActions: 3,
|
||||
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
|
||||
})
|
||||
}
|
||||
|
||||
func verifyResetCmd(t *testing.T, tc resetCase) {
|
||||
home, err := ioutil.TempDir("", "helm_home")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(home)
|
||||
|
||||
var buf bytes.Buffer
|
||||
c := &helm.FakeClient{
|
||||
Rels: tc.resp,
|
||||
}
|
||||
fc := fake.NewSimpleClientset()
|
||||
cmd := &resetCmd{
|
||||
removeHelmHome: tc.removeHelmHome,
|
||||
force: tc.force,
|
||||
out: &buf,
|
||||
home: helmpath.Home(home),
|
||||
client: c,
|
||||
kubeClient: fc,
|
||||
namespace: core.NamespaceDefault,
|
||||
}
|
||||
|
||||
err = cmd.run()
|
||||
if !tc.err && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
if tc.err {
|
||||
got = err.Error()
|
||||
}
|
||||
|
||||
actions := fc.Actions()
|
||||
if tc.expectedActions > 0 && len(actions) != tc.expectedActions {
|
||||
t.Errorf("Expected %d actions, got %d", tc.expectedActions, len(actions))
|
||||
}
|
||||
if !strings.Contains(got, tc.expectedOutput) {
|
||||
t.Errorf("expected %q, got %q", tc.expectedOutput, got)
|
||||
}
|
||||
_, err = os.Stat(home)
|
||||
if !tc.removeHelmHome && err != nil {
|
||||
t.Errorf("Helm home directory %s does not exists", home)
|
||||
}
|
||||
if tc.removeHelmHome && err == nil {
|
||||
t.Errorf("Helm home directory %s exists", home)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue