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) }