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