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} 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 {@code 230 * gcr.io/kubernetes-helm/tiller:v2.5.0} will be used instead 231 * 232 * @param imagePullPolicy an {@link ImagePullPolicy} specifying how 233 * the Tiller image should be pulled; may be {@code null} in which 234 * case {@link ImagePullPolicy#IF_NOT_PRESENT} will be used instead 235 * 236 * @param hostNetwork the value to be used for the {@linkplain 237 * PodSpec#setHostNetwork(Boolean) <code>hostNetwork</code> 238 * property} of the Tiller Pod's {@link PodSpec} 239 * 240 * @param tls whether Tiller's conversations with Kubernetes will be 241 * encrypted using TLS 242 * 243 * @param verifyTls whether, if and only if {@code tls} is {@code 244 * true}, additional TLS-related verification will be performed 245 * 246 * @param tlsKeyUri a {@link URI} to the public key used during TLS 247 * communication with Kubernetes; may be {@code null} if {@code tls} 248 * is {@code false} 249 * 250 * @param tlsCertUri a {@link URI} to the certificate used during 251 * TLS communication with Kubernetes; may be {@code null} if {@code 252 * tls} is {@code false} 253 * 254 * @param tlsCaCertUri a {@link URI} to the certificate authority 255 * used during TLS communication with Kubernetes; may be {@code 256 * null} if {@code tls} is {@code false} 257 * 258 * @exception IOException if a communication error occurs 259 * 260 * @see #install(String, String, String, Map, String, String, 261 * ImagePullPolicy, boolean, boolean, boolean, URI, URI, URI) 262 * 263 * @see #upgrade(String, String, String, String, String, 264 * ImagePullPolicy, Map) 265 */ 266 public void init(final boolean upgrade, 267 String namespace, 268 String deploymentName, 269 String serviceName, 270 Map<String, String> labels, 271 String serviceAccountName, 272 String imageName, 273 final ImagePullPolicy imagePullPolicy, 274 final boolean hostNetwork, 275 final boolean tls, 276 final boolean verifyTls, 277 final URI tlsKeyUri, 278 final URI tlsCertUri, 279 final URI tlsCaCertUri) 280 throws IOException { 281 namespace = normalizeNamespace(namespace); 282 deploymentName = normalizeDeploymentName(deploymentName); 283 serviceName = normalizeServiceName(serviceName); 284 labels = normalizeLabels(labels); 285 serviceAccountName = normalizeServiceAccountName(serviceAccountName); 286 imageName = normalizeImageName(imageName); 287 288 try { 289 this.install(namespace, 290 deploymentName, 291 serviceName, 292 labels, 293 serviceAccountName, 294 imageName, 295 imagePullPolicy, 296 hostNetwork, 297 tls, 298 verifyTls, 299 tlsKeyUri, 300 tlsCertUri, 301 tlsCaCertUri); 302 } catch (final KubernetesClientException kubernetesClientException) { 303 final Status status = kubernetesClientException.getStatus(); 304 if (status == null || !"AlreadyExists".equals(status.getReason())) { 305 throw kubernetesClientException; 306 } else if (upgrade) { 307 this.upgrade(namespace, 308 deploymentName, 309 serviceName, 310 serviceAccountName, 311 imageName, 312 imagePullPolicy, 313 labels); 314 } 315 } 316 } 317 318 public void install() { 319 try { 320 this.install(null, null, null, null, null, null, null, false, false, false, null, null, null); 321 } catch (final IOException willNotHappen) { 322 throw new AssertionError(willNotHappen); 323 } 324 } 325 326 public void install(String namespace, 327 final String deploymentName, 328 final String serviceName, 329 Map<String, String> labels, 330 final String serviceAccountName, 331 final String imageName, 332 final ImagePullPolicy imagePullPolicy, 333 final boolean hostNetwork, 334 final boolean tls, 335 final boolean verifyTls, 336 final URI tlsKeyUri, 337 final URI tlsCertUri, 338 final URI tlsCaCertUri) 339 throws IOException { 340 namespace = normalizeNamespace(namespace); 341 labels = normalizeLabels(labels); 342 final Deployment deployment = 343 this.createDeployment(namespace, 344 normalizeDeploymentName(deploymentName), 345 labels, 346 normalizeServiceAccountName(serviceAccountName), 347 normalizeImageName(imageName), 348 imagePullPolicy, 349 hostNetwork, 350 tls, 351 verifyTls); 352 353 this.kubernetesClient.extensions().deployments().inNamespace(namespace).create(deployment); 354 355 final Service service = this.createService(namespace, normalizeServiceName(serviceName), labels); 356 this.kubernetesClient.services().inNamespace(namespace).create(service); 357 358 if (tls) { 359 final Secret secret = 360 this.createSecret(namespace, 361 tlsKeyUri, 362 tlsCertUri, 363 tlsCaCertUri, 364 labels); 365 this.kubernetesClient.secrets().inNamespace(namespace).create(secret); 366 } 367 368 } 369 370 public void upgrade() { 371 this.upgrade(null, null, null, null, null, null, null); 372 } 373 374 public void upgrade(String namespace, 375 final String deploymentName, 376 String serviceName, 377 final String serviceAccountName, 378 final String imageName, 379 final ImagePullPolicy imagePullPolicy, 380 final Map<String, String> labels) { 381 namespace = normalizeNamespace(namespace); 382 serviceName = normalizeServiceName(serviceName); 383 384 this.kubernetesClient.extensions() 385 .deployments() 386 .inNamespace(namespace) 387 .withName(normalizeDeploymentName(deploymentName)) 388 .edit() 389 .editSpec() 390 .editTemplate() 391 .editSpec() 392 .editContainer(0) 393 .withImage(normalizeImageName(imageName)) 394 .withImagePullPolicy(normalizeImagePullPolicy(imagePullPolicy)) 395 .and() 396 .withServiceAccountName(normalizeServiceAccountName(serviceAccountName)) 397 .endSpec() 398 .endTemplate() 399 .endSpec() 400 .done(); 401 402 // TODO: this way of emulating install.go's check to see if the 403 // Service exists...not sure it's right 404 final Service service = this.kubernetesClient.services() 405 .inNamespace(namespace) 406 .withName(serviceName) 407 .get(); 408 if (service == null) { 409 this.createService(namespace, serviceName, normalizeLabels(labels)); 410 } 411 412 } 413 414 protected Service createService(final String namespace, 415 final String serviceName, 416 Map<String, String> labels) { 417 labels = normalizeLabels(labels); 418 419 final Service service = new Service(); 420 421 final ObjectMeta metadata = new ObjectMeta(); 422 metadata.setNamespace(normalizeNamespace(namespace)); 423 metadata.setName(normalizeServiceName(serviceName)); 424 metadata.setLabels(labels); 425 426 service.setMetadata(metadata); 427 service.setSpec(this.createServiceSpec(labels)); 428 429 return service; 430 } 431 432 protected Deployment createDeployment(String namespace, 433 final String deploymentName, 434 Map<String, String> labels, 435 final String serviceAccountName, 436 final String imageName, 437 final ImagePullPolicy imagePullPolicy, 438 final boolean hostNetwork, 439 final boolean tls, 440 final boolean verifyTls) { 441 namespace = normalizeNamespace(namespace); 442 labels = normalizeLabels(labels); 443 444 final Deployment deployment = new Deployment(); 445 446 final ObjectMeta metadata = new ObjectMeta(); 447 metadata.setNamespace(namespace); 448 metadata.setName(normalizeDeploymentName(deploymentName)); 449 metadata.setLabels(labels); 450 deployment.setMetadata(metadata); 451 452 deployment.setSpec(this.createDeploymentSpec(labels, serviceAccountName, imageName, imagePullPolicy, namespace, hostNetwork, tls, verifyTls)); 453 return deployment; 454 } 455 456 protected Secret createSecret(final String namespace, 457 final URI tlsKeyUri, 458 final URI tlsCertUri, 459 final URI tlsCaCertUri, 460 final Map<String, String> labels) 461 throws IOException { 462 463 final Secret secret = new Secret(); 464 secret.setType("Opaque"); 465 466 final Map<String, String> secretData = new HashMap<>(); 467 468 try (final InputStream tlsKeyStream = read(tlsKeyUri)) { 469 if (tlsKeyStream != null) { 470 secretData.put("tls.key", Base64.getEncoder().encodeToString(toByteArray(tlsKeyStream))); 471 } 472 } 473 474 try (final InputStream tlsCertStream = read(tlsCertUri)) { 475 if (tlsCertStream != null) { 476 secretData.put("tls.crt", Base64.getEncoder().encodeToString(toByteArray(tlsCertStream))); 477 } 478 } 479 480 try (final InputStream tlsCaCertStream = read(tlsCaCertUri)) { 481 if (tlsCaCertStream != null) { 482 secretData.put("ca.crt", Base64.getEncoder().encodeToString(toByteArray(tlsCaCertStream))); 483 } 484 } 485 486 secret.setData(secretData); 487 488 final ObjectMeta metadata = new ObjectMeta(); 489 metadata.setNamespace(normalizeNamespace(namespace)); 490 metadata.setName(SECRET_NAME); 491 metadata.setLabels(normalizeLabels(labels)); 492 secret.setMetadata(metadata); 493 494 return secret; 495 } 496 497 protected DeploymentSpec createDeploymentSpec(final Map<String, String> labels, 498 final String serviceAccountName, 499 final String imageName, 500 final ImagePullPolicy imagePullPolicy, 501 final String namespace, 502 final boolean hostNetwork, 503 final boolean tls, 504 final boolean verifyTls) { 505 final DeploymentSpec deploymentSpec = new DeploymentSpec(); 506 final PodTemplateSpec podTemplateSpec = new PodTemplateSpec(); 507 final ObjectMeta metadata = new ObjectMeta(); 508 metadata.setLabels(normalizeLabels(labels)); 509 podTemplateSpec.setMetadata(metadata); 510 final PodSpec podSpec = new PodSpec(); 511 podSpec.setServiceAccountName(normalizeServiceAccountName(serviceAccountName)); 512 podSpec.setContainers(Arrays.asList(this.createContainer(imageName, imagePullPolicy, namespace, tls, verifyTls))); 513 podSpec.setHostNetwork(Boolean.valueOf(hostNetwork)); 514 final Map<String, String> nodeSelector = new HashMap<>(); 515 nodeSelector.put("beta.kubernetes.io/os", "linux"); 516 podSpec.setNodeSelector(nodeSelector); 517 if (tls) { 518 final Volume volume = new Volume(); 519 volume.setName(DEFAULT_NAME + "-certs"); 520 final SecretVolumeSource secretVolumeSource = new SecretVolumeSource(); 521 secretVolumeSource.setSecretName(SECRET_NAME); 522 volume.setSecret(secretVolumeSource); 523 podSpec.setVolumes(Arrays.asList(volume)); 524 } 525 podTemplateSpec.setSpec(podSpec); 526 deploymentSpec.setTemplate(podTemplateSpec); 527 return deploymentSpec; 528 } 529 530 protected Container createContainer(final String imageName, 531 final ImagePullPolicy imagePullPolicy, 532 final String namespace, 533 final boolean tls, 534 final boolean verifyTls) { 535 final Container container = new Container(); 536 container.setName(DEFAULT_NAME); 537 container.setImage(normalizeImageName(imageName)); 538 container.setImagePullPolicy(normalizeImagePullPolicy(imagePullPolicy)); 539 540 final ContainerPort containerPort = new ContainerPort(); 541 containerPort.setContainerPort(Integer.valueOf(44134)); 542 containerPort.setName(DEFAULT_NAME); 543 container.setPorts(Arrays.asList(containerPort)); 544 545 final List<EnvVar> env = new ArrayList<>(); 546 547 final EnvVar tillerNamespace = new EnvVar(); 548 tillerNamespace.setName("TILLER_NAMESPACE"); 549 tillerNamespace.setValue(normalizeNamespace(namespace)); 550 env.add(tillerNamespace); 551 552 if (tls) { 553 final EnvVar tlsVerify = new EnvVar(); 554 tlsVerify.setName("TILLER_TLS_VERIFY"); 555 tlsVerify.setValue(verifyTls ? "1" : ""); 556 env.add(tlsVerify); 557 558 final EnvVar tlsEnable = new EnvVar(); 559 tlsEnable.setName("TILLER_TLS_ENABLE"); 560 tlsEnable.setValue("1"); 561 env.add(tlsEnable); 562 563 final EnvVar tlsCerts = new EnvVar(); 564 tlsCerts.setName("TILLER_TLS_CERTS"); 565 tlsCerts.setValue(TILLER_TLS_CERTS_PATH); 566 env.add(tlsCerts); 567 } 568 569 container.setEnv(env); 570 571 572 final IntOrString port44135 = new IntOrString(Integer.valueOf(44135)); 573 574 final HTTPGetAction livenessHttpGetAction = new HTTPGetAction(); 575 livenessHttpGetAction.setPath("/liveness"); 576 livenessHttpGetAction.setPort(port44135); 577 final Probe livenessProbe = new Probe(); 578 livenessProbe.setHttpGet(livenessHttpGetAction); 579 livenessProbe.setInitialDelaySeconds(ONE); 580 livenessProbe.setTimeoutSeconds(ONE); 581 container.setLivenessProbe(livenessProbe); 582 583 final HTTPGetAction readinessHttpGetAction = new HTTPGetAction(); 584 readinessHttpGetAction.setPath("/readiness"); 585 readinessHttpGetAction.setPort(port44135); 586 final Probe readinessProbe = new Probe(); 587 readinessProbe.setHttpGet(readinessHttpGetAction); 588 readinessProbe.setInitialDelaySeconds(ONE); 589 readinessProbe.setTimeoutSeconds(ONE); 590 container.setReadinessProbe(readinessProbe); 591 592 if (tls) { 593 final VolumeMount volumeMount = new VolumeMount(); 594 volumeMount.setName(DEFAULT_NAME + "-certs"); 595 volumeMount.setReadOnly(true); 596 volumeMount.setMountPath(TILLER_TLS_CERTS_PATH); 597 container.setVolumeMounts(Arrays.asList(volumeMount)); 598 } 599 600 return container; 601 } 602 603 protected ServiceSpec createServiceSpec(final Map<String, String> labels) { 604 final ServiceSpec serviceSpec = new ServiceSpec(); 605 serviceSpec.setType("ClusterIP"); 606 607 final ServicePort servicePort = new ServicePort(); 608 servicePort.setName(DEFAULT_NAME); 609 servicePort.setPort(Integer.valueOf(44134)); 610 servicePort.setTargetPort(new IntOrString(DEFAULT_NAME)); 611 serviceSpec.setPorts(Arrays.asList(servicePort)); 612 613 serviceSpec.setSelector(normalizeLabels(labels)); 614 return serviceSpec; 615 } 616 617 protected final String normalizeNamespace(String namespace) { 618 if (namespace == null || namespace.isEmpty()) { 619 namespace = this.tillerNamespace; 620 if (namespace == null || namespace.isEmpty()) { 621 namespace = DEFAULT_NAMESPACE; 622 } 623 } 624 return namespace; 625 } 626 627 628 /* 629 * Static methods. 630 */ 631 632 633 protected static final Map<String, String> normalizeLabels(Map<String, String> labels) { 634 if (labels == null) { 635 labels = new HashMap<>(7); 636 } 637 if (!labels.containsKey("app")) { 638 labels.put("app", "helm"); 639 } 640 if (!labels.containsKey("name")) { 641 labels.put("name", DEFAULT_NAME); 642 } 643 return labels; 644 } 645 646 protected static final String normalizeDeploymentName(final String deploymentName) { 647 if (deploymentName == null || deploymentName.isEmpty()) { 648 return DEFAULT_DEPLOYMENT_NAME; 649 } else { 650 return deploymentName; 651 } 652 } 653 654 protected static final String normalizeImageName(final String imageName) { 655 if (imageName == null || imageName.isEmpty()) { 656 return DEFAULT_IMAGE_NAME; 657 } else { 658 return imageName; 659 } 660 } 661 662 private static final String normalizeImagePullPolicy(ImagePullPolicy imagePullPolicy) { 663 if (imagePullPolicy == null) { 664 imagePullPolicy = DEFAULT_IMAGE_PULL_POLICY; 665 } 666 assert imagePullPolicy != null; 667 return imagePullPolicy.toString(); 668 } 669 670 protected static final String normalizeServiceAccountName(final String serviceAccountName) { 671 return serviceAccountName == null ? "" : serviceAccountName; 672 } 673 674 protected static final String normalizeServiceName(final String serviceName) { 675 if (serviceName == null || serviceName.isEmpty()) { 676 return DEFAULT_DEPLOYMENT_NAME; // yes, DEFAULT_*DEPLOYMENT*_NAME 677 } else { 678 return serviceName; 679 } 680 } 681 682 private static final InputStream read(final URI uri) throws IOException { 683 final InputStream returnValue; 684 if (uri == null) { 685 returnValue = null; 686 } else { 687 final URL url = uri.toURL(); 688 assert url != null; 689 final InputStream uriStream = url.openStream(); 690 if (uriStream == null) { 691 returnValue = null; 692 } else if (uriStream instanceof BufferedInputStream) { 693 returnValue = (BufferedInputStream)uriStream; 694 } else { 695 returnValue = new BufferedInputStream(uriStream); 696 } 697 } 698 return returnValue; 699 } 700 701 private static final byte[] toByteArray(final InputStream inputStream) throws IOException { 702 // Interesting historical anecdotes at https://stackoverflow.com/a/1264737/208288. 703 byte[] returnValue = null; 704 if (inputStream != null) { 705 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 706 returnValue = new byte[4096]; // arbitrary size 707 int bytesRead; 708 while ((bytesRead = inputStream.read(returnValue, 0, returnValue.length)) != -1) { 709 buffer.write(returnValue, 0, bytesRead); 710 } 711 buffer.flush(); 712 returnValue = buffer.toByteArray(); 713 } 714 return returnValue; 715 } 716 717 718 /* 719 * Inner and nested classes. 720 */ 721 722 723 /** 724 * An {@code enum} representing valid values for a Kubernetes {@code 725 * imagePullPolicy} field. 726 * 727 * @author <a href="https://about.me/lairdnelson" 728 * target="_parent">Laird Nelson</a> 729 */ 730 public static enum ImagePullPolicy { 731 732 733 /** 734 * An {@link ImagePullPolicy} indicating that a Docker image 735 * should always be pulled. 736 */ 737 ALWAYS("Always"), 738 739 /** 740 * An {@link ImagePullPolicy} indicating that a Docker image 741 * should be pulled only if it is not already cached locally. 742 */ 743 IF_NOT_PRESENT("IfNotPresent"), 744 745 /** 746 * An {@link ImagePullPolicy} indicating that a Docker image 747 * should never be pulled. 748 */ 749 NEVER("Never"); 750 751 /** 752 * The actual valid Kubernetes value for this {@link 753 * ImagePullPolicy}. 754 * 755 * <p>This field is never {@code null}.</p> 756 */ 757 private final String value; 758 759 760 /* 761 * Constructors. 762 */ 763 764 765 /** 766 * Creates a new {@link ImagePullPolicy}. 767 * 768 * @param value the valid Kubernetes value for this {@link 769 * ImagePullPolicy}; must not be {@code null} 770 * 771 * @exception NullPointerException if {@code value} is {@code 772 * null} 773 */ 774 ImagePullPolicy(final String value) { 775 Objects.requireNonNull(value); 776 this.value = value; 777 } 778 779 780 /* 781 * Instance methods. 782 */ 783 784 785 /** 786 * Returns the valid Kubernetes value for this {@link 787 * ImagePullPolicy}. 788 * 789 * <p>This method never returns {@code null}.</p> 790 * 791 * @return the valid Kubernetes value for this {@link 792 * ImagePullPolicy}; never {@code null} 793 */ 794 @Override 795 public final String toString() { 796 return this.value; 797 } 798 } 799 800}