github.com/flant/helm@v2.8.1+incompatible/cmd/helm/installer/install.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package installer // import "k8s.io/helm/cmd/helm/installer"
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"strings"
    24  
    25  	"github.com/Masterminds/semver"
    26  	"github.com/ghodss/yaml"
    27  	"k8s.io/api/core/v1"
    28  	"k8s.io/api/extensions/v1beta1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/util/intstr"
    32  	"k8s.io/client-go/kubernetes"
    33  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    34  	extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
    35  	"k8s.io/helm/pkg/version"
    36  
    37  	"k8s.io/helm/pkg/chartutil"
    38  )
    39  
    40  // Install uses Kubernetes client to install Tiller.
    41  //
    42  // Returns an error if the command failed.
    43  func Install(client kubernetes.Interface, opts *Options) error {
    44  	if err := createDeployment(client.ExtensionsV1beta1(), opts); err != nil {
    45  		return err
    46  	}
    47  	if err := createService(client.CoreV1(), opts.Namespace); err != nil {
    48  		return err
    49  	}
    50  	if opts.tls() {
    51  		if err := createSecret(client.CoreV1(), opts); err != nil {
    52  			return err
    53  		}
    54  	}
    55  	return nil
    56  }
    57  
    58  // Upgrade uses Kubernetes client to upgrade Tiller to current version.
    59  //
    60  // Returns an error if the command failed.
    61  func Upgrade(client kubernetes.Interface, opts *Options) error {
    62  	obj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
    63  	if err != nil {
    64  		return err
    65  	}
    66  	tillerImage := obj.Spec.Template.Spec.Containers[0].Image
    67  	if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
    68  		return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
    69  	}
    70  	obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
    71  	obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
    72  	obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
    73  	if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
    74  		return err
    75  	}
    76  	// If the service does not exists that would mean we are upgrading from a Tiller version
    77  	// that didn't deploy the service, so install it.
    78  	_, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
    79  	if apierrors.IsNotFound(err) {
    80  		return createService(client.CoreV1(), opts.Namespace)
    81  	}
    82  	return err
    83  }
    84  
    85  // semverCompare returns whether the client's version is older, equal or newer than the given image's version.
    86  func semverCompare(image string) int {
    87  	split := strings.Split(image, ":")
    88  	if len(split) < 2 {
    89  		// If we don't know the version, we consider the client version newer.
    90  		return 1
    91  	}
    92  	tillerVersion, err := semver.NewVersion(split[1])
    93  	if err != nil {
    94  		// same thing with unparsable tiller versions (e.g. canary releases).
    95  		return 1
    96  	}
    97  	clientVersion, err := semver.NewVersion(version.Version)
    98  	if err != nil {
    99  		// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
   100  		return 1
   101  	}
   102  	return clientVersion.Compare(tillerVersion)
   103  }
   104  
   105  // createDeployment creates the Tiller Deployment resource.
   106  func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
   107  	obj, err := deployment(opts)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	_, err = client.Deployments(obj.Namespace).Create(obj)
   112  	return err
   113  
   114  }
   115  
   116  // deployment gets the deployment object that installs Tiller.
   117  func deployment(opts *Options) (*v1beta1.Deployment, error) {
   118  	return generateDeployment(opts)
   119  }
   120  
   121  // createService creates the Tiller service resource
   122  func createService(client corev1.ServicesGetter, namespace string) error {
   123  	obj := service(namespace)
   124  	_, err := client.Services(obj.Namespace).Create(obj)
   125  	return err
   126  }
   127  
   128  // service gets the service object that installs Tiller.
   129  func service(namespace string) *v1.Service {
   130  	return generateService(namespace)
   131  }
   132  
   133  // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
   134  // resource.
   135  func DeploymentManifest(opts *Options) (string, error) {
   136  	obj, err := deployment(opts)
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  	buf, err := yaml.Marshal(obj)
   141  	return string(buf), err
   142  }
   143  
   144  // ServiceManifest gets the manifest (as a string) that describes the Tiller Service
   145  // resource.
   146  func ServiceManifest(namespace string) (string, error) {
   147  	obj := service(namespace)
   148  	buf, err := yaml.Marshal(obj)
   149  	return string(buf), err
   150  }
   151  
   152  func generateLabels(labels map[string]string) map[string]string {
   153  	labels["app"] = "helm"
   154  	return labels
   155  }
   156  
   157  // parseNodeSelectors parses a comma delimited list of key=values pairs into a map.
   158  func parseNodeSelectorsInto(labels string, m map[string]string) error {
   159  	kv := strings.Split(labels, ",")
   160  	for _, v := range kv {
   161  		el := strings.Split(v, "=")
   162  		if len(el) == 2 {
   163  			m[el[0]] = el[1]
   164  		} else {
   165  			return fmt.Errorf("invalid nodeSelector label: %q", kv)
   166  		}
   167  	}
   168  	return nil
   169  }
   170  func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
   171  	labels := generateLabels(map[string]string{"name": "tiller"})
   172  	nodeSelectors := map[string]string{}
   173  	if len(opts.NodeSelectors) > 0 {
   174  		err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors)
   175  		if err != nil {
   176  			return nil, err
   177  		}
   178  	}
   179  	d := &v1beta1.Deployment{
   180  		ObjectMeta: metav1.ObjectMeta{
   181  			Namespace: opts.Namespace,
   182  			Name:      deploymentName,
   183  			Labels:    labels,
   184  		},
   185  		Spec: v1beta1.DeploymentSpec{
   186  			Template: v1.PodTemplateSpec{
   187  				ObjectMeta: metav1.ObjectMeta{
   188  					Labels: labels,
   189  				},
   190  				Spec: v1.PodSpec{
   191  					ServiceAccountName: opts.ServiceAccount,
   192  					Containers: []v1.Container{
   193  						{
   194  							Name:            "tiller",
   195  							Image:           opts.selectImage(),
   196  							ImagePullPolicy: opts.pullPolicy(),
   197  							Ports: []v1.ContainerPort{
   198  								{ContainerPort: 44134, Name: "tiller"},
   199  								{ContainerPort: 44135, Name: "http"},
   200  							},
   201  							Env: []v1.EnvVar{
   202  								{Name: "TILLER_NAMESPACE", Value: opts.Namespace},
   203  								{Name: "TILLER_HISTORY_MAX", Value: fmt.Sprintf("%d", opts.MaxHistory)},
   204  							},
   205  							LivenessProbe: &v1.Probe{
   206  								Handler: v1.Handler{
   207  									HTTPGet: &v1.HTTPGetAction{
   208  										Path: "/liveness",
   209  										Port: intstr.FromInt(44135),
   210  									},
   211  								},
   212  								InitialDelaySeconds: 1,
   213  								TimeoutSeconds:      1,
   214  							},
   215  							ReadinessProbe: &v1.Probe{
   216  								Handler: v1.Handler{
   217  									HTTPGet: &v1.HTTPGetAction{
   218  										Path: "/readiness",
   219  										Port: intstr.FromInt(44135),
   220  									},
   221  								},
   222  								InitialDelaySeconds: 1,
   223  								TimeoutSeconds:      1,
   224  							},
   225  						},
   226  					},
   227  					HostNetwork:  opts.EnableHostNetwork,
   228  					NodeSelector: nodeSelectors,
   229  				},
   230  			},
   231  		},
   232  	}
   233  
   234  	if opts.tls() {
   235  		const certsDir = "/etc/certs"
   236  
   237  		var tlsVerify, tlsEnable = "", "1"
   238  		if opts.VerifyTLS {
   239  			tlsVerify = "1"
   240  		}
   241  
   242  		// Mount secret to "/etc/certs"
   243  		d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
   244  			Name:      "tiller-certs",
   245  			ReadOnly:  true,
   246  			MountPath: certsDir,
   247  		})
   248  		// Add environment variable required for enabling TLS
   249  		d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []v1.EnvVar{
   250  			{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
   251  			{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
   252  			{Name: "TILLER_TLS_CERTS", Value: certsDir},
   253  		}...)
   254  		// Add secret volume to deployment
   255  		d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v1.Volume{
   256  			Name: "tiller-certs",
   257  			VolumeSource: v1.VolumeSource{
   258  				Secret: &v1.SecretVolumeSource{
   259  					SecretName: "tiller-secret",
   260  				},
   261  			},
   262  		})
   263  	}
   264  	// if --override values were specified, ultimately convert values and deployment to maps,
   265  	// merge them and convert back to Deployment
   266  	if len(opts.Values) > 0 {
   267  		// base deployment struct
   268  		var dd v1beta1.Deployment
   269  		// get YAML from original deployment
   270  		dy, err := yaml.Marshal(d)
   271  		if err != nil {
   272  			return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err)
   273  		}
   274  		// convert deployment YAML to values
   275  		dv, err := chartutil.ReadValues(dy)
   276  		if err != nil {
   277  			return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err)
   278  		}
   279  		dm := dv.AsMap()
   280  		// merge --set values into our map
   281  		sm, err := opts.valuesMap(dm)
   282  		if err != nil {
   283  			return nil, fmt.Errorf("Error merging --set values into Deployment manifest")
   284  		}
   285  		finalY, err := yaml.Marshal(sm)
   286  		if err != nil {
   287  			return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err)
   288  		}
   289  		// convert merged values back into deployment
   290  		err = yaml.Unmarshal(finalY, &dd)
   291  		if err != nil {
   292  			return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err)
   293  		}
   294  		d = &dd
   295  	}
   296  
   297  	return d, nil
   298  }
   299  
   300  func generateService(namespace string) *v1.Service {
   301  	labels := generateLabels(map[string]string{"name": "tiller"})
   302  	s := &v1.Service{
   303  		ObjectMeta: metav1.ObjectMeta{
   304  			Namespace: namespace,
   305  			Name:      serviceName,
   306  			Labels:    labels,
   307  		},
   308  		Spec: v1.ServiceSpec{
   309  			Type: v1.ServiceTypeClusterIP,
   310  			Ports: []v1.ServicePort{
   311  				{
   312  					Name:       "tiller",
   313  					Port:       44134,
   314  					TargetPort: intstr.FromString("tiller"),
   315  				},
   316  			},
   317  			Selector: labels,
   318  		},
   319  	}
   320  	return s
   321  }
   322  
   323  // SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource.
   324  func SecretManifest(opts *Options) (string, error) {
   325  	o, err := generateSecret(opts)
   326  	if err != nil {
   327  		return "", err
   328  	}
   329  	buf, err := yaml.Marshal(o)
   330  	return string(buf), err
   331  }
   332  
   333  // createSecret creates the Tiller secret resource.
   334  func createSecret(client corev1.SecretsGetter, opts *Options) error {
   335  	o, err := generateSecret(opts)
   336  	if err != nil {
   337  		return err
   338  	}
   339  	_, err = client.Secrets(o.Namespace).Create(o)
   340  	return err
   341  }
   342  
   343  // generateSecret builds the secret object that hold Tiller secrets.
   344  func generateSecret(opts *Options) (*v1.Secret, error) {
   345  
   346  	labels := generateLabels(map[string]string{"name": "tiller"})
   347  	secret := &v1.Secret{
   348  		Type: v1.SecretTypeOpaque,
   349  		Data: make(map[string][]byte),
   350  		ObjectMeta: metav1.ObjectMeta{
   351  			Name:      secretName,
   352  			Labels:    labels,
   353  			Namespace: opts.Namespace,
   354  		},
   355  	}
   356  	var err error
   357  	if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
   358  		return nil, err
   359  	}
   360  	if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
   361  		return nil, err
   362  	}
   363  	if opts.VerifyTLS {
   364  		if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
   365  			return nil, err
   366  		}
   367  	}
   368  	return secret, nil
   369  }
   370  
   371  func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }