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