001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017 MicroBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.helm; 018 019import java.io.BufferedInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.InputStream; 022import java.io.IOException; 023 024import java.net.URI; 025import java.net.URL; 026 027import java.util.Arrays; 028import java.util.ArrayList; 029import java.util.Base64; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.Objects; 034 035import io.fabric8.kubernetes.client.DefaultKubernetesClient; 036import io.fabric8.kubernetes.client.KubernetesClient; 037import io.fabric8.kubernetes.client.KubernetesClientException; 038 039import io.fabric8.kubernetes.api.model.Container; 040import io.fabric8.kubernetes.api.model.ContainerPort; 041import io.fabric8.kubernetes.api.model.EnvVar; 042import io.fabric8.kubernetes.api.model.HTTPGetAction; 043import io.fabric8.kubernetes.api.model.IntOrString; 044import io.fabric8.kubernetes.api.model.ObjectMeta; 045import io.fabric8.kubernetes.api.model.PodSpec; 046import io.fabric8.kubernetes.api.model.PodTemplateSpec; 047import io.fabric8.kubernetes.api.model.Probe; 048import io.fabric8.kubernetes.api.model.Secret; 049import io.fabric8.kubernetes.api.model.SecretVolumeSource; 050import io.fabric8.kubernetes.api.model.Service; 051import io.fabric8.kubernetes.api.model.ServicePort; 052import io.fabric8.kubernetes.api.model.ServiceSpec; 053import io.fabric8.kubernetes.api.model.Status; 054import io.fabric8.kubernetes.api.model.Volume; 055import io.fabric8.kubernetes.api.model.VolumeMount; 056 057import io.fabric8.kubernetes.api.model.extensions.Deployment; 058import io.fabric8.kubernetes.api.model.extensions.DeploymentSpec; 059 060import org.microbean.development.annotation.Experimental; 061 062/** 063 * A class that idiomatically but faithfully emulates the 064 * Tiller-installing behavior of the {@code helm init} command. 065 * 066 * <p>In general, this class follows the logic as expressed in <a 067 * href="https://github.com/kubernetes/helm/blob/master/cmd/helm/installer/install.go">the 068 * {@code install.go} source code from the Helm project</a>, 069 * problematic or not. The intent is to have an installer, usable as 070 * an idiomatic Java library, that behaves just like {@code helm 071 * init}.</p> 072 * 073 * <p><strong>Note:</strong> This class is experimental and its API is 074 * subject to change without notice.</p> 075 * 076 * @author <a href="https://about.me/lairdnelson" 077 * target="_parent">Laird Nelson</a> 078 * 079 * @see #init() 080 * 081 * @see <a 082 * href="https://github.com/kubernetes/helm/blob/master/cmd/helm/installer/install.go">The 083 * <code>install.go</code> source code from the Helm project</a> 084 */ 085@Experimental 086public class TillerInstaller { 087 088 089 /* 090 * Static fields. 091 */ 092 093 094 /* 095 * Atomic static fields. 096 */ 097 098 private static final Integer ONE = Integer.valueOf(1); 099 100 private static final ImagePullPolicy DEFAULT_IMAGE_PULL_POLICY = ImagePullPolicy.IF_NOT_PRESENT; 101 102 private static final String DEFAULT_NAME = "tiller"; 103 104 private static final String DEFAULT_NAMESPACE = "kube-system"; 105 106 private static final String TILLER_TLS_CERTS_PATH = "/etc/certs"; 107 108 /** 109 * The version of Tiller to install. 110 */ 111 public static final String VERSION = "2.6.2"; 112 113 /* 114 * Derivative static fields. 115 */ 116 117 private static final String DEFAULT_IMAGE_NAME = "gcr.io/kubernetes-helm/" + DEFAULT_NAME + ":v" + VERSION; 118 119 private static final String DEFAULT_DEPLOYMENT_NAME = DEFAULT_NAME + "-deploy"; 120 121 private static final String SECRET_NAME = DEFAULT_NAME + "-secret"; 122 123 124 /* 125 * Instance fields. 126 */ 127 128 129 private final KubernetesClient kubernetesClient; 130 131 private final String tillerNamespace; 132 133 134 /* 135 * Constructors. 136 */ 137 138 139 /** 140 * Creates a new {@link TillerInstaller}, using a new {@link 141 * DefaultKubernetesClient}. 142 * 143 * @see #TillerInstaller(KubernetesClient) 144 */ 145 public TillerInstaller() { 146 this(new DefaultKubernetesClient()); 147 } 148 149 /** 150 * Creates a new {@link TillerInstaller}. 151 * 152 * @param kubernetesClient the {@link KubernetesClient} to use to 153 * communicate with Kubernetes; must not be {@code null} 154 * 155 * @exception NullPointerException if {@code kubernetesClient} is 156 * {@code null} 157 */ 158 public TillerInstaller(final KubernetesClient kubernetesClient) { 159 super(); 160 Objects.requireNonNull(kubernetesClient); 161 this.kubernetesClient = kubernetesClient; 162 final String tillerNamespace = System.getenv("TILLER_NAMESPACE"); 163 if (tillerNamespace == null || tillerNamespace.isEmpty()) { 164 this.tillerNamespace = DEFAULT_NAMESPACE; 165 } else { 166 this.tillerNamespace = tillerNamespace; 167 } 168 } 169 170 171 /* 172 * Instance methods. 173 */ 174 175 176 177 public void init() { 178 try { 179 this.init(false, null, null, null, null, null, null, null, false, false, false, null, null, null); 180 } catch (final IOException willNotHappen) { 181 throw new AssertionError(willNotHappen); 182 } 183 } 184 185 public void init(final boolean upgrade) { 186 try { 187 this.init(upgrade, null, null, null, null, null, null, null, false, false, false, null, null, null); 188 } catch (final IOException willNotHappen) { 189 throw new AssertionError(willNotHappen); 190 } 191 } 192 193 /** 194 * Attempts to {@linkplain #install(String, String, String, Map, 195 * String, String, ImagePullPolicy, boolean, boolean, boolean, URI, 196 * URI, URI) install} Tiller into the Kubernetes cluster, silently 197 * returning if Tiller is already installed and {@code upgrade} is 198 * {@code false}, or {@linkplain #upgrade(String, String, String, 199 * String, String, ImagePullPolicy, Map) upgrading} the Tiller 200 * installation if {@code upgrade} is {@code true} and a newer 201 * version of Tiller is available. 202 * 203 * @param upgrade whether or not to attempt an upgrade if Tiller is 204 * already installed 205 * 206 * @param namespace the Kubernetes namespace into which Tiller will 207 * be installed, if it is not already installed; may be {@code null} 208 * in which case a default will be used 209 * 210 * @param deploymentName the name that the Kubernetes Deployment 211 * representing Tiller will have; may be {@code null}; {@code 212 * tiller-deploy} by default 213 * 214 * @param serviceName the name that the Kubernetes Service 215 * representing Tiller will have; may be {@code null}; {@code 216 * tiller-deploy} (yes, {@code tiller-deploy}) by default 217 * 218 * @param labels the Kubernetes Labels that will be applied to 219 * various Kubernetes resources representing Tiller; may be {@code 220 * null} in which case a {@link Map} consisting of a label of {@code 221 * app} with a value of {@code helm} and a label of {@code name} 222 * with a value of {@code tiller} will be used instead 223 * 224 * @param serviceAccountName the name of the Kubernetes Service 225 * Account that Tiller should use; may be {@code null} in which case 226 * the default Service Account will be used instead 227 * 228 * @param imageName the name of the Docker image that contains the 229 * Tiller code; may be {@code null} in which case the Java {@link 230 * String} <code>"gcr.io/kubernetes-helm/tiller:v" + {@value 231 * #VERSION}</code> will be used instead 232 * 233 * @param imagePullPolicy an {@link ImagePullPolicy} specifying how 234 * the Tiller image should be pulled; may be {@code null} in which 235 * case {@link ImagePullPolicy#IF_NOT_PRESENT} will be used instead 236 * 237 * @param hostNetwork the value to be used for the {@linkplain 238 * PodSpec#setHostNetwork(Boolean) <code>hostNetwork</code> 239 * property} of the Tiller Pod's {@link PodSpec} 240 * 241 * @param tls whether Tiller's conversations with Kubernetes will be 242 * encrypted using TLS 243 * 244 * @param verifyTls whether, if and only if {@code tls} is {@code 245 * true}, additional TLS-related verification will be performed 246 * 247 * @param tlsKeyUri a {@link URI} to the public key used during TLS 248 * communication with Kubernetes; may be {@code null} if {@code tls} 249 * is {@code false} 250 * 251 * @param tlsCertUri a {@link URI} to the certificate used during 252 * TLS communication with Kubernetes; may be {@code null} if {@code 253 * tls} is {@code false} 254 * 255 * @param tlsCaCertUri a {@link URI} to the certificate authority 256 * used during TLS communication with Kubernetes; may be {@code 257 * null} if {@code tls} is {@code false} 258 * 259 * @exception IOException if a communication error occurs 260 * 261 * @see #install(String, String, String, Map, String, String, 262 * ImagePullPolicy, boolean, boolean, boolean, URI, URI, URI) 263 * 264 * @see #upgrade(String, String, String, String, String, 265 * ImagePullPolicy, Map) 266 */ 267 public void init(final boolean upgrade, 268 String namespace, 269 String deploymentName, 270 String serviceName, 271 Map<String, String> labels, 272 String serviceAccountName, 273 String imageName, 274 final ImagePullPolicy imagePullPolicy, 275 final boolean hostNetwork, 276 final boolean tls, 277 final boolean verifyTls, 278 final URI tlsKeyUri, 279 final URI tlsCertUri, 280 final URI tlsCaCertUri) 281 throws IOException { 282 namespace = normalizeNamespace(namespace); 283 deploymentName = normalizeDeploymentName(deploymentName); 284 serviceName = normalizeServiceName(serviceName); 285 labels = normalizeLabels(labels); 286 serviceAccountName = normalizeServiceAccountName(serviceAccountName); 287 imageName = normalizeImageName(imageName); 288 289 try { 290 this.install(namespace, 291 deploymentName, 292 serviceName, 293 labels, 294 serviceAccountName, 295 imageName, 296 imagePullPolicy, 297 hostNetwork, 298 tls, 299 verifyTls, 300 tlsKeyUri, 301 tlsCertUri, 302 tlsCaCertUri); 303 } catch (final KubernetesClientException kubernetesClientException) { 304 final Status status = kubernetesClientException.getStatus(); 305 if (status == null || !"AlreadyExists".equals(status.getReason())) { 306 throw kubernetesClientException; 307 } else if (upgrade) { 308 this.upgrade(namespace, 309 deploymentName, 310 serviceName, 311 serviceAccountName, 312 imageName, 313 imagePullPolicy, 314 labels); 315 } 316 } 317 } 318 319 public void install() { 320 try { 321 this.install(null, null, null, null, null, null, null, false, false, false, null, null, null); 322 } catch (final IOException willNotHappen) { 323 throw new AssertionError(willNotHappen); 324 } 325 } 326 327 public void install(String namespace, 328 final String deploymentName, 329 final String serviceName, 330 Map<String, String> labels, 331 final String serviceAccountName, 332 final String imageName, 333 final ImagePullPolicy imagePullPolicy, 334 final boolean hostNetwork, 335 final boolean tls, 336 final boolean verifyTls, 337 final URI tlsKeyUri, 338 final URI tlsCertUri, 339 final URI tlsCaCertUri) 340 throws IOException { 341 namespace = normalizeNamespace(namespace); 342 labels = normalizeLabels(labels); 343 final Deployment deployment = 344 this.createDeployment(namespace, 345 normalizeDeploymentName(deploymentName), 346 labels, 347 normalizeServiceAccountName(serviceAccountName), 348 normalizeImageName(imageName), 349 imagePullPolicy, 350 hostNetwork, 351 tls, 352 verifyTls); 353 354 this.kubernetesClient.extensions().deployments().inNamespace(namespace).create(deployment); 355 356 final Service service = this.createService(namespace, normalizeServiceName(serviceName), labels); 357 this.kubernetesClient.services().inNamespace(namespace).create(service); 358 359 if (tls) { 360 final Secret secret = 361 this.createSecret(namespace, 362 tlsKeyUri, 363 tlsCertUri, 364 tlsCaCertUri, 365 labels); 366 this.kubernetesClient.secrets().inNamespace(namespace).create(secret); 367 } 368 369 } 370 371 public void upgrade() { 372 this.upgrade(null, null, null, null, null, null, null); 373 } 374 375 public void upgrade(String namespace, 376 final String deploymentName, 377 String serviceName, 378 final String serviceAccountName, 379 final String imageName, 380 final ImagePullPolicy imagePullPolicy, 381 final Map<String, String> labels) { 382 namespace = normalizeNamespace(namespace); 383 serviceName = normalizeServiceName(serviceName); 384 385 this.kubernetesClient.extensions() 386 .deployments() 387 .inNamespace(namespace) 388 .withName(normalizeDeploymentName(deploymentName)) 389 .edit() 390 .editSpec() 391 .editTemplate() 392 .editSpec() 393 .editContainer(0) 394 .withImage(normalizeImageName(imageName)) 395 .withImagePullPolicy(normalizeImagePullPolicy(imagePullPolicy)) 396 .and() 397 .withServiceAccountName(normalizeServiceAccountName(serviceAccountName)) 398 .endSpec() 399 .endTemplate() 400 .endSpec() 401 .done(); 402 403 // TODO: this way of emulating install.go's check to see if the 404 // Service exists...not sure it's right 405 final Service service = this.kubernetesClient.services() 406 .inNamespace(namespace) 407 .withName(serviceName) 408 .get(); 409 if (service == null) { 410 this.createService(namespace, serviceName, normalizeLabels(labels)); 411 } 412 413 } 414 415 protected Service createService(final String namespace, 416 final String serviceName, 417 Map<String, String> labels) { 418 labels = normalizeLabels(labels); 419 420 final Service service = new Service(); 421 422 final ObjectMeta metadata = new ObjectMeta(); 423 metadata.setNamespace(normalizeNamespace(namespace)); 424 metadata.setName(normalizeServiceName(serviceName)); 425 metadata.setLabels(labels); 426 427 service.setMetadata(metadata); 428 service.setSpec(this.createServiceSpec(labels)); 429 430 return service; 431 } 432 433 protected Deployment createDeployment(String namespace, 434 final String deploymentName, 435 Map<String, String> labels, 436 final String serviceAccountName, 437 final String imageName, 438 final ImagePullPolicy imagePullPolicy, 439 final boolean hostNetwork, 440 final boolean tls, 441 final boolean verifyTls) { 442 namespace = normalizeNamespace(namespace); 443 labels = normalizeLabels(labels); 444 445 final Deployment deployment = new Deployment(); 446 447 final ObjectMeta metadata = new ObjectMeta(); 448 metadata.setNamespace(namespace); 449 metadata.setName(normalizeDeploymentName(deploymentName)); 450 metadata.setLabels(labels); 451 deployment.setMetadata(metadata); 452 453 deployment.setSpec(this.createDeploymentSpec(labels, serviceAccountName, imageName, imagePullPolicy, namespace, hostNetwork, tls, verifyTls)); 454 return deployment; 455 } 456 457 protected Secret createSecret(final String namespace, 458 final URI tlsKeyUri, 459 final URI tlsCertUri, 460 final URI tlsCaCertUri, 461 final Map<String, String> labels) 462 throws IOException { 463 464 final Secret secret = new Secret(); 465 secret.setType("Opaque"); 466 467 final Map<String, String> secretData = new HashMap<>(); 468 469 try (final InputStream tlsKeyStream = read(tlsKeyUri)) { 470 if (tlsKeyStream != null) { 471 secretData.put("tls.key", Base64.getEncoder().encodeToString(toByteArray(tlsKeyStream))); 472 } 473 } 474 475 try (final InputStream tlsCertStream = read(tlsCertUri)) { 476 if (tlsCertStream != null) { 477 secretData.put("tls.crt", Base64.getEncoder().encodeToString(toByteArray(tlsCertStream))); 478 } 479 } 480 481 try (final InputStream tlsCaCertStream = read(tlsCaCertUri)) { 482 if (tlsCaCertStream != null) { 483 secretData.put("ca.crt", Base64.getEncoder().encodeToString(toByteArray(tlsCaCertStream))); 484 } 485 } 486 487 secret.setData(secretData); 488 489 final ObjectMeta metadata = new ObjectMeta(); 490 metadata.setNamespace(normalizeNamespace(namespace)); 491 metadata.setName(SECRET_NAME); 492 metadata.setLabels(normalizeLabels(labels)); 493 secret.setMetadata(metadata); 494 495 return secret; 496 } 497 498 protected DeploymentSpec createDeploymentSpec(final Map<String, String> labels, 499 final String serviceAccountName, 500 final String imageName, 501 final ImagePullPolicy imagePullPolicy, 502 final String namespace, 503 final boolean hostNetwork, 504 final boolean tls, 505 final boolean verifyTls) { 506 final DeploymentSpec deploymentSpec = new DeploymentSpec(); 507 final PodTemplateSpec podTemplateSpec = new PodTemplateSpec(); 508 final ObjectMeta metadata = new ObjectMeta(); 509 metadata.setLabels(normalizeLabels(labels)); 510 podTemplateSpec.setMetadata(metadata); 511 final PodSpec podSpec = new PodSpec(); 512 podSpec.setServiceAccountName(normalizeServiceAccountName(serviceAccountName)); 513 podSpec.setContainers(Arrays.asList(this.createContainer(imageName, imagePullPolicy, namespace, tls, verifyTls))); 514 podSpec.setHostNetwork(Boolean.valueOf(hostNetwork)); 515 final Map<String, String> nodeSelector = new HashMap<>(); 516 nodeSelector.put("beta.kubernetes.io/os", "linux"); 517 podSpec.setNodeSelector(nodeSelector); 518 if (tls) { 519 final Volume volume = new Volume(); 520 volume.setName(DEFAULT_NAME + "-certs"); 521 final SecretVolumeSource secretVolumeSource = new SecretVolumeSource(); 522 secretVolumeSource.setSecretName(SECRET_NAME); 523 volume.setSecret(secretVolumeSource); 524 podSpec.setVolumes(Arrays.asList(volume)); 525 } 526 podTemplateSpec.setSpec(podSpec); 527 deploymentSpec.setTemplate(podTemplateSpec); 528 return deploymentSpec; 529 } 530 531 protected Container createContainer(final String imageName, 532 final ImagePullPolicy imagePullPolicy, 533 final String namespace, 534 final boolean tls, 535 final boolean verifyTls) { 536 final Container container = new Container(); 537 container.setName(DEFAULT_NAME); 538 container.setImage(normalizeImageName(imageName)); 539 container.setImagePullPolicy(normalizeImagePullPolicy(imagePullPolicy)); 540 541 final ContainerPort containerPort = new ContainerPort(); 542 containerPort.setContainerPort(Integer.valueOf(44134)); 543 containerPort.setName(DEFAULT_NAME); 544 container.setPorts(Arrays.asList(containerPort)); 545 546 final List<EnvVar> env = new ArrayList<>(); 547 548 final EnvVar tillerNamespace = new EnvVar(); 549 tillerNamespace.setName("TILLER_NAMESPACE"); 550 tillerNamespace.setValue(normalizeNamespace(namespace)); 551 env.add(tillerNamespace); 552 553 if (tls) { 554 final EnvVar tlsVerify = new EnvVar(); 555 tlsVerify.setName("TILLER_TLS_VERIFY"); 556 tlsVerify.setValue(verifyTls ? "1" : ""); 557 env.add(tlsVerify); 558 559 final EnvVar tlsEnable = new EnvVar(); 560 tlsEnable.setName("TILLER_TLS_ENABLE"); 561 tlsEnable.setValue("1"); 562 env.add(tlsEnable); 563 564 final EnvVar tlsCerts = new EnvVar(); 565 tlsCerts.setName("TILLER_TLS_CERTS"); 566 tlsCerts.setValue(TILLER_TLS_CERTS_PATH); 567 env.add(tlsCerts); 568 } 569 570 container.setEnv(env); 571 572 573 final IntOrString port44135 = new IntOrString(Integer.valueOf(44135)); 574 575 final HTTPGetAction livenessHttpGetAction = new HTTPGetAction(); 576 livenessHttpGetAction.setPath("/liveness"); 577 livenessHttpGetAction.setPort(port44135); 578 final Probe livenessProbe = new Probe(); 579 livenessProbe.setHttpGet(livenessHttpGetAction); 580 livenessProbe.setInitialDelaySeconds(ONE); 581 livenessProbe.setTimeoutSeconds(ONE); 582 container.setLivenessProbe(livenessProbe); 583 584 final HTTPGetAction readinessHttpGetAction = new HTTPGetAction(); 585 readinessHttpGetAction.setPath("/readiness"); 586 readinessHttpGetAction.setPort(port44135); 587 final Probe readinessProbe = new Probe(); 588 readinessProbe.setHttpGet(readinessHttpGetAction); 589 readinessProbe.setInitialDelaySeconds(ONE); 590 readinessProbe.setTimeoutSeconds(ONE); 591 container.setReadinessProbe(readinessProbe); 592 593 if (tls) { 594 final VolumeMount volumeMount = new VolumeMount(); 595 volumeMount.setName(DEFAULT_NAME + "-certs"); 596 volumeMount.setReadOnly(true); 597 volumeMount.setMountPath(TILLER_TLS_CERTS_PATH); 598 container.setVolumeMounts(Arrays.asList(volumeMount)); 599 } 600 601 return container; 602 } 603 604 protected ServiceSpec createServiceSpec(final Map<String, String> labels) { 605 final ServiceSpec serviceSpec = new ServiceSpec(); 606 serviceSpec.setType("ClusterIP"); 607 608 final ServicePort servicePort = new ServicePort(); 609 servicePort.setName(DEFAULT_NAME); 610 servicePort.setPort(Integer.valueOf(44134)); 611 servicePort.setTargetPort(new IntOrString(DEFAULT_NAME)); 612 serviceSpec.setPorts(Arrays.asList(servicePort)); 613 614 serviceSpec.setSelector(normalizeLabels(labels)); 615 return serviceSpec; 616 } 617 618 protected final String normalizeNamespace(String namespace) { 619 if (namespace == null || namespace.isEmpty()) { 620 namespace = this.tillerNamespace; 621 if (namespace == null || namespace.isEmpty()) { 622 namespace = DEFAULT_NAMESPACE; 623 } 624 } 625 return namespace; 626 } 627 628 629 /* 630 * Static methods. 631 */ 632 633 634 protected static final Map<String, String> normalizeLabels(Map<String, String> labels) { 635 if (labels == null) { 636 labels = new HashMap<>(7); 637 } 638 if (!labels.containsKey("app")) { 639 labels.put("app", "helm"); 640 } 641 if (!labels.containsKey("name")) { 642 labels.put("name", DEFAULT_NAME); 643 } 644 return labels; 645 } 646 647 protected static final String normalizeDeploymentName(final String deploymentName) { 648 if (deploymentName == null || deploymentName.isEmpty()) { 649 return DEFAULT_DEPLOYMENT_NAME; 650 } else { 651 return deploymentName; 652 } 653 } 654 655 protected static final String normalizeImageName(final String imageName) { 656 if (imageName == null || imageName.isEmpty()) { 657 return DEFAULT_IMAGE_NAME; 658 } else { 659 return imageName; 660 } 661 } 662 663 private static final String normalizeImagePullPolicy(ImagePullPolicy imagePullPolicy) { 664 if (imagePullPolicy == null) { 665 imagePullPolicy = DEFAULT_IMAGE_PULL_POLICY; 666 } 667 assert imagePullPolicy != null; 668 return imagePullPolicy.toString(); 669 } 670 671 protected static final String normalizeServiceAccountName(final String serviceAccountName) { 672 return serviceAccountName == null ? "" : serviceAccountName; 673 } 674 675 protected static final String normalizeServiceName(final String serviceName) { 676 if (serviceName == null || serviceName.isEmpty()) { 677 return DEFAULT_DEPLOYMENT_NAME; // yes, DEFAULT_*DEPLOYMENT*_NAME 678 } else { 679 return serviceName; 680 } 681 } 682 683 private static final InputStream read(final URI uri) throws IOException { 684 final InputStream returnValue; 685 if (uri == null) { 686 returnValue = null; 687 } else { 688 final URL url = uri.toURL(); 689 assert url != null; 690 final InputStream uriStream = url.openStream(); 691 if (uriStream == null) { 692 returnValue = null; 693 } else if (uriStream instanceof BufferedInputStream) { 694 returnValue = (BufferedInputStream)uriStream; 695 } else { 696 returnValue = new BufferedInputStream(uriStream); 697 } 698 } 699 return returnValue; 700 } 701 702 private static final byte[] toByteArray(final InputStream inputStream) throws IOException { 703 // Interesting historical anecdotes at https://stackoverflow.com/a/1264737/208288. 704 byte[] returnValue = null; 705 if (inputStream != null) { 706 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 707 returnValue = new byte[4096]; // arbitrary size 708 int bytesRead; 709 while ((bytesRead = inputStream.read(returnValue, 0, returnValue.length)) != -1) { 710 buffer.write(returnValue, 0, bytesRead); 711 } 712 buffer.flush(); 713 returnValue = buffer.toByteArray(); 714 } 715 return returnValue; 716 } 717 718 719 /* 720 * Inner and nested classes. 721 */ 722 723 724 /** 725 * An {@code enum} representing valid values for a Kubernetes {@code 726 * imagePullPolicy} field. 727 * 728 * @author <a href="https://about.me/lairdnelson" 729 * target="_parent">Laird Nelson</a> 730 */ 731 public static enum ImagePullPolicy { 732 733 734 /** 735 * An {@link ImagePullPolicy} indicating that a Docker image 736 * should always be pulled. 737 */ 738 ALWAYS("Always"), 739 740 /** 741 * An {@link ImagePullPolicy} indicating that a Docker image 742 * should be pulled only if it is not already cached locally. 743 */ 744 IF_NOT_PRESENT("IfNotPresent"), 745 746 /** 747 * An {@link ImagePullPolicy} indicating that a Docker image 748 * should never be pulled. 749 */ 750 NEVER("Never"); 751 752 /** 753 * The actual valid Kubernetes value for this {@link 754 * ImagePullPolicy}. 755 * 756 * <p>This field is never {@code null}.</p> 757 */ 758 private final String value; 759 760 761 /* 762 * Constructors. 763 */ 764 765 766 /** 767 * Creates a new {@link ImagePullPolicy}. 768 * 769 * @param value the valid Kubernetes value for this {@link 770 * ImagePullPolicy}; must not be {@code null} 771 * 772 * @exception NullPointerException if {@code value} is {@code 773 * null} 774 */ 775 ImagePullPolicy(final String value) { 776 Objects.requireNonNull(value); 777 this.value = value; 778 } 779 780 781 /* 782 * Instance methods. 783 */ 784 785 786 /** 787 * Returns the valid Kubernetes value for this {@link 788 * ImagePullPolicy}. 789 * 790 * <p>This method never returns {@code null}.</p> 791 * 792 * @return the valid Kubernetes value for this {@link 793 * ImagePullPolicy}; never {@code null} 794 */ 795 @Override 796 public final String toString() { 797 return this.value; 798 } 799 } 800 801}