Repository: brooklyn-server
Updated Branches:
  refs/heads/master 12e6b0045 -> 0debf391d


Add support for kubernetes helm

- bump microbean-helm version
- support default stable ChartRepository
- use auto-generated release name


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/6ddc595d
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/6ddc595d
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/6ddc595d

Branch: refs/heads/master
Commit: 6ddc595dd549e13343fc4896fc05224c420ed136
Parents: 12e6b00
Author: Andrea Turli <andrea.tu...@gmail.com>
Authored: Mon Aug 21 15:21:52 2017 +0200
Committer: andreaturli <andrea.tu...@gmail.com>
Committed: Mon Mar 12 11:06:27 2018 +0100

----------------------------------------------------------------------
 locations/container/pom.xml                     |  88 ++++++-
 .../entity/kubernetes/KubernetesHelmChart.java  |  37 +++
 .../kubernetes/KubernetesHelmChartImpl.java     |  35 +++
 .../location/kubernetes/KubernetesLocation.java | 258 ++++++++++++++-----
 .../container/supplier/TillerSupplier.java      |  43 ++++
 .../kubernetes/KubernetesLocationLiveTest.java  |   2 +
 .../KubernetesLocationYamlLiveTest.java         |  24 ++
 7 files changed, 417 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/pom.xml
----------------------------------------------------------------------
diff --git a/locations/container/pom.xml b/locations/container/pom.xml
index 6bd4be1..da1434b 100644
--- a/locations/container/pom.xml
+++ b/locations/container/pom.xml
@@ -32,11 +32,43 @@
     </parent>
 
     <properties>
-        <kubernetes-client.version>1.4.27</kubernetes-client.version>
+        <kubernetes-client.version>2.5.6</kubernetes-client.version>
     </properties>
 
     <dependencies>
         <dependency>
+            <groupId>org.microbean</groupId>
+            <artifactId>microbean-helm</artifactId>
+            <version>2.7.2.1.0.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.api.grpc</groupId>
+                    <artifactId>proto-google-common-protos</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.google.errorprone</groupId>
+                    <artifactId>error_prone_annotations</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>io.grpc</groupId>
+                    <artifactId>grpc-context</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.google.errorprone</groupId>
+            <artifactId>error_prone_annotations</artifactId>
+            <version>2.0.19</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-context</artifactId>
+            <version>1.4.0</version>
+        </dependency>
+
+        <dependency>
             <groupId>io.fabric8</groupId>
             <artifactId>kubernetes-client</artifactId>
             <version>${kubernetes-client.version}</version>
@@ -69,6 +101,10 @@
                     <groupId>javax.ws.rs</groupId>
                     <artifactId>jsr311-api</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>com.squareup.okio</groupId>
+                    <artifactId>okio</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
 
