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