diff --git a/karaf/features/pom.xml b/karaf/features/pom.xml
index 34da460647..13ed9008ef 100755
--- a/karaf/features/pom.xml
+++ b/karaf/features/pom.xml
@@ -19,101 +19,102 @@
-->
-
- 4.0.0
-
- org.apache.brooklyn
- brooklyn-karaf
- 1.1.0-SNAPSHOT
-
- brooklyn-features
- Brooklyn Karaf Features
- pom
+ 4.0.0
+
+ org.apache.brooklyn
+ brooklyn-karaf
+ 1.1.0-SNAPSHOT
+
-
-
-
- src/main/feature
- true
-
- **/*
-
-
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
- 2.6
-
- false
-
- ${*}
-
-
-
-
- filter
- generate-resources
-
- resources
-
-
-
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
-
-
- package
-
- attach-artifact
-
-
-
-
- target/classes/feature.xml
- features
- xml
-
-
-
-
-
-
-
-
-
-
-
- org.eclipse.m2e
- lifecycle-mapping
- 1.0.0
-
-
-
-
-
- org.apache.karaf.tooling
- karaf-maven-plugin
- [4.1.6,)
-
- verify
-
-
-
-
-
-
-
-
-
-
-
-
-
+ brooklyn-features
+ Brooklyn Karaf Features
+ pom
+
+
+
+
+ src/main/feature
+ true
+
+ **/*
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.6
+
+ false
+
+ ${*}
+
+
+
+
+ filter
+ generate-resources
+
+ resources
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.1.0
+
+
+ package
+
+ attach-artifact
+
+
+
+
+ target/classes/feature.xml
+ features
+ xml
+
+
+
+
+
+
+
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ org.apache.karaf.tooling
+ karaf-maven-plugin
+ [4.1.6,)
+
+ verify
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/karaf/features/src/main/feature/feature.xml b/karaf/features/src/main/feature/feature.xml
index 8ce7ecf343..dd71f9fdeb 100644
--- a/karaf/features/src/main/feature/feature.xml
+++ b/karaf/features/src/main/feature/feature.xml
@@ -202,7 +202,7 @@
brooklyn-camp-brooklyn
brooklyn-camp-base
- cxf-jaxrs-with-optional-dependencies
+ cxf-jaxrs
mvn:org.apache.cxf/cxf-rt-rs-security-cors/${cxf.version}
mvn:org.apache.cxf/cxf-rt-frontend-jaxrs/${cxf.version}
mvn:org.apache.cxf.karaf/cxf-karaf-commands/${cxf.version}
@@ -218,14 +218,6 @@
-
- cxf-jaxrs
-
-
- mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/${fasterxml.jackson.version}
-
-
-
brooklyn-rest-resources-prereqs
mvn:org.apache.brooklyn/brooklyn-rest-resources/${project.version}
@@ -301,6 +293,8 @@
brooklyn-core
+ brooklyn-utils-common
+ brooklyn-locations-jclouds
mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/${fasterxml.jackson.version}
mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/${fasterxml.jackson.version}
@@ -315,6 +309,7 @@
wrap:mvn:com.squareup.okhttp3/okhttp/3.12.6$Bundle-SymbolicName=squareup-okhttp3&Bundle-Version=3.12.6&Import-Package=okio;version=1.15,*;resolution:=optional
wrap:mvn:com.squareup.okhttp3/logging-interceptor/3.12.6$Bundle-SymbolicName=squareup-okhttp3-logging-interceptor&Bundle-Version=3.12.6&Import-Package=*;resolution:=mandatory
+
mvn:io.fabric8/zjsonpatch/0.3.0
@@ -322,9 +317,48 @@
mvn:io.fabric8/kubernetes-client/${kubernetes-client.version}/jar/bundle
mvn:io.fabric8/openshift-client/${kubernetes-client.version}/jar/bundle
+ mvn:org.microbean/microbean-helm/2.12.4-SNAPSHOT
+
+ wrap:mvn:org.microbean/microbean-development-annotations/0.1.3$Bundle-SymbolicName=microbean-development-annotations&Bundle-Version=0.1.3&Import-Package=*;resolution:=mandatory
+ wrap:mvn:org.kamranzafar/jtar/2.3$Bundle-SymbolicName=jtar&Bundle-Version=2.3&Import-Package=*;resolution:=mandatory
+ wrap:mvn:com.github.zafarkhaja/java-semver/0.9.0$Bundle-SymbolicName=java-semver&Bundle-Version=0.9.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:org.microbean/microbean-kubernetes/0.1.3$Bundle-SymbolicName=microbean-kubernetes&Bundle-Version=0.1.3&Import-Package=*;resolution:=mandatory
+
+ wrap:mvn:io.grpc/grpc-netty/1.23.0$Bundle-SymbolicName=grpc-netty&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ mvn:io.netty/netty-codec-http2/4.1.38.Final
+ mvn:io.netty/netty-resolver/4.1.38.Final
+ mvn:io.netty/netty-common/4.1.38.Final
+ mvn:io.netty/netty-buffer/4.1.38.Final
+ mvn:io.netty/netty-codec/4.1.38.Final
+ mvn:io.netty/netty-transport/4.1.38.Final
+ mvn:io.netty/netty-codec-http/4.1.38.Final
+ mvn:io.netty/netty-codec-socks/4.1.38.Final
+ mvn:io.netty/netty-handler/4.1.38.Final
+ mvn:io.netty/netty-handler-proxy/4.1.38.Final
+ wrap:mvn:io.grpc/grpc-core/1.23.0$Bundle-SymbolicName=grpc-core&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.grpc/grpc-context/1.23.0$Bundle-SymbolicName=grpc-context&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.opencensus/opencensus-api/0.11.0$Bundle-SymbolicName=opencensus-api&Bundle-Version=0.11.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.opencensus/opencensus-contrib-grpc-metrics/0.11.0$Bundle-SymbolicName=opencensus-contrib-grpc-metrics&Bundle-Version=0.11.0&Import-Package=*;resolution:=mandatory
+
+ mvn:com.google.protobuf/protobuf-java/3.9.0
+ wrap:mvn:io.grpc/grpc-protobuf/1.23.0$Bundle-SymbolicName=grpc-protobuf&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.grpc/grpc-services/1.23.0$Bundle-SymbolicName=grpc-services&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.grpc/grpc-stub/1.23.0$Bundle-SymbolicName=grpc-stub&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.grpc/grpc-protobuf-lite/1.23.0$Bundle-SymbolicName=grpc-protobuf-lite&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ mvn:com.google.protobuf/protobuf-java-util/3.9.0
+ wrap:mvn:com.google.api.grpc/proto-google-common-protos/1.12.0$Bundle-SymbolicName=proto-google-common-protos&Bundle-Version=1.12.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:io.grpc/grpc-core/1.23.0$Bundle-SymbolicName=grpc-core&Bundle-Version=1.23.0&Import-Package=*;resolution:=mandatory
+ wrap:mvn:com.google.instrumentation/instrumentation-api/0.4.3$Bundle-SymbolicName=instrumentation-api&Bundle-Version=0.4.3&Import-Package=*;resolution:=mandatory
+
+ wrap:mvn:com.google.errorprone/error_prone_annotations/2.3.2$Bundle-SymbolicName=error_prone_annotations&Bundle-Version=2.3.2&Import-Package=*;resolution:=optional
+ wrap:mvn:io.perfmark/perfmark-api/0.17.0$Bundle-SymbolicName=perfmark-api&Bundle-Version=0.17.0&Import-Package=*;resolution:=optional
+
+ mvn:io.netty/netty-tcnative-boringssl-static/2.0.30.Final
+
mvn:org.apache.brooklyn/brooklyn-locations-container/${project.version}
+
mvn:org.apache.brooklyn/brooklyn-test-framework/${project.version}
diff --git a/locations/container/README.md b/locations/container/README.md
new file mode 100644
index 0000000000..5c62b017ae
--- /dev/null
+++ b/locations/container/README.md
@@ -0,0 +1,30 @@
+
+# Kubernetes Location
+
+Brooklyn Container Location has an extensive support for Kubernetes deployments
+In particular, it supports
+
+- KubernetesResource
+- KubernetesHelmChart
+- KubernetesContainer
+
+## Kubernets Helm Chart
+
+Here's an example of an Helm based blueprint
+
+```YAML
+location:
+ kubernetes:
+ endpoint: https://localhost:6443
+ kubeconfig: /home/user/.kube/config
+services:
+- type: org.apache.brooklyn.container.entity.kubernetes.KubernetesHelmChart
+ name: jenkins-helm
+ chartName: jenkins
+```
+
+Notice, in this case, it is pointing at a local k8s cluster (created using Docker on Mac) and specify a `kubeconfig`
+file for connection details.
+
+The `KubernetesHelmChart` entity will install the latest version of the `chart` named `jenkins` from the Chart repository `stable` at `https://kubernetes-charts.storage.googleapis.com/`
+You can install a specific version of the chart by using `chartVersion` config key.
\ No newline at end of file
diff --git a/locations/container/pom.xml b/locations/container/pom.xml
index 2a4021b7c5..85e9c1f37e 100644
--- a/locations/container/pom.xml
+++ b/locations/container/pom.xml
@@ -32,6 +32,31 @@
+
+ org.microbean
+ microbean-helm
+ 2.12.4-SNAPSHOT
+
+
+ org.yaml
+ snakeyaml
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+ io.netty
+ netty-tcnative-boringssl-static
+
+
+
+
+
+ io.netty
+ netty-tcnative-boringssl-static
+ 2.0.30.Final
+
io.fabric8
openshift-client
@@ -150,4 +175,5 @@
test
+
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 0000000000..55362074c8
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesHelmChart.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+@ImplementedBy(KubernetesHelmChartImpl.class)
+public interface KubernetesHelmChart extends SoftwareProcess {
+
+ ConfigKey CHART_NAME = ConfigKeys.builder(String.class)
+ .name("chartName")
+ .description("Helm Chart name")
+ .build();
+
+ ConfigKey CHART_VERSION = ConfigKeys.builder(String.class)
+ .name("chartVersion")
+ .description("Helm Chart version")
+ .build();
+}
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 0000000000..04bd3ce974
--- /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);
+ }
+
+}
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 d6023a0235..d0147b1beb 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,16 +18,28 @@
*/
package org.apache.brooklyn.container.location.kubernetes;
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.nio.charset.Charset;
-import java.util.*;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nullable;
-
+import com.google.common.base.Optional;
+import com.google.common.base.*;
+import com.google.common.collect.*;
+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 org.microbean.helm.ReleaseManager;
+import org.microbean.helm.Tiller;
+import org.microbean.helm.TillerInstaller;
+import org.microbean.helm.chart.repository.ChartRepository;
import io.fabric8.kubernetes.api.model.*;
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
+import io.fabric8.kubernetes.api.model.apps.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;
@@ -37,12 +49,14 @@
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;
@@ -72,28 +86,19 @@
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.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-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 io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
-import io.fabric8.kubernetes.api.model.apps.DeploymentStatus;
-import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.KubernetesClientException;
+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.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
public class KubernetesLocation extends AbstractLocation implements MachineProvisioningLocation, KubernetesLocationConfig {
@@ -131,6 +136,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 Tiller tiller;
public static final String ADDRESS_KEY = "address";
private ConfigBag currentConfig;
@@ -171,6 +177,8 @@ public KubernetesMachineLocation obtain(Map, ?> flags) {
Entity entity = validateCallerContext(setup);
if (isKubernetesResource(entity)) {
return createKubernetesResourceLocation(entity, setup);
+ } else if (isKubernetesHelmChart(entity)) {
+ return createKubernetesHelmChartLocation(entity, setup);
} else {
return createKubernetesContainerLocation(entity, setup);
}
@@ -181,6 +189,8 @@ public void release(KubernetesMachineLocation machine) {
Entity entity = validateCallerContext(machine);
if (isKubernetesResource(entity)) {
deleteKubernetesResourceLocation(entity);
+ } else if (isKubernetesHelmChart(entity)) {
+ deleteKubernetesHelmChartLocation(entity);
} else {
deleteKubernetesContainerLocation(entity, machine);
}
@@ -266,6 +276,22 @@ public String getFailureMessage() {
waitForExitCondition(exitCondition);
}
}
+ protected void deleteKubernetesHelmChartLocation(Entity entity) {
+ final String releaseName = entity.sensors().get(KubernetesResource.RESOURCE_NAME);
+ ReleaseManager chartManager = new ReleaseManager(tiller);
+ try {
+ Future uninstallReleaseResponseFuture = chartManager.uninstall(UninstallReleaseRequest.newBuilder()
+ .setTimeout(300L)
+ .setName(releaseName)
+ .setPurge(true)
+ .build());
+ UninstallReleaseResponse response = uninstallReleaseResponseFuture.get();
+ LOG.debug("Release {} uninstalled", response);
+ } catch (IOException | InterruptedException | ExecutionException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
protected synchronized void deleteEmptyNamespace(final String name) {
if (!name.equals("default") && isNamespaceEmpty(name)) {
@@ -321,7 +347,10 @@ public Boolean call() {
return false;
}
HasMetadata check = client.resource(result.get(0)).inNamespace(result.get(0).getMetadata().getNamespace()).get();
- return check != null;
+ if (result.size() > 1 || check != null || check.getMetadata() == null) {
+ return false;
+ }
+ return true;
}
@Override
@@ -374,7 +403,10 @@ protected boolean findResourceAddress(LocationSpec extends KubernetesMachineLo
ReplicationController replicationController = (ReplicationController) metadata;
labels = replicationController.getSpec().getTemplate().getMetadata().getLabels();
}
- Pod pod = resourceType.equals(KubernetesResource.POD) ? getPod(namespace, resourceName) : getPod(namespace, labels);
+ Pod pod;
+ try(KubernetesClient client = getClient()) {
+ pod = resourceType.equals(KubernetesResource.POD) ? getPod(namespace, resourceName, client) : getPod(namespace, labels, client);
+ }
entity.sensors().set(KubernetesPod.KUBERNETES_POD, pod.getMetadata().getName());
InetAddress node = Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
@@ -409,8 +441,8 @@ protected boolean findResourceAddress(LocationSpec extends KubernetesMachineLo
LOG.warn("Multiple pods referenced by service {} in namespace {}, using {}: {}",
resourceName, namespace, podName, Iterables.toString(podNames));
}
- try {
- Pod pod = getPod(namespace, podName);
+ try(KubernetesClient client = getClient()) {
+ Pod pod = getPod(namespace, podName, client);
entity.sensors().set(KubernetesPod.KUBERNETES_POD, podName);
InetAddress node = Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
@@ -436,38 +468,39 @@ protected KubernetesMachineLocation createKubernetesContainerLocation(Entity ent
Iterable inboundPorts = findInboundPorts(entity, setup);
Map env = findEnvironmentVariables(entity, setup, imageName);
Map metadata = findMetadata(entity, setup, deploymentName);
+ KubernetesSshMachineLocation machine;
+ try(KubernetesClient client = getClient()) {
+ if (volumes != null) {
+ createPersistentVolumes(volumes, client);
+ }
- if (volumes != null) {
- createPersistentVolumes(volumes);
- }
-
- Namespace namespace = createOrGetNamespace(lookup(NAMESPACE, entity, setup), setup.get(CREATE_NAMESPACE));
+ Namespace namespace = createOrGetNamespace(lookup(NAMESPACE, entity, setup), setup.get(CREATE_NAMESPACE));
- if (secrets != null) {
- createSecrets(namespace.getMetadata().getName(), secrets);
- }
+ if (secrets != null) {
+ createSecrets(namespace.getMetadata().getName(), secrets);
+ }
- Container container = buildContainer(namespace.getMetadata().getName(), metadata, deploymentName, imageName, inboundPorts, env, limits, privileged);
- deploy(namespace.getMetadata().getName(), entity, metadata, deploymentName, container, replicas, secrets);
- Service service = exposeService(namespace.getMetadata().getName(), metadata, deploymentName, inboundPorts);
- Pod pod = getPod(namespace.getMetadata().getName(), metadata);
+ Container container = buildContainer(namespace.getMetadata().getName(), metadata, deploymentName, imageName, inboundPorts, env, limits, privileged);
+ deploy(namespace.getMetadata().getName(), entity, metadata, deploymentName, container, replicas, secrets);
+ Service service = exposeService(namespace.getMetadata().getName(), metadata, deploymentName, inboundPorts);
+ Pod pod = getPod(namespace.getMetadata().getName(), metadata, client);
- entity.sensors().set(KubernetesPod.KUBERNETES_NAMESPACE, namespace.getMetadata().getName());
- entity.sensors().set(KubernetesPod.KUBERNETES_DEPLOYMENT, deploymentName);
- entity.sensors().set(KubernetesPod.KUBERNETES_POD, pod.getMetadata().getName());
- entity.sensors().set(KubernetesPod.KUBERNETES_SERVICE, service.getMetadata().getName());
+ entity.sensors().set(KubernetesPod.KUBERNETES_NAMESPACE, namespace.getMetadata().getName());
+ entity.sensors().set(KubernetesPod.KUBERNETES_DEPLOYMENT, deploymentName);
+ entity.sensors().set(KubernetesPod.KUBERNETES_POD, pod.getMetadata().getName());
+ entity.sensors().set(KubernetesPod.KUBERNETES_SERVICE, service.getMetadata().getName());
- LocationSpec 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());
+ LocationSpec 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());
- KubernetesSshMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec);
- registerPortMappings(machine, entity, service);
- if (!isDockerContainer(entity)) {
- waitForSshable(machine, Duration.FIVE_MINUTES);
+ machine = getManagementContext().getLocationManager().createLocation(locationSpec);
+ registerPortMappings(machine, entity, service);
+ if (!isDockerContainer(entity)) {
+ waitForSshable(machine, Duration.FIVE_MINUTES);
+ }
}
-
return machine;
}
@@ -475,6 +508,81 @@ protected String getContainerResourceType() {
return KubernetesResource.DEPLOYMENT;
}
+ protected KubernetesMachineLocation createKubernetesHelmChartLocation(Entity entity, ConfigBag setup) {
+ try (KubernetesClient client = getClient()){
+ Map podLabels = ImmutableMap.of("name", "tiller", "app", "helm");
+ if (!isTillerInstalled("kube-system", podLabels, client)) {
+ installTillerPodAndService("kube-system", "tiller-deploy", podLabels, client);
+ }
+ // Create ${HOME}/.helm/{cache/archive,repository/cache}
+ try {
+ Files.createDirectories(Paths.get(System.getProperty("user.home"),".helm", "cache", "archive"));
+ Files.createDirectories(Paths.get(System.getProperty("user.home"),".helm", "repository", "cache"));
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+
+ 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);
+ 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 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, client);
+ registerPortMappings(machine, entity, service);
+ return machine;
+
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private void installTillerPodAndService(String namespace, String serviceName, Map podLabels, KubernetesClient client) {
+ TillerInstaller installer = new TillerInstaller();
+ installer.init(true);
+ getPod(namespace, podLabels, client);
+ getService(namespace, serviceName, client);
+ }
+
+ private boolean isTillerInstalled(String namespace, Map podLabels, KubernetesClient client) {
+ return !Iterables.isEmpty(client.pods().inNamespace(namespace).withLabels(podLabels).list().getItems());
+ }
+
protected void waitForSshable(final SshMachineLocation machine, Duration timeout) {
Callable checker = () -> {
int exitstatus = machine.execScript(
@@ -502,12 +610,17 @@ protected void waitForSshable(final SshMachineLocation machine, Duration timeout
}
}
- 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 ports = service.getSpec().getPorts();
- String publicHostText = machine.getSshHostAndPort().getHostText();
- LOG.debug("Recording port-mappings for container {} of {}: {}", machine, this, ports);
+ 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) {
String protocol = port.getProtocol();
@@ -559,43 +672,45 @@ public String getFailureMessage() {
}
}
- protected Pod getPod(final String namespace, final String name) {
- try (KubernetesClient client = getClient()) {
- ExitCondition exitCondition = new ExitCondition() {
- @Override
- public Boolean call() {
- Pod result = client.pods().inNamespace(namespace).withName(name).get();
- return result != null && result.getStatus().getPodIP() != null;
- }
+ protected Pod getPod(final String namespace, final String name, KubernetesClient client) {
+ ExitCondition exitCondition = new ExitCondition() {
+ @Override
+ public Boolean call() {
+ Pod result = client.pods().inNamespace(namespace).withName(name).get();
+ return result != null && result.getStatus().getPodIP() != null;
+ }
- @Override
- public String getFailureMessage() {
- return "Cannot find pod with name: " + name;
- }
- };
- waitForExitCondition(exitCondition);
- return client.pods().inNamespace(namespace).withName(name).get();
- }
+ @Override
+ public String getFailureMessage() {
+ return "Cannot find pod with name: " + name;
+ }
+ };
+ waitForExitCondition(exitCondition);
+ return client.pods().inNamespace(namespace).withName(name).get();
}
- protected Pod getPod(final String namespace, final Map metadata) {
- try (KubernetesClient client = getClient()) {
- ExitCondition exitCondition = new ExitCondition() {
- @Override
- public Boolean call() {
- PodList result = client.pods().inNamespace(namespace).withLabels(metadata).list();
- return !result.getItems().isEmpty() && result.getItems().get(0).getStatus().getPodIP() != null;
- }
+ protected Pod getPod(final String namespace, final Map metadata, KubernetesClient client) {
+ ExitCondition exitCondition = new ExitCondition() {
+ @Override
+ public Boolean call() {
+ PodList result = client.pods().inNamespace(namespace).withLabels(metadata).list();
+ return result.getItems().size() >= 1 &&
+ Iterables.any(result.getItems().get(0).getStatus().getConditions(), new Predicate() {
+ @Override
+ public boolean apply(@Nullable PodCondition input) {
+ return input.getStatus().equals("True");
+ }
+ });
+ }
- @Override
- public String getFailureMessage() {
- return "Cannot find pod with metadata: " + Joiner.on(" ").withKeyValueSeparator("=").join(metadata);
- }
- };
- waitForExitCondition(exitCondition);
- PodList result = client.pods().inNamespace(namespace).withLabels(metadata).list();
- return result.getItems().get(0);
- }
+ @Override
+ public String getFailureMessage() {
+ return "Cannot find pod with metadata: " + Joiner.on(" ").withKeyValueSeparator("=").join(metadata);
+ }
+ };
+ waitForExitCondition(exitCondition);
+ PodList result = client.pods().inNamespace(namespace).withLabels(metadata).list();
+ return result.getItems().get(0);
}
protected void createSecrets(String namespace, Map secrets) {
@@ -765,7 +880,9 @@ public Boolean call() {
return false;
}
for (EndpointSubset subset : endpoints.getSubsets()) {
- if (!subset.getNotReadyAddresses().isEmpty()) {
+ if (!subset.getNotReadyAddresses().isEmpty() &&
+ subset.getAddresses().isEmpty() &&
+ subset.getNotReadyAddresses().size() > 0) {
return false;
}
}
@@ -808,7 +925,7 @@ protected LocationSpec prepareSshableLocationSpec(
return locationSpec;
}
- protected void createPersistentVolumes(List volumes) {
+ protected void createPersistentVolumes(List volumes, KubernetesClient client) {
for (final String persistentVolume : volumes) {
PersistentVolume volume = new PersistentVolumeBuilder()
.withNewMetadata()
@@ -821,24 +938,22 @@ protected void createPersistentVolumes(List volumes) {
.withNewHostPath().withPath("/tmp/pv-1").endHostPath() // TODO make it configurable
.endSpec()
.build();
- try (KubernetesClient client = getClient()) {
- client.persistentVolumes().create(volume);
- ExitCondition exitCondition = new ExitCondition() {
- @Override
- public Boolean call() {
- PersistentVolume pv = client.persistentVolumes().withName(persistentVolume).get();
- return pv != null && pv.getStatus() != null
- && pv.getStatus().getPhase().equals(PHASE_AVAILABLE);
- }
+ client.persistentVolumes().create(volume);
+ ExitCondition exitCondition = new ExitCondition() {
+ @Override
+ public Boolean call() {
+ PersistentVolume pv = client.persistentVolumes().withName(persistentVolume).get();
+ return pv != null && pv.getStatus() != null
+ && pv.getStatus().getPhase().equals(PHASE_AVAILABLE);
+ }
- @Override
- public String getFailureMessage() {
- PersistentVolume pv = client.persistentVolumes().withName(persistentVolume).get();
- return "PersistentVolume for " + persistentVolume + " " + (pv == null ? "absent" : "pv=" + pv);
- }
- };
- waitForExitCondition(exitCondition);
- }
+ @Override
+ public String getFailureMessage() {
+ PersistentVolume pv = client.persistentVolumes().withName(persistentVolume).get();
+ return "PersistentVolume for " + persistentVolume + " " + (pv == null ? "absent" : "pv=" + pv);
+ }
+ };
+ waitForExitCondition(exitCondition);
}
}
@@ -970,6 +1085,10 @@ protected boolean isKubernetesResource(Entity entity) {
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();
}
diff --git a/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java
index a66938d710..3df344f8da 100644
--- a/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java
@@ -107,7 +107,7 @@ protected boolean findResourceAddress(LocationSpec extends KubernetesMachineLo
if (resourceType.equals(OpenShiftResource.DEPLOYMENT_CONFIG)) {
DeploymentConfig deploymentConfig = (DeploymentConfig) metadata;
Map labels = deploymentConfig.getSpec().getTemplate().getMetadata().getLabels();
- Pod pod = getPod(namespace, labels);
+ Pod pod = getPod(namespace, labels, client);
entity.sensors().set(OpenShiftPod.KUBERNETES_POD, pod.getMetadata().getName());
InetAddress node = Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
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 0000000000..ce9f22e531
--- /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 {
+ 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);
+ }
+ }
+}
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 ed42bd2250..4df41b42be 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 machines;
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 a3030b1522..76bcd59695 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.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.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 void setUp() throws Exception {
"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 void testNginxService() throws Exception {
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 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() {
diff --git a/pom.xml b/pom.xml
index 761a03231d..50f75bb3e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,8 +104,8 @@
2.1.2
1.2.3
1.7.25
-
- 18.0
+
+ 19.0
- 2.10.1
+ 2.10.1
3.3.5
4.5.10
4.4.12
@@ -134,7 +134,7 @@
1.25
1.5.6
- 2.5
+ 2.7
3.0.1
1.61
0.2.0