@@ -88,7 +124,20 @@
             <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-locations-jclouds</artifactId>
             <version>${brooklyn.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.squareup.okio</groupId>
+                    <artifactId>okio</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okio</groupId>
+            <artifactId>okio</artifactId>
+            <version>1.13.0</version>
         </dependency>
+
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
@@ -152,4 +201,41 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
+
+    <build>
+        <extensions>
+            <!-- Use os-maven-plugin to initialize the "os.detected" 
properties -->
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.5.0.Final</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <!-- Use Ant to configure the appropriate "tcnative.classifier" 
property -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>initialize</phase>
+                        <configuration>
+                            <exportAntProperties>true</exportAntProperties>
+                            <target>
+                                <condition property="tcnative.classifier"
+                                           
value="${os.detected.classifier}-fedora"
+                                           else="${os.detected.classifier}">
+                                    <isset 
property="os.detected.release.fedora"/>
+                                </condition>
+                            </target>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChart.java
----------------------------------------------------------------------
diff --git 
a/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChart.java
 
b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChart.java
new file mode 100644
index 0000000..e44a4bc
--- /dev/null
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChart.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.container.entity.kubernetes;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.util.core.ResourcePredicates;
+
+@ImplementedBy(KubernetesHelmChartImpl.class)
+public interface KubernetesHelmChart extends SoftwareProcess {
+
+   ConfigKey<String> CHART_NAME = ConfigKeys.builder(String.class)
+           .name("chartName")
+           .description("Helm Chart name")
+           .build();
+
+   ConfigKey<String> CHART_VERSION = ConfigKeys.builder(String.class)
+           .name("chartVersion")
+           .description("Helm Chart version")
+           .build();
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChartImpl.java
----------------------------------------------------------------------
diff --git 
a/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChartImpl.java
 
b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChartImpl.java
new file mode 100644
index 0000000..9d3484e
--- /dev/null
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChartImpl.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.container.entity.kubernetes;
+
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+
+public class KubernetesHelmChartImpl extends EmptySoftwareProcessImpl 
implements KubernetesHelmChart {
+
+   @Override
+   public void init() {
+      super.init();
+
+      config().set(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true);
+      config().set(PROVISIONING_PROPERTIES.subKey("useJcloudsSshInit"), false);
+      config().set(PROVISIONING_PROPERTIES.subKey("waitForSshable"), false);
+      
config().set(PROVISIONING_PROPERTIES.subKey("pollForFirstReachableAddress"), 
false);
+      config().set(EmptySoftwareProcessImpl.USE_SSH_MONITORING, false);
+   }
+   
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
----------------------------------------------------------------------
diff --git 
a/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
 
b/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
index 1c62b6b..0b6c2b5 100644
--- 
a/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
@@ -18,69 +18,13 @@
  */
 package org.apache.brooklyn.container.location.kubernetes;
 
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nullable;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.location.MachineLocation;
-import org.apache.brooklyn.api.location.MachineProvisioningLocation;
-import org.apache.brooklyn.api.location.NoMachinesAvailableException;
-import org.apache.brooklyn.api.location.PortRange;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.api.sensor.EnricherSpec;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.container.entity.docker.DockerContainer;
-import org.apache.brooklyn.container.entity.kubernetes.KubernetesPod;
-import org.apache.brooklyn.container.entity.kubernetes.KubernetesResource;
-import org.apache.brooklyn.container.location.docker.DockerJcloudsLocation;
-import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesEmptyMachineLocation;
-import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation;
-import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesSshMachineLocation;
-import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.location.AbstractLocation;
-import org.apache.brooklyn.core.location.LocationConfigKeys;
-import org.apache.brooklyn.core.location.PortRanges;
-import org.apache.brooklyn.core.location.access.PortForwardManager;
-import 
org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
-import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
-import org.apache.brooklyn.core.network.OnPublicNetworkEnricher;
-import org.apache.brooklyn.core.sensor.Sensors;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.ResourceUtils;
-import org.apache.brooklyn.util.core.config.ConfigBag;
-import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
-import org.apache.brooklyn.util.core.internal.ssh.SshTool;
-import org.apache.brooklyn.util.core.text.TemplateProcessor;
-import org.apache.brooklyn.util.exceptions.ReferenceWithError;
-import org.apache.brooklyn.util.net.Networking;
-import org.apache.brooklyn.util.repeat.Repeater;
-import org.apache.brooklyn.util.stream.Streams;
-import org.apache.brooklyn.util.text.Identifiers;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.common.base.Functions;
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Stopwatch;
+import com.google.common.base.Suppliers;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -91,7 +35,12 @@ import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.io.BaseEncoding;
 import com.google.common.net.HostAndPort;
-
+import hapi.chart.ChartOuterClass.Chart;
+import hapi.release.ReleaseOuterClass.Release;
+import hapi.services.tiller.Tiller.InstallReleaseRequest;
+import hapi.services.tiller.Tiller.InstallReleaseResponse;
+import hapi.services.tiller.Tiller.UninstallReleaseRequest;
+import hapi.services.tiller.Tiller.UninstallReleaseResponse;
 import io.fabric8.kubernetes.api.model.Container;
 import io.fabric8.kubernetes.api.model.ContainerBuilder;
 import io.fabric8.kubernetes.api.model.ContainerPort;
@@ -104,9 +53,11 @@ import io.fabric8.kubernetes.api.model.EnvVarBuilder;
 import io.fabric8.kubernetes.api.model.HasMetadata;
 import io.fabric8.kubernetes.api.model.Namespace;
 import io.fabric8.kubernetes.api.model.NamespaceBuilder;
+import io.fabric8.kubernetes.api.model.Node;
 import io.fabric8.kubernetes.api.model.PersistentVolume;
 import io.fabric8.kubernetes.api.model.PersistentVolumeBuilder;
 import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodCondition;
 import io.fabric8.kubernetes.api.model.PodList;
 import io.fabric8.kubernetes.api.model.PodTemplateSpec;
 import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
@@ -123,8 +74,75 @@ import io.fabric8.kubernetes.api.model.ServicePortBuilder;
 import io.fabric8.kubernetes.api.model.extensions.Deployment;
 import io.fabric8.kubernetes.api.model.extensions.DeploymentBuilder;
 import io.fabric8.kubernetes.api.model.extensions.DeploymentStatus;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
 import io.fabric8.kubernetes.client.KubernetesClient;
 import io.fabric8.kubernetes.client.KubernetesClientException;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.api.location.PortRange;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.container.entity.docker.DockerContainer;
+import org.apache.brooklyn.container.entity.kubernetes.KubernetesHelmChart;
+import org.apache.brooklyn.container.entity.kubernetes.KubernetesPod;
+import org.apache.brooklyn.container.entity.kubernetes.KubernetesResource;
+import org.apache.brooklyn.container.location.docker.DockerJcloudsLocation;
+import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesEmptyMachineLocation;
+import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation;
+import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesSshMachineLocation;
+import org.apache.brooklyn.container.supplier.TillerSupplier;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.PortRanges;
+import org.apache.brooklyn.core.location.access.PortForwardManager;
+import 
org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
+import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
+import org.apache.brooklyn.core.network.OnPublicNetworkEnricher;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
+import org.apache.brooklyn.util.core.internal.ssh.SshTool;
+import org.apache.brooklyn.util.core.text.TemplateProcessor;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.microbean.helm.ReleaseManager;
+import org.microbean.helm.Tiller;
+import org.microbean.helm.TillerInstaller;
+import org.microbean.helm.chart.repository.ChartRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.brooklyn.test.Asserts.assertNotNull;
 
 public class KubernetesLocation extends AbstractLocation implements 
MachineProvisioningLocation<KubernetesMachineLocation>, 
KubernetesLocationConfig {
 
@@ -163,6 +181,7 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
     public static final String BROOKLYN_ROOT_PASSWORD = 
"BROOKLYN_ROOT_PASSWORD";
     private static final Logger LOG = 
LoggerFactory.getLogger(KubernetesLocation.class);
     private KubernetesClient client;
+    private Tiller tiller;
 
     public KubernetesLocation() {
         super();
@@ -202,9 +221,12 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         ConfigBag setup = 
ResolvingConfigBag.newInstanceExtending(getManagementContext(), setupRaw);
 
         client = getClient(setup);
+
         Entity entity = validateCallerContext(setup);
         if (isKubernetesResource(entity)) {
             return createKubernetesResourceLocation(entity, setup);
+        } else if (isKubernetesHelmChart(entity)) {
+            return createKubernetesHelmChartLocation(entity, setup);
         } else {
             return createKubernetesContainerLocation(entity, setup);
         }
@@ -215,6 +237,8 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         Entity entity = validateCallerContext(machine);
         if (isKubernetesResource(entity)) {
             deleteKubernetesResourceLocation(entity);
+        } else if (isKubernetesHelmChart(entity)) {
+            deleteKubernetesHelmChartLocation(entity);
         } else {
             deleteKubernetesContainerLocation(entity, machine);
         }
@@ -284,6 +308,21 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         return false;
     }
 
+    protected void deleteKubernetesHelmChartLocation(Entity entity) {
+        final String releaseName = 
entity.sensors().get(KubernetesResource.RESOURCE_NAME);
+        ReleaseManager chartManager = new ReleaseManager(tiller);
+        try {
+            Future<UninstallReleaseResponse> response = 
chartManager.uninstall(UninstallReleaseRequest.newBuilder()
+                    .setTimeout(300L)
+                    .setName(releaseName)
+                    .setPurge(true)
+                    .build());
+            LOG.debug("Release {} uninstalled", response);
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
     protected void undeploy(final String namespace, final String deployment, 
final String pod) {
         
client.extensions().deployments().inNamespace(namespace).withName(deployment).delete();
         ExitCondition exitCondition = new ExitCondition() {
@@ -347,8 +386,8 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
                 if (result.isEmpty()) {
                     return false;
                 }
-                List<HasMetadata> check = 
client.resource(result.get(0)).inNamespace(result.get(0).getMetadata().getNamespace()).get();
-                if (result.size() > 1 || check.size() != 1 || 
check.get(0).getMetadata() == null) {
+                HasMetadata check = 
client.resource(result.get(0)).inNamespace(result.get(0).getMetadata().getNamespace()).get();
+                if (result.size() > 1 || check != null || check.getMetadata() 
== null) {
                     return false;
                 }
                 return true;
@@ -385,7 +424,7 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
 
         if (resourceType.equals(KubernetesResource.SERVICE) && machine 
instanceof KubernetesSshMachineLocation) {
             Service service = getService(namespace, resourceName);
-            registerPortMappings((KubernetesSshMachineLocation) machine, 
entity, service);
+            registerPortMappings(machine, entity, service);
         }
 
         return machine;
@@ -482,7 +521,7 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         entity.sensors().set(KubernetesPod.KUBERNETES_POD, 
pod.getMetadata().getName());
         entity.sensors().set(KubernetesPod.KUBERNETES_SERVICE, 
service.getMetadata().getName());
 
-        LocationSpec<KubernetesSshMachineLocation> locationSpec = 
prepareSshableLocationSpec(entity, setup, namespace, deploymentName, service, 
pod)
+        LocationSpec<KubernetesSshMachineLocation> locationSpec = 
prepareSshableLocationSpec(entity, setup, service, pod)
                 .configure(KubernetesMachineLocation.KUBERNETES_NAMESPACE, 
namespace.getMetadata().getName())
                 .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_NAME, 
deploymentName)
                 .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_TYPE, 
getContainerResourceType());
@@ -500,6 +539,72 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         return KubernetesResource.DEPLOYMENT;
     }
 
+    protected KubernetesMachineLocation 
createKubernetesHelmChartLocation(Entity entity, ConfigBag setup) {
+        Map<String, String> podLabels = ImmutableMap.of("name", "tiller", 
"app", "helm");
+        if (!isTillerInstalled("kube-system", podLabels)) {
+            installTillerPodAndService("kube-system", "tiller-deploy", 
podLabels);
+        }
+        tiller = Suppliers.memoize(new 
TillerSupplier((DefaultKubernetesClient) client)).get();
+        String chartName = entity.config().get(KubernetesHelmChart.CHART_NAME);
+        String chartVersion = 
entity.config().get(KubernetesHelmChart.CHART_VERSION);
+
+        ReleaseManager chartManager = new ReleaseManager(tiller);
+        try {
+            InstallReleaseRequest.Builder requestBuilder = 
InstallReleaseRequest.newBuilder();
+            requestBuilder.setTimeout(300L);
+            requestBuilder.setWait(true);
+
+            ChartRepository chartRepository = new ChartRepository("stable", 
new URI("https://kubernetes-charts.storage.googleapis.com/";));
+            if (chartVersion == null) {
+                ChartRepository.Index.Entry latest = 
chartRepository.getIndex().getEntries().get(chartName).first();
+                chartVersion = latest.getVersion();
+            }
+
+            Chart.Builder chartBuilder = chartRepository.resolve(chartName, 
chartVersion);
+            Future<InstallReleaseResponse> releaseFuture = 
chartManager.install(requestBuilder, chartBuilder);
+            Release release = releaseFuture.get().getRelease();
+
+            String resourceName = release.getName();
+            String namespace = release.getNamespace();
+            LOG.debug("Resource {} (from chart {}) deployed to {}", new 
Object[]{resourceName, chartName, namespace});
+
+            Node node = Iterables.getFirst(client.nodes().list().getItems(), 
null); // null should never happen here
+            String nodeAddress = 
node.getStatus().getAddresses().get(0).getAddress();
+            InetAddress inetAddress = 
Networking.getInetAddressWithFixedName(nodeAddress);
+
+            entity.sensors().set(KubernetesPod.KUBERNETES_NAMESPACE, 
namespace);
+            entity.sensors().set(KubernetesResource.RESOURCE_NAME, 
resourceName);
+
+            LocationSpec<? extends KubernetesMachineLocation> locationSpec = 
LocationSpec.create(KubernetesEmptyMachineLocation.class);
+            locationSpec.configure(CALLER_CONTEXT, setup.get(CALLER_CONTEXT))
+                    .configure(KubernetesMachineLocation.KUBERNETES_NAMESPACE, 
namespace)
+                    
.configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_NAME, resourceName)
+                    .configure("address", inetAddress)
+                    .configure(SshMachineLocation.PRIVATE_ADDRESSES, 
ImmutableSet.of(nodeAddress));
+
+            KubernetesMachineLocation machine = 
getManagementContext().getLocationManager().createLocation(locationSpec);
+
+            String serviceName = String.format("%s-%s", resourceName, 
chartName);
+            Service service = getService(namespace, serviceName);
+            registerPortMappings(machine, entity, service);
+            return machine;
+
+        } catch (Exception e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private void installTillerPodAndService(String namespace, String 
serviceName, Map<String, String> podLabels) {
+        TillerInstaller installer = new TillerInstaller();
+        installer.init(true);
+        getPod(namespace, podLabels);
+        getService(namespace, serviceName);
+    }
+
+    private boolean isTillerInstalled(String namespace, Map<String, String> 
podLabels) {
+        return 
!Iterables.isEmpty(client.pods().inNamespace(namespace).withLabels(podLabels).list().getItems());
+    }
+
     protected void waitForSshable(final SshMachineLocation machine, Duration 
timeout) {
         Callable<Boolean> checker = new Callable<Boolean>() {
             public Boolean call() {
@@ -530,11 +635,16 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         }
     }
 
-    protected void registerPortMappings(KubernetesSshMachineLocation machine, 
Entity entity, Service service) {
+    protected void registerPortMappings(KubernetesMachineLocation machine, 
Entity entity, Service service) {
         PortForwardManager portForwardManager = (PortForwardManager) 
getManagementContext().getLocationRegistry()
                 
.getLocationManaged(PortForwardManagerLocationResolver.PFM_GLOBAL_SPEC);
         List<ServicePort> ports = service.getSpec().getPorts();
-        String publicHostText = ((SshMachineLocation) 
machine).getSshHostAndPort().getHostText();
+        String publicHostText;
+        if (machine instanceof SshMachineLocation) {
+            publicHostText = ((SshMachineLocation) 
machine).getSshHostAndPort().getHostText();
+        } else {
+            publicHostText = 
Iterables.getFirst(machine.config().get(SshMachineLocation.PRIVATE_ADDRESSES), 
null);
+        }
         LOG.debug("Recording port-mappings for container {} of {}: {}", new 
Object[]{machine, this, ports});
 
         for (ServicePort port : ports) {
@@ -607,7 +717,13 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
             @Override
             public Boolean call() {
                 PodList result = 
client.pods().inNamespace(namespace).withLabels(metadata).list();
-                return result.getItems().size() >= 1 && 
result.getItems().get(0).getStatus().getPodIP() != null;
+                return result.getItems().size() >= 1 && 
+                       
Iterables.any(result.getItems().get(0).getStatus().getConditions(), new 
Predicate<PodCondition>() {
+                                   @Override
+                                   public boolean apply(@Nullable PodCondition 
input) {
+                                       return input.getStatus().equals("True");
+                                   }
+                               });
             }
 
             @Override
@@ -777,7 +893,7 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
                     return false;
                 }
                 for (EndpointSubset subset : endpoints.getSubsets()) {
-                    if (subset.getNotReadyAddresses().size() > 0) {
+                    if (subset.getAddresses().isEmpty() && 
subset.getNotReadyAddresses().size() > 0) {
                         return false;
                     }
                 }
@@ -795,7 +911,7 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         return 
client.services().inNamespace(namespace).withName(serviceName).get();
     }
 
-    protected LocationSpec<KubernetesSshMachineLocation> 
prepareSshableLocationSpec(Entity entity, ConfigBag setup, Namespace namespace, 
String deploymentName, Service service, Pod pod) {
+    protected LocationSpec<KubernetesSshMachineLocation> 
prepareSshableLocationSpec(Entity entity, ConfigBag setup, Service service, Pod 
pod) {
         InetAddress node = 
Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
         String podAddress = pod.getStatus().getPodIP();
         LocationSpec<KubernetesSshMachineLocation> locationSpec = 
LocationSpec.create(KubernetesSshMachineLocation.class)
@@ -994,6 +1110,10 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         return implementsInterface(entity, KubernetesResource.class);
     }
 
+    protected boolean isKubernetesHelmChart(Entity entity) {
+        return implementsInterface(entity, KubernetesHelmChart.class);
+    }
+
     public boolean implementsInterface(Entity entity, Class<?> type) {
         return 
Iterables.tryFind(Arrays.asList(entity.getClass().getInterfaces()), 
Predicates.assignableFrom(type)).isPresent();
     }
@@ -1048,7 +1168,7 @@ public class KubernetesLocation extends AbstractLocation 
implements MachineProvi
         }
     }
 
-    public static interface ExitCondition extends Callable<Boolean> {
-        public String getFailureMessage();
+    public interface ExitCondition extends Callable<Boolean> {
+        String getFailureMessage();
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/src/main/java/org/apache/brooklyn/container/supplier/TillerSupplier.java
----------------------------------------------------------------------
diff --git 
a/locations/container/src/main/java/org/apache/brooklyn/container/supplier/TillerSupplier.java
 
b/locations/container/src/main/java/org/apache/brooklyn/container/supplier/TillerSupplier.java
new file mode 100644
index 0000000..ce9f22e
--- /dev/null
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/supplier/TillerSupplier.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.container.supplier;
+
+import java.net.MalformedURLException;
+
+import org.microbean.helm.Tiller;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+
+public class TillerSupplier implements Supplier<Tiller> {
+   private final DefaultKubernetesClient client;
+
+   public TillerSupplier(DefaultKubernetesClient client) {
+      this.client = client;
+   }
+
+   @Override
+   public Tiller get() {
+      try {
+         return new Tiller(client);
+      } catch (MalformedURLException e) {
+         throw Throwables.propagate(e);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
index ed42bd2..4df41b4 100644
--- 
a/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
@@ -61,6 +61,8 @@ public class KubernetesLocationLiveTest extends 
BrooklynAppLiveTestSupport {
     public static final String KUBERNETES_ENDPOINT = 
System.getProperty("test.brooklyn-container-service.kubernetes.endpoint", "");
     public static final String IDENTITY = 
System.getProperty("test.brooklyn-container-service.kubernetes.identity", "");
     public static final String CREDENTIAL = 
System.getProperty("test.brooklyn-container-service.kubernetes.credential", "");
+    public static final String KUBECONFIG = 
System.getProperty("test.brooklyn-container-service.kubernetes.kubeconfig", "");
+
     private static final Logger LOG = 
LoggerFactory.getLogger(KubernetesLocationLiveTest.class);
     protected KubernetesLocation loc;
     protected List<KubernetesMachineLocation> machines;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6ddc595d/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
----------------------------------------------------------------------
diff --git 
a/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
index a3030b1..76bcd59 100644
--- 
a/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
@@ -24,6 +24,7 @@ import static com.google.common.base.Predicates.not;
 import static com.google.common.base.Predicates.notNull;
 import static 
org.apache.brooklyn.container.location.kubernetes.KubernetesLocationLiveTest.CREDENTIAL;
 import static 
org.apache.brooklyn.container.location.kubernetes.KubernetesLocationLiveTest.IDENTITY;
+import static 
org.apache.brooklyn.container.location.kubernetes.KubernetesLocationLiveTest.KUBECONFIG;
 import static 
org.apache.brooklyn.container.location.kubernetes.KubernetesLocationLiveTest.KUBERNETES_ENDPOINT;
 import static 
org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEquals;
 import static 
org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEqualsEventually;
@@ -43,6 +44,7 @@ import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.location.MachineProvisioningLocation;
 import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
 import org.apache.brooklyn.container.entity.docker.DockerContainer;
+import org.apache.brooklyn.container.entity.kubernetes.KubernetesHelmChart;
 import org.apache.brooklyn.container.entity.kubernetes.KubernetesPod;
 import org.apache.brooklyn.container.entity.kubernetes.KubernetesResource;
 import org.apache.brooklyn.core.entity.Attributes;
@@ -93,6 +95,7 @@ public class KubernetesLocationYamlLiveTest extends 
AbstractYamlTest {
                 "location:",
                 "  kubernetes:",
                 "    " + KubernetesLocationConfig.MASTER_URL.getName() + ": 
\"" + KUBERNETES_ENDPOINT + "\"",
+                "    " + (StringUtils.isBlank(KUBECONFIG) ? "" : "kubeconfig: 
" + KUBECONFIG),
                 "    " + (StringUtils.isBlank(IDENTITY) ? "" : "identity: " + 
IDENTITY),
                 "    " + (StringUtils.isBlank(CREDENTIAL) ? "" : "credential: 
" + CREDENTIAL));
     }
@@ -510,6 +513,27 @@ public class KubernetesLocationYamlLiveTest extends 
AbstractYamlTest {
         assertReachableEventually(HostAndPort.fromString(httpPublicPort));
     }
 
+    @Test(groups={"Live"})
+    public void testJenkinsHelmChart() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                locationYaml,
+                "services:",
+                "  - type: " + KubernetesHelmChart.class.getName(),
+                "    name: \"jenkins-helm\"",
+                "    chartName: jenkins");
+        Entity app = createStartWaitAndLogApplication(yaml);
+
+        Iterable<KubernetesHelmChart> resources = 
Entities.descendantsAndSelf(app, KubernetesHelmChart.class);
+        KubernetesHelmChart jenkisHelm = Iterables.find(resources, 
EntityPredicates.displayNameEqualTo("jenkins-helm"));
+
+        assertEntityHealthy(jenkisHelm);
+
+        Entities.dumpInfo(app);
+
+        Integer httpPort = assertAttributeEventuallyNonNull(jenkisHelm, 
Sensors.newIntegerSensor("kubernetes.http.port"));
+        assertEquals(httpPort, Integer.valueOf(8080));
+    }
+
     protected void assertReachableEventually(final HostAndPort hostAndPort) {
         succeedsEventually(new Runnable() {
             public void run() {

Reply via email to