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

     1  /*
     2  Copyright The Helm Authors.
     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  	"fmt"
    21  	"io/ioutil"
    22  	"strings"
    23  
    24  	"github.com/Masterminds/semver"
    25  	"github.com/ghodss/yaml"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	"k8s.io/api/core/v1"
    28  	extensionsv1beta1 "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/runtime"
    32  	"k8s.io/apimachinery/pkg/util/intstr"
    33  	"k8s.io/client-go/kubernetes"
    34  	appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
    35  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    36  
    37  	"k8s.io/helm/pkg/chartutil"
    38  	"k8s.io/helm/pkg/tiller/environment"
    39  )
    40  
    41  // Install uses Kubernetes client to install Tiller.
    42  //
    43  // Returns an error if the command failed.
    44  func Install(client kubernetes.Interface, opts *Options) error {
    45  	if err := createDeployment(client.AppsV1(), opts); err != nil {
    46  		return err
    47  	}
    48  	if err := createService(client.CoreV1(), opts.Namespace); err != nil {
    49  		return err
    50  	}
    51  	if opts.tls() {
    52  		if err := createSecret(client.CoreV1(), opts); err != nil {
    53  			return err
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  // Upgrade uses Kubernetes client to upgrade Tiller to current version.
    60  //
    61  // Returns an error if the command failed.
    62  func Upgrade(client kubernetes.Interface, opts *Options) error {
    63  	appsobj, err := client.AppsV1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
    64  	if err == nil {
    65  		// Can happen in two cases:
    66  		// 1. helm init inserted an apps/v1 Deployment up front in Kubernetes
    67  		// 2. helm init inserted an extensions/v1beta1 Deployment against a K8s cluster already
    68  		//    supporting apps/v1 Deployment. In such a case K8s is returning the apps/v1 object anyway.`
    69  		//    (for the same reason "kubectl convert" is being deprecated)
    70  		return upgradeAppsTillerDeployment(client, opts, appsobj)
    71  	}
    72  
    73  	extensionsobj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
    74  	if err == nil {
    75  		// User performed helm init against older version of kubernetes (Previous to 1.9)
    76  		return upgradeExtensionsTillerDeployment(client, opts, extensionsobj)
    77  	}
    78  
    79  	return err
    80  }
    81  
    82  func upgradeAppsTillerDeployment(client kubernetes.Interface, opts *Options, obj *appsv1.Deployment) error {
    83  	// Update the PodTemplateSpec section of the deployment
    84  	if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil {
    85  		return err
    86  	}
    87  
    88  	if _, err := client.AppsV1().Deployments(opts.Namespace).Update(obj); err != nil {
    89  		return err
    90  	}
    91  
    92  	// If the service does not exist that would mean we are upgrading from a Tiller version
    93  	// that didn't deploy the service, so install it.
    94  	_, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
    95  	if apierrors.IsNotFound(err) {
    96  		return createService(client.CoreV1(), opts.Namespace)
    97  	}
    98  
    99  	return err
   100  }
   101  
   102  func upgradeExtensionsTillerDeployment(client kubernetes.Interface, opts *Options, obj *extensionsv1beta1.Deployment) error {
   103  	// Update the PodTemplateSpec section of the deployment
   104  	if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil {
   105  		return err
   106  	}
   107  
   108  	if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
   109  		return err
   110  	}
   111  
   112  	// If the service does not exist that would mean we are upgrading from a Tiller version
   113  	// that didn't deploy the service, so install it.
   114  	_, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
   115  	if apierrors.IsNotFound(err) {
   116  		return createService(client.CoreV1(), opts.Namespace)
   117  	}
   118  
   119  	return err
   120  }
   121  
   122  func updatePodTemplate(podSpec *v1.PodSpec, opts *Options) error {
   123  	tillerImage := podSpec.Containers[0].Image
   124  	clientImage := opts.SelectImage()
   125  
   126  	if semverCompare(tillerImage, clientImage) == -1 && !opts.ForceUpgrade {
   127  		return fmt.Errorf("current Tiller version %s is newer than client version %s, use --force-upgrade to downgrade", tillerImage, clientImage)
   128  	}
   129  	podSpec.Containers[0].Image = clientImage
   130  	podSpec.Containers[0].ImagePullPolicy = opts.pullPolicy()
   131  	podSpec.ServiceAccountName = opts.ServiceAccount
   132  
   133  	return nil
   134  }
   135  
   136  // semverCompare returns whether the client's version is older, equal or newer than the given image's version.
   137  func semverCompare(tillerImage, clientImage string) int {
   138  	tillerVersion, err := string2semver(tillerImage)
   139  	if err != nil {
   140  		// same thing with unparsable tiller versions (e.g. canary releases).
   141  		return 1
   142  	}
   143  
   144  	// clientVersion, err := semver.NewVersion(currentVersion)
   145  	clientVersion, err := string2semver(clientImage)
   146  	if err != nil {
   147  		// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
   148  		return 1
   149  	}
   150  
   151  	return clientVersion.Compare(tillerVersion)
   152  }
   153  
   154  func string2semver(image string) (*semver.Version, error) {
   155  	split := strings.Split(image, ":")
   156  	if len(split) < 2 {
   157  		// If we don't know the version, we consider the client version newer.
   158  		return nil, fmt.Errorf("no repository in image %s", image)
   159  	}
   160  	return semver.NewVersion(split[1])
   161  }
   162  
   163  // createDeployment creates the Tiller Deployment resource.
   164  func createDeployment(client appsv1client.DeploymentsGetter, opts *Options) error {
   165  	obj, err := generateDeployment(opts)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	_, err = client.Deployments(obj.Namespace).Create(obj)
   170  	return err
   171  
   172  }
   173  
   174  // Deployment gets a deployment object that can be used to generate a manifest
   175  // as a string. This object should not be submitted directly to the Kubernetes
   176  // api
   177  func Deployment(opts *Options) (*appsv1.Deployment, error) {
   178  	dep, err := generateDeployment(opts)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	dep.TypeMeta = metav1.TypeMeta{
   183  		Kind:       "Deployment",
   184  		APIVersion: "apps/v1",
   185  	}
   186  	return dep, nil
   187  }
   188  
   189  // createService creates the Tiller service resource
   190  func createService(client corev1.ServicesGetter, namespace string) error {
   191  	obj := generateService(namespace)
   192  	_, err := client.Services(obj.Namespace).Create(obj)
   193  	return err
   194  }
   195  
   196  // Service gets a service object that can be used to generate a manifest as a
   197  // string. This object should not be submitted directly to the Kubernetes api
   198  func Service(namespace string) *v1.Service {
   199  	svc := generateService(namespace)
   200  	svc.TypeMeta = metav1.TypeMeta{
   201  		Kind:       "Service",
   202  		APIVersion: "v1",
   203  	}
   204  	return svc
   205  }
   206  
   207  // TillerManifests gets the Deployment, Service, and Secret (if tls-enabled) manifests
   208  func TillerManifests(opts *Options) ([]string, error) {
   209  	dep, err := Deployment(opts)
   210  	if err != nil {
   211  		return []string{}, err
   212  	}
   213  
   214  	svc := Service(opts.Namespace)
   215  
   216  	objs := []runtime.Object{dep, svc}
   217  
   218  	if opts.EnableTLS {
   219  		secret, err := Secret(opts)
   220  		if err != nil {
   221  			return []string{}, err
   222  		}
   223  		objs = append(objs, secret)
   224  	}
   225  
   226  	manifests := make([]string, len(objs))
   227  	for i, obj := range objs {
   228  		o, err := yaml.Marshal(obj)
   229  		if err != nil {
   230  			return []string{}, err
   231  		}
   232  		manifests[i] = string(o)
   233  	}
   234  
   235  	return manifests, err
   236  }
   237  
   238  func generateLabels(labels map[string]string) map[string]string {
   239  	labels["app"] = "helm"
   240  	return labels
   241  }
   242  
   243  // parseNodeSelectorsInto parses a comma delimited list of key=values pairs into a map.
   244  func parseNodeSelectorsInto(labels string, m map[string]string) error {
   245  	kv := strings.Split(labels, ",")
   246  	for _, v := range kv {
   247  		el := strings.Split(v, "=")
   248  		if len(el) == 2 {
   249  			m[el[0]] = el[1]
   250  		} else {
   251  			return fmt.Errorf("invalid nodeSelector label: %q", kv)
   252  		}
   253  	}
   254  	return nil
   255  }
   256  func generateDeployment(opts *Options) (*appsv1.Deployment, error) {
   257  	labels := generateLabels(map[string]string{"name": "tiller"})
   258  	nodeSelectors := map[string]string{}
   259  	if len(opts.NodeSelectors) > 0 {
   260  		err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors)
   261  		if err != nil {
   262  			return nil, err
   263  		}
   264  	}
   265  	d := &appsv1.Deployment{
   266  		ObjectMeta: metav1.ObjectMeta{
   267  			Namespace: opts.Namespace,
   268  			Name:      deploymentName,
   269  			Labels:    labels,
   270  		},
   271  		Spec: appsv1.DeploymentSpec{
   272  			Replicas: opts.getReplicas(),
   273  			Selector: &metav1.LabelSelector{
   274  				MatchLabels: labels,
   275  			},
   276  			Template: v1.PodTemplateSpec{
   277  				ObjectMeta: metav1.ObjectMeta{
   278  					Labels: labels,
   279  				},
   280  				Spec: v1.PodSpec{
   281  					ServiceAccountName:           opts.ServiceAccount,
   282  					AutomountServiceAccountToken: &opts.AutoMountServiceAccountToken,
   283  					Containers: []v1.Container{
   284  						{
   285  							Name:            "tiller",
   286  							Image:           opts.SelectImage(),
   287  							ImagePullPolicy: opts.pullPolicy(),
   288  							Ports: []v1.ContainerPort{
   289  								{ContainerPort: environment.DefaultTillerPort, Name: "tiller"},
   290  								{ContainerPort: environment.DefaultTillerProbePort, Name: "http"},
   291  							},
   292  							Env: []v1.EnvVar{
   293  								{Name: "TILLER_NAMESPACE", Value: opts.Namespace},
   294  								{Name: "TILLER_HISTORY_MAX", Value: fmt.Sprintf("%d", opts.MaxHistory)},
   295  							},
   296  							LivenessProbe: &v1.Probe{
   297  								Handler: v1.Handler{
   298  									HTTPGet: &v1.HTTPGetAction{
   299  										Path: "/liveness",
   300  										Port: intstr.FromInt(environment.DefaultTillerProbePort),
   301  									},
   302  								},
   303  								InitialDelaySeconds: 1,
   304  								TimeoutSeconds:      1,
   305  							},
   306  							ReadinessProbe: &v1.Probe{
   307  								Handler: v1.Handler{
   308  									HTTPGet: &v1.HTTPGetAction{
   309  										Path: "/readiness",
   310  										Port: intstr.FromInt(environment.DefaultTillerProbePort),
   311  									},
   312  								},
   313  								InitialDelaySeconds: 1,
   314  								TimeoutSeconds:      1,
   315  							},
   316  						},
   317  					},
   318  					HostNetwork:  opts.EnableHostNetwork,
   319  					NodeSelector: nodeSelectors,
   320  				},
   321  			},
   322  		},
   323  	}
   324  
   325  	if opts.tls() {
   326  		const certsDir = "/etc/certs"
   327  
   328  		var tlsVerify, tlsEnable = "", "1"
   329  		if opts.VerifyTLS {
   330  			tlsVerify = "1"
   331  		}
   332  
   333  		// Mount secret to "/etc/certs"
   334  		d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
   335  			Name:      "tiller-certs",
   336  			ReadOnly:  true,
   337  			MountPath: certsDir,
   338  		})
   339  		// Add environment variable required for enabling TLS
   340  		d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []v1.EnvVar{
   341  			{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
   342  			{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
   343  			{Name: "TILLER_TLS_CERTS", Value: certsDir},
   344  		}...)
   345  		// Add secret volume to deployment
   346  		d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v1.Volume{
   347  			Name: "tiller-certs",
   348  			VolumeSource: v1.VolumeSource{
   349  				Secret: &v1.SecretVolumeSource{
   350  					SecretName: "tiller-secret",
   351  				},
   352  			},
   353  		})
   354  	}
   355  	// if --override values were specified, ultimately convert values and deployment to maps,
   356  	// merge them and convert back to Deployment
   357  	if len(opts.Values) > 0 {
   358  		// base deployment struct
   359  		var dd appsv1.Deployment
   360  		// get YAML from original deployment
   361  		dy, err := yaml.Marshal(d)
   362  		if err != nil {
   363  			return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err)
   364  		}
   365  		// convert deployment YAML to values
   366  		dv, err := chartutil.ReadValues(dy)
   367  		if err != nil {
   368  			return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err)
   369  		}
   370  		dm := dv.AsMap()
   371  		// merge --set values into our map
   372  		sm, err := opts.valuesMap(dm)
   373  		if err != nil {
   374  			return nil, fmt.Errorf("Error merging --set values into Deployment manifest")
   375  		}
   376  		finalY, err := yaml.Marshal(sm)
   377  		if err != nil {
   378  			return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err)
   379  		}
   380  		// convert merged values back into deployment
   381  		err = yaml.Unmarshal(finalY, &dd)
   382  		if err != nil {
   383  			return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err)
   384  		}
   385  		d = &dd
   386  	}
   387  
   388  	return d, nil
   389  }
   390  
   391  func generateService(namespace string) *v1.Service {
   392  	labels := generateLabels(map[string]string{"name": "tiller"})
   393  	s := &v1.Service{
   394  		ObjectMeta: metav1.ObjectMeta{
   395  			Namespace: namespace,
   396  			Name:      serviceName,
   397  			Labels:    labels,
   398  		},
   399  		Spec: v1.ServiceSpec{
   400  			Type: v1.ServiceTypeClusterIP,
   401  			Ports: []v1.ServicePort{
   402  				{
   403  					Name:       "tiller",
   404  					Port:       environment.DefaultTillerPort,
   405  					TargetPort: intstr.FromString("tiller"),
   406  				},
   407  			},
   408  			Selector: labels,
   409  		},
   410  	}
   411  	return s
   412  }
   413  
   414  // Secret gets a secret object that can be used to generate a manifest as a
   415  // string. This object should not be submitted directly to the Kubernetes api
   416  func Secret(opts *Options) (*v1.Secret, error) {
   417  	secret, err := generateSecret(opts)
   418  	if err != nil {
   419  		return nil, err
   420  	}
   421  
   422  	secret.TypeMeta = metav1.TypeMeta{
   423  		Kind:       "Secret",
   424  		APIVersion: "v1",
   425  	}
   426  
   427  	return secret, nil
   428  }
   429  
   430  // createSecret creates the Tiller secret resource.
   431  func createSecret(client corev1.SecretsGetter, opts *Options) error {
   432  	o, err := generateSecret(opts)
   433  	if err != nil {
   434  		return err
   435  	}
   436  	_, err = client.Secrets(o.Namespace).Create(o)
   437  	return err
   438  }
   439  
   440  // generateSecret builds the secret object that hold Tiller secrets.
   441  func generateSecret(opts *Options) (*v1.Secret, error) {
   442  
   443  	labels := generateLabels(map[string]string{"name": "tiller"})
   444  	secret := &v1.Secret{
   445  		Type: v1.SecretTypeOpaque,
   446  		Data: make(map[string][]byte),
   447  		ObjectMeta: metav1.ObjectMeta{
   448  			Name:      secretName,
   449  			Labels:    labels,
   450  			Namespace: opts.Namespace,
   451  		},
   452  	}
   453  	var err error
   454  	if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
   455  		return nil, err
   456  	}
   457  	if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
   458  		return nil, err
   459  	}
   460  	if opts.VerifyTLS {
   461  		if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
   462  			return nil, err
   463  		}
   464  	}
   465  	return secret, nil
   466  }
   467  
   468  func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }