You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by dr...@apache.org on 2017/06/29 15:36:27 UTC

[45/50] [abbrv] brooklyn-server git commit: Merge tag 'apache-brooklyn' into feature/container-service

Merge tag 'apache-brooklyn' into feature/container-service

* tag 'apache-brooklyn': (339 commits)
  Removed references to AMP and QA framework
  Change tabs to spaces in BOM
  Fix README documentation and typos in BOM file
  Removes Docs as they need to be re-writtern to fit into Brooklyn
  Updates LICENCE to Apache-2.0
  Fix openshift config so location config is used
  white_check_mark: Fix build and cleanup
  no_entry: Updates package signatures
  no_entry: Merge resources into new location
  no_entry: Move openshift classes to new location
  no_entry: Move kubernetes classes to new location
  no_entry: Move docker classes to new location
  Tidying up for open-sourcing of project
  Filter out empty KubernetesEmptyMachineLocation for KubernetesPod compatibility
  Update wordpress image tag to 4-apache
  Fix handling of location and Kubernetes live tests
  Organize imports
  Use KubernetesMachineLocation correctly in location specs
  Add test for OpenShiftResource deployment and update other OpenShift tests
  Handle multiple addresses for services and set resource type correctly
  ...


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

Branch: refs/heads/master
Commit: 92a65d4507ae6fa9a576ba03c89c694f6c8e21b7
Parents: 6bdad92 975807b
Author: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Authored: Mon Jun 12 14:58:16 2017 -0400
Committer: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Committed: Mon Jun 12 14:58:16 2017 -0400

----------------------------------------------------------------------
 locations/container/pom.xml                     |  153 +++
 .../entity/docker/DockerContainer.java          |   91 ++
 .../entity/docker/DockerContainerImpl.java      |   88 ++
 .../entity/kubernetes/KubernetesPod.java        |  101 ++
 .../entity/kubernetes/KubernetesPodImpl.java    |   25 +
 .../entity/kubernetes/KubernetesResource.java   |   58 +
 .../kubernetes/KubernetesResourceImpl.java      |   36 +
 .../entity/openshift/OpenShiftPod.java          |   27 +
 .../entity/openshift/OpenShiftPodImpl.java      |   25 +
 .../entity/openshift/OpenShiftResource.java     |   32 +
 .../entity/openshift/OpenShiftResourceImpl.java |   24 +
 .../location/docker/DockerJcloudsLocation.java  |  196 ++++
 .../location/docker/DockerLocationResolver.java |   89 ++
 .../location/kubernetes/ImageChooser.java       |   85 ++
 .../location/kubernetes/KubernetesCerts.java    |   74 ++
 .../kubernetes/KubernetesClientRegistry.java    |   28 +
 .../KubernetesClientRegistryImpl.java           |   97 ++
 .../location/kubernetes/KubernetesLocation.java | 1052 ++++++++++++++++++
 .../kubernetes/KubernetesLocationConfig.java    |  182 +++
 .../kubernetes/KubernetesLocationResolver.java  |   65 ++
 .../machine/KubernetesEmptyMachineLocation.java |   85 ++
 .../machine/KubernetesMachineLocation.java      |   45 +
 .../machine/KubernetesSshMachineLocation.java   |   46 +
 .../openshift/OpenShiftClientRegistryImpl.java  |   44 +
 .../location/openshift/OpenShiftLocation.java   |  261 +++++
 .../openshift/OpenShiftLocationConfig.java      |   32 +
 .../openshift/OpenShiftLocationResolver.java    |   65 ++
 ...pache.brooklyn.api.location.LocationResolver |   21 +
 .../resources/OSGI-INF/blueprint/blueprint.xml  |   37 +
 .../docker/DockerJcloudsLocationLiveTest.java   |  265 +++++
 .../docker/DockerLocationResolverTest.java      |  118 ++
 .../location/kubernetes/ImageChooserTest.java   |   85 ++
 .../kubernetes/KubernetesCertsTest.java         |  162 +++
 .../kubernetes/KubernetesLocationLiveTest.java  |  235 ++++
 .../KubernetesLocationResolverTest.java         |  102 ++
 .../KubernetesLocationYamlLiveTest.java         |  521 +++++++++
 .../openshift/OpenShiftLocationLiveTest.java    |   65 ++
 .../OpenShiftLocationResolverTest.java          |  103 ++
 .../OpenShiftLocationYamlLiveTest.java          |  141 +++
 .../resources/nginx-replication-controller.yaml |   37 +
 .../src/test/resources/nginx-service.yaml       |   29 +
 41 files changed, 5027 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/pom.xml
----------------------------------------------------------------------
diff --cc locations/container/pom.xml
index 0000000,0000000..b5dd506
new file mode 100644
--- /dev/null
+++ b/locations/container/pom.xml
@@@ -1,0 -1,0 +1,153 @@@
++<?xml version="1.0" encoding="UTF-8"?>
++<!--
++    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.
++-->
++<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
++         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
++    <modelVersion>4.0.0</modelVersion>
++    <artifactId>brooklyn-locations-container</artifactId>
++    <packaging>jar</packaging>
++    <name>Brooklyn Container Location Targets</name>
++
++    <parent>
++        <groupId>org.apache.brooklyn</groupId>
++        <artifactId>brooklyn-parent</artifactId>
++        <version>0.12.0-SNAPSHOT</version>  <!-- BROOKLYN_VERSION -->
++    </parent>
++
++    <properties>
++        <kubernetes-client.version>1.4.27</kubernetes-client.version>
++    </properties>
++    <dependencies>
++        <dependency>
++            <groupId>io.fabric8</groupId>
++            <artifactId>kubernetes-client</artifactId>
++            <version>${kubernetes-client.version}</version>
++            <classifier>bundle</classifier>
++            <exclusions>
++                <exclusion>
++                    <groupId>com.squareup.okio</groupId>
++                    <artifactId>okio</artifactId>
++                </exclusion>
++            </exclusions>
++        </dependency>
++        <dependency>
++            <groupId>io.fabric8</groupId>
++            <artifactId>openshift-client</artifactId>
++            <version>${kubernetes-client.version}</version>
++            <classifier>bundle</classifier>
++        </dependency>
++
++        <!--
++            Expect Brooklyn to exclude this, and to choose its own jclouds-docker version.
++            So if this version is not kept up-to-date then it won't impact Brooklyn.
++        -->
++        <dependency>
++            <groupId>${jclouds.groupId}.api</groupId>
++            <artifactId>docker</artifactId>
++            <version>${jclouds.version}</version>
++            <exclusions>
++                <exclusion>
++                    <!-- Conflicts with javax.ws.rs-api -->
++                    <groupId>javax.ws.rs</groupId>
++                    <artifactId>jsr311-api</artifactId>
++                </exclusion>
++            </exclusions>
++        </dependency>
++
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-core</artifactId>
++            <version>${brooklyn.version}</version>
++        </dependency>
++
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-software-base</artifactId>
++            <version>${brooklyn.version}</version>
++        </dependency>
++
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-locations-jclouds</artifactId>
++            <version>${brooklyn.version}</version>
++        </dependency>
++        <dependency>
++            <groupId>org.slf4j</groupId>
++            <artifactId>slf4j-api</artifactId>
++        </dependency>
++
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-test-support</artifactId>
++            <version>${brooklyn.version}</version>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-utils-test-support</artifactId>
++            <version>${brooklyn.version}</version>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-core</artifactId>
++            <version>${brooklyn.version}</version>
++            <classifier>tests</classifier>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-locations-jclouds</artifactId>
++            <version>${brooklyn.version}</version>
++            <classifier>tests</classifier>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-launcher</artifactId>
++            <version>${brooklyn.version}</version>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-launcher</artifactId>
++            <version>${brooklyn.version}</version>
++            <type>test-jar</type>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.apache.brooklyn</groupId>
++            <artifactId>brooklyn-camp</artifactId>
++            <version>${brooklyn.version}</version>
++            <classifier>tests</classifier>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>org.testng</groupId>
++            <artifactId>testng</artifactId>
++            <scope>test</scope>
++        </dependency>
++        <dependency>
++            <groupId>javax.ws.rs</groupId>
++            <artifactId>javax.ws.rs-api</artifactId>
++            <version>${jax-rs-api.version}</version>
++            <scope>test</scope>
++        </dependency>
++    </dependencies>
++</project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/docker/DockerContainer.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/docker/DockerContainer.java
index 0000000,0000000..f8831a4
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/docker/DockerContainer.java
@@@ -1,0 -1,0 +1,91 @@@
++/*
++ * 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.docker;
++
++import com.google.common.collect.ImmutableMap;
++import com.google.common.reflect.TypeToken;
++import org.apache.brooklyn.api.entity.ImplementedBy;
++import org.apache.brooklyn.config.ConfigKey;
++import org.apache.brooklyn.core.config.BasicConfigInheritance;
++import org.apache.brooklyn.core.config.ConfigKeys;
++import org.apache.brooklyn.core.config.MapConfigKey;
++import org.apache.brooklyn.entity.software.base.SoftwareProcess;
++
++/**
++ * The DockerContainer type is for easily deploying any docker image from the
++ * image repository set on the target swarm or docker-engine based location
++ * <p>
++ * Example YAML is shown below. Note the different types of the {@code env}
++ * key in the location config and the {@code docker.container.environment}
++ * key on the entity. The entity environment variables will override any
++ * environment configured on the location. To specify environment variables
++ * that will be set when executing SSH commands against the container you
++ * should use the {@link SoftwareProcess#SHELL_ENVIRONMENT shell.env} key.
++ * <p>
++ * <pre>{@code location:
++ *   docker:
++ *     endpoint: "https://52.29.59.193:3376"
++ *     identity: "~/.certs/cert.pem"
++ *     credential: "~/.certs/key.pem"
++ *     templateOptions:
++ *       networkMode: "brooklyn"
++ *     env:
++ *       - "HTTP_CONFIG_ROOT=/var/httpd"
++ *       - "USE_DEFAULTS=true"
++ * services:
++ *   - type: org.apache.brooklyn.container.entity.docker.DockerContainer
++ *     brooklyn.config:
++ *       docker.container.imageName: "apache/httpd:latest"
++ *       docker.container.disableSsh: true
++ *       docker.container.inboundPorts:
++ *         - "8080-8081"
++ *       docker.container.environment:
++ *         ENABLE_JMX: false
++ *         ENABLE_SHUTDOWN: false
++ * }</pre>
++ */
++@ImplementedBy(DockerContainerImpl.class)
++public interface DockerContainer extends SoftwareProcess {
++
++    ConfigKey<Boolean> DISABLE_SSH =
++            ConfigKeys.newBooleanConfigKey(
++                    "docker.container.disableSsh",
++                    "Skip checks such as ssh for when docker image doesn't allow ssh",
++                    Boolean.TRUE);
++
++    ConfigKey<String> IMAGE_NAME =
++            ConfigKeys.newStringConfigKey(
++                    "docker.container.imageName",
++                    "Image name to pull from docker hub");
++
++    @SuppressWarnings("serial")
++    ConfigKey<Iterable<String>> INBOUND_TCP_PORTS =
++            ConfigKeys.newConfigKey(
++                    new TypeToken<Iterable<String>>() {
++                    },
++                    "docker.container.inboundPorts",
++                    "List of ports, that the docker image opens, to be made public");
++
++    MapConfigKey<Object> CONTAINER_ENVIRONMENT = new MapConfigKey.Builder<Object>(Object.class, "docker.container.environment")
++            .description("Environment variables to set on container startup")
++            .defaultValue(ImmutableMap.<String, Object>of())
++            .typeInheritance(BasicConfigInheritance.DEEP_MERGE)
++            .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE)
++            .build();
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/docker/DockerContainerImpl.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/docker/DockerContainerImpl.java
index 0000000,0000000..f0a9708
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/docker/DockerContainerImpl.java
@@@ -1,0 -1,0 +1,88 @@@
++/*
++ * 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.docker;
++
++import com.google.common.base.Strings;
++import com.google.common.collect.ImmutableSet;
++import org.apache.brooklyn.api.sensor.AttributeSensor;
++import org.apache.brooklyn.api.sensor.EnricherSpec;
++import org.apache.brooklyn.core.entity.Attributes;
++import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
++import org.apache.brooklyn.core.location.PortRanges;
++import org.apache.brooklyn.core.network.OnPublicNetworkEnricher;
++import org.apache.brooklyn.core.sensor.Sensors;
++import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
++import org.apache.brooklyn.util.collections.MutableList;
++
++import java.util.Iterator;
++import java.util.List;
++
++public class DockerContainerImpl extends EmptySoftwareProcessImpl implements DockerContainer {
++
++    @Override
++    public void init() {
++        super.init();
++
++        String imageName = config().get(DockerContainer.IMAGE_NAME);
++        if (!Strings.isNullOrEmpty(imageName)) {
++            config().set(PROVISIONING_PROPERTIES.subKey("imageId"), imageName);
++        }
++
++        if (Boolean.TRUE.equals(config().get(DockerContainer.DISABLE_SSH))) {
++            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);
++        }
++
++        ImmutableSet.Builder<AttributeSensor<Integer>> builder = ImmutableSet.builder();
++        List<String> portRanges = MutableList.copyOf(config().get(DockerContainer.INBOUND_TCP_PORTS));
++        for (String portRange : portRanges) {
++            Iterator<Integer> iterator = PortRanges.fromString(portRange).iterator();
++            while (iterator.hasNext()) {
++                Integer port = iterator.next();
++                AttributeSensor<Integer> element = Sensors.newIntegerSensor("docker.port." + port);
++                sensors().set(element, port);
++                builder.add(element);
++            }
++        }
++
++        enrichers().add(EnricherSpec.create(OnPublicNetworkEnricher.class).configure(OnPublicNetworkEnricher.SENSORS, builder.build()));
++    }
++
++    @Override
++    protected void disconnectSensors() {
++        if (isSshMonitoringEnabled()) {
++            disconnectServiceUpIsRunning();
++        }
++        super.disconnectSensors();
++    }
++
++    @Override
++    protected void connectSensors() {
++        super.connectSensors();
++        if (isSshMonitoringEnabled()) {
++            connectServiceUpIsRunning();
++        } else {
++            sensors().set(Attributes.SERVICE_UP, true);
++        }
++    }
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
index 0000000,0000000..b5e9a9d
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
@@@ -1,0 -1,0 +1,101 @@@
++/*
++ * 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 com.google.common.collect.ImmutableMap;
++import com.google.common.reflect.TypeToken;
++import org.apache.brooklyn.api.entity.ImplementedBy;
++import org.apache.brooklyn.api.sensor.AttributeSensor;
++import org.apache.brooklyn.config.ConfigKey;
++import org.apache.brooklyn.container.entity.docker.DockerContainer;
++import org.apache.brooklyn.container.location.kubernetes.KubernetesLocationConfig;
++import org.apache.brooklyn.core.config.BasicConfigInheritance;
++import org.apache.brooklyn.core.config.ConfigKeys;
++import org.apache.brooklyn.core.config.MapConfigKey;
++import org.apache.brooklyn.core.sensor.Sensors;
++import org.apache.brooklyn.util.math.MathPredicates;
++
++import java.util.List;
++import java.util.Map;
++
++@ImplementedBy(KubernetesPodImpl.class)
++public interface KubernetesPod extends DockerContainer {
++
++    ConfigKey<String> NAMESPACE = KubernetesLocationConfig.NAMESPACE;
++
++    ConfigKey<Boolean> PRIVILEGED = KubernetesLocationConfig.PRIVILEGED;
++
++    @SuppressWarnings("serial")
++    ConfigKey<List<String>> PERSISTENT_VOLUMES = ConfigKeys.builder(new TypeToken<List<String>>() {
++    })
++            .name("persistentVolumes")
++            .description("Persistent volumes used by the pod")
++            .build();
++
++    ConfigKey<String> DEPLOYMENT = ConfigKeys.builder(String.class)
++            .name("deployment")
++            .description("The name of the service the deployed pod will use")
++            .build();
++
++    ConfigKey<Integer> REPLICAS = ConfigKeys.builder(Integer.class)
++            .name("replicas")
++            .description("Number of replicas in the pod")
++            .constraint(MathPredicates.greaterThanOrEqual(1d))
++            .defaultValue(1)
++            .build();
++
++    @SuppressWarnings("serial")
++    ConfigKey<Map<String, String>> SECRETS = ConfigKeys.builder(new TypeToken<Map<String, String>>() {
++    })
++            .name("secrets")
++            .description("Secrets to be added to the pod")
++            .build();
++
++    @SuppressWarnings("serial")
++    ConfigKey<Map<String, String>> LIMITS = ConfigKeys.builder(new TypeToken<Map<String, String>>() {
++    })
++            .name("limits")
++            .description("Container resource limits for the pod")
++            .build();
++
++    MapConfigKey<Object> METADATA = new MapConfigKey.Builder<Object>(Object.class, "metadata")
++            .description("Metadata to set on the pod")
++            .defaultValue(ImmutableMap.<String, Object>of())
++            .typeInheritance(BasicConfigInheritance.DEEP_MERGE)
++            .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE)
++            .build();
++
++    AttributeSensor<String> KUBERNETES_DEPLOYMENT = Sensors.builder(String.class, "kubernetes.deployment")
++            .description("Deployment resources run in")
++            .build();
++
++    AttributeSensor<String> KUBERNETES_NAMESPACE = Sensors.builder(String.class, "kubernetes.namespace")
++            .description("Namespace that resources run in")
++            .build();
++
++    AttributeSensor<String> KUBERNETES_SERVICE = Sensors.builder(String.class, "kubernetes.service")
++            .description("Service that exposes the deployment")
++            .build();
++
++    AttributeSensor<String> KUBERNETES_POD = Sensors.builder(String.class, "kubernetes.pod")
++            .description("Pod running the deployment")
++            .build();
++
++    String EMPTY = "Empty";
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
index 0000000,0000000..0765896
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
@@@ -1,0 -1,0 +1,25 @@@
++/*
++ * 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.container.entity.docker.DockerContainerImpl;
++
++public class KubernetesPodImpl extends DockerContainerImpl implements KubernetesPod {
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
index 0000000,0000000..5754897
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
@@@ -1,0 -1,0 +1,58 @@@
++/*
++ * 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.api.sensor.AttributeSensor;
++import org.apache.brooklyn.config.ConfigKey;
++import org.apache.brooklyn.core.config.ConfigKeys;
++import org.apache.brooklyn.core.sensor.Sensors;
++import org.apache.brooklyn.entity.software.base.SoftwareProcess;
++import org.apache.brooklyn.util.core.ResourcePredicates;
++
++@ImplementedBy(KubernetesResourceImpl.class)
++public interface KubernetesResource extends SoftwareProcess {
++
++    ConfigKey<String> RESOURCE_FILE = ConfigKeys.builder(String.class)
++            .name("resource")
++            .description("Kubernetes resource YAML file URI")
++            .constraint(ResourcePredicates.urlExists())
++            .build();
++
++    AttributeSensor<String> RESOURCE_TYPE = Sensors.builder(String.class, "kubernetes.resource.type")
++            .description("Kubernetes resource type")
++            .build();
++
++    AttributeSensor<String> RESOURCE_NAME = Sensors.builder(String.class, "kubernetes.resource.name")
++            .description("Kubernetes resource name")
++            .build();
++
++    AttributeSensor<String> KUBERNETES_NAMESPACE = KubernetesPod.KUBERNETES_NAMESPACE;
++
++    String POD = "Pod";
++    String DEPLOYMENT = "Deployment";
++    String REPLICA_SET = "ReplicaSet";
++    String CONFIG_MAP = "ConfigMap";
++    String PERSISTENT_VOLUME = "PersistentVolume";
++    String SECRET = "Secret";
++    String SERVICE = "Service";
++    String REPLICATION_CONTROLLER = "ReplicationController";
++    String NAMESPACE = "Namespace";
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
index 0000000,0000000..d87e22d
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
@@@ -1,0 -1,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.core.entity.BrooklynConfigKeys;
++import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
++
++public class KubernetesResourceImpl extends EmptySoftwareProcessImpl implements KubernetesResource {
++
++    @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/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftPod.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftPod.java
index 0000000,0000000..739580c
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftPod.java
@@@ -1,0 -1,0 +1,27 @@@
++/*
++ * 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.openshift;
++
++import org.apache.brooklyn.api.entity.ImplementedBy;
++import org.apache.brooklyn.container.entity.kubernetes.KubernetesPod;
++
++@ImplementedBy(OpenShiftPodImpl.class)
++public interface OpenShiftPod extends KubernetesPod {
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftPodImpl.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftPodImpl.java
index 0000000,0000000..2d83421
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftPodImpl.java
@@@ -1,0 -1,0 +1,25 @@@
++/*
++ * 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.openshift;
++
++import org.apache.brooklyn.container.entity.kubernetes.KubernetesPodImpl;
++
++public class OpenShiftPodImpl extends KubernetesPodImpl implements OpenShiftPod {
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftResource.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftResource.java
index 0000000,0000000..6c39538
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftResource.java
@@@ -1,0 -1,0 +1,32 @@@
++/*
++ * 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.openshift;
++
++import org.apache.brooklyn.api.entity.ImplementedBy;
++import org.apache.brooklyn.container.entity.kubernetes.KubernetesResource;
++
++@ImplementedBy(OpenShiftResourceImpl.class)
++public interface OpenShiftResource extends KubernetesResource {
++
++    String DEPLOYMENT_CONFIG = "DeploymentConfig";
++    String PROJECT = "Project";
++    String TEMPLATE = "Template";
++    String BUILD_CONFIG = "BuildConfig";
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftResourceImpl.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftResourceImpl.java
index 0000000,0000000..fc1be96
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/openshift/OpenShiftResourceImpl.java
@@@ -1,0 -1,0 +1,24 @@@
++/*
++ * 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.openshift;
++
++import org.apache.brooklyn.container.entity.kubernetes.KubernetesResourceImpl;
++
++public class OpenShiftResourceImpl extends KubernetesResourceImpl implements OpenShiftResource {
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocation.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocation.java
index 0000000,0000000..124a191
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocation.java
@@@ -1,0 -1,0 +1,196 @@@
++/*
++ * 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.location.docker;
++
++import com.google.common.base.Functions;
++import com.google.common.collect.ImmutableList;
++import com.google.common.collect.Maps;
++import org.apache.brooklyn.api.entity.Entity;
++import org.apache.brooklyn.api.location.MachineLocation;
++import org.apache.brooklyn.api.location.NoMachinesAvailableException;
++import org.apache.brooklyn.config.ConfigKey;
++import org.apache.brooklyn.container.entity.docker.DockerContainer;
++import org.apache.brooklyn.core.config.ConfigKeys;
++import org.apache.brooklyn.core.location.LocationConfigKeys;
++import org.apache.brooklyn.location.jclouds.JcloudsLocation;
++import org.apache.brooklyn.location.jclouds.JcloudsLocationCustomizer;
++import org.apache.brooklyn.util.collections.MutableList;
++import org.apache.brooklyn.util.collections.MutableMap;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++import org.apache.brooklyn.util.text.Identifiers;
++import org.apache.brooklyn.util.text.Strings;
++import org.jclouds.compute.ComputeService;
++import org.jclouds.compute.domain.Image;
++import org.jclouds.compute.domain.OsFamily;
++import org.jclouds.compute.domain.Template;
++import org.jclouds.docker.compute.options.DockerTemplateOptions;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++import javax.annotation.Nullable;
++import java.util.Collection;
++import java.util.List;
++import java.util.Map;
++
++import static com.google.common.base.Preconditions.checkNotNull;
++
++/**
++ * For provisioning docker containers, using the jclouds-docker integration.
++ * <p>
++ * This adds special support for default Cloudsoft images. If the image description matches our
++ * cloudsoft regexes, then auto-generate a password and pass that in as
++ * {@code CLOUDSOFT_ROOT_PASSWORD} when launching the container. That will then be used as the
++ * {@link DockerTemplateOptions#getLoginPassword()}.
++ * <p>
++ * Also, if no image is specified then this will set the default to "cloudsoft/centos:7"
++ * (see https://hub.docker.com/r/cloudsoft/centos/).
++ */
++public class DockerJcloudsLocation extends JcloudsLocation {
++
++    public static final ConfigKey<Boolean> INJECT_LOGIN_CREDENTIAL = ConfigKeys.newBooleanConfigKey(
++            "injectLoginCredential",
++            "Whether to inject login credentials (if null, will infer from image choice)",
++            null);
++    public static final ConfigKey<String> DEFAULT_IMAGE_DESCRIPTION_REGEX = ConfigKeys.newStringConfigKey(
++            "defaultImageDescriptionRegex",
++            "The default image description to use, if no other image preferences are supplied",
++            "cloudsoft/centos:7");
++    private static final Logger LOG = LoggerFactory.getLogger(DockerJcloudsLocation.class);
++    /**
++     * The regex for the image descriptions that support us injecting login credentials.
++     */
++    private static final List<String> IMAGE_DESCRIPTION_REGEXES_REQUIRING_INJECTED_LOGIN_CREDS = ImmutableList.of(
++            "cloudsoft/centos.*",
++            "cloudsoft/ubuntu.*");
++
++    private static final List<ImageMetadata> DEFAULT_IMAGES = ImmutableList.of(
++            new ImageMetadata(OsFamily.CENTOS, "7", "cloudsoft/centos:7"),
++            new ImageMetadata(OsFamily.UBUNTU, "14.04", "cloudsoft/ubuntu:14.04"),
++            new ImageMetadata(OsFamily.UBUNTU, "16.04", "cloudsoft/ubuntu:16.04"));
++
++    @Override
++    protected MachineLocation obtainOnce(ConfigBag setup) throws NoMachinesAvailableException {
++        // Use the provider name that jclouds expects; rely on resolver to have validated this.
++        setup.configure(JcloudsLocation.CLOUD_PROVIDER, "docker");
++
++        // Inject default image, if absent
++        String imageId = setup.get(JcloudsLocation.IMAGE_ID);
++        String imageNameRegex = setup.get(JcloudsLocation.IMAGE_NAME_REGEX);
++        String imageDescriptionRegex = setup.get(JcloudsLocation.IMAGE_DESCRIPTION_REGEX);
++        String defaultImageDescriptionRegex = setup.get(DEFAULT_IMAGE_DESCRIPTION_REGEX);
++        OsFamily osFamily = setup.get(OS_FAMILY);
++        String osVersionRegex = setup.get(OS_VERSION_REGEX);
++
++        if (Strings.isBlank(imageId) && Strings.isBlank(imageNameRegex) && Strings.isBlank(imageDescriptionRegex)) {
++            if (osFamily != null || osVersionRegex != null) {
++                for (ImageMetadata imageMetadata : DEFAULT_IMAGES) {
++                    if (imageMetadata.matches(osFamily, osVersionRegex)) {
++                        String imageDescription = imageMetadata.getImageDescription();
++                        LOG.debug("Setting default image regex to {}, for obtain call in {}; removing osFamily={} and osVersionRegex={}",
++                                new Object[]{imageDescription, this, osFamily, osVersionRegex});
++                        setup.configure(JcloudsLocation.IMAGE_DESCRIPTION_REGEX, imageDescription);
++                        setup.configure(OS_FAMILY, null);
++                        setup.configure(OS_VERSION_REGEX, null);
++                        break;
++                    }
++                }
++            } else if (Strings.isNonBlank(defaultImageDescriptionRegex)) {
++                LOG.debug("Setting default image regex to {}, for obtain call in {}", defaultImageDescriptionRegex, this);
++                setup.configure(JcloudsLocation.IMAGE_DESCRIPTION_REGEX, defaultImageDescriptionRegex);
++            }
++        }
++
++        return super.obtainOnce(setup);
++    }
++
++    @Override
++    public Template buildTemplate(ComputeService computeService, ConfigBag config, Collection<JcloudsLocationCustomizer> customizers) {
++        String loginUser = config.get(JcloudsLocation.LOGIN_USER);
++        String loginPassword = config.get(JcloudsLocation.LOGIN_USER_PASSWORD);
++        String loginKeyFile = config.get(JcloudsLocation.LOGIN_USER_PRIVATE_KEY_FILE);
++        String loginKeyData = config.get(JcloudsLocation.LOGIN_USER_PRIVATE_KEY_DATA);
++
++        Template template = super.buildTemplate(computeService, config, customizers);
++        DockerTemplateOptions templateOptions = (DockerTemplateOptions) template.getOptions();
++        Image image = template.getImage();
++        List<String> env = MutableList.copyOf(templateOptions.getEnv());
++
++        // Inject login credentials, if required
++        Boolean injectLoginCredentials = config.get(INJECT_LOGIN_CREDENTIAL);
++        if (injectLoginCredentials == null) {
++            String imageDescription = image.getDescription();
++            for (String regex : IMAGE_DESCRIPTION_REGEXES_REQUIRING_INJECTED_LOGIN_CREDS) {
++                if (imageDescription != null && imageDescription.matches(regex)) {
++                    injectLoginCredentials = true;
++                    break;
++                }
++            }
++        }
++        if (Strings.isBlank(loginUser) && Strings.isBlank(loginPassword) && Strings.isBlank(loginKeyFile) && Strings.isBlank(loginKeyData)) {
++            if (Boolean.TRUE.equals(injectLoginCredentials)) {
++                loginUser = "root";
++                loginPassword = Identifiers.makeRandomPassword(12);
++                templateOptions.overrideLoginUser(loginUser);
++                templateOptions.overrideLoginPassword(loginPassword);
++
++                env.add("CLOUDSOFT_ROOT_PASSWORD=" + loginPassword);
++            }
++        }
++
++        Entity context = validateCallerContext(config);
++        Map<String, Object> containerEnv = MutableMap.copyOf(context.config().get(DockerContainer.CONTAINER_ENVIRONMENT));
++        for (Map.Entry<String, String> entry : Maps.transformValues(containerEnv, Functions.toStringFunction()).entrySet()) {
++            env.add(String.format("%s=%s", entry.getKey(), entry.getValue()));
++        }
++        templateOptions.env(env);
++
++        return template;
++    }
++
++    private Entity validateCallerContext(ConfigBag setup) {
++        // Lookup entity flags
++        Object callerContext = setup.get(LocationConfigKeys.CALLER_CONTEXT);
++        if (callerContext == null || !(callerContext instanceof Entity)) {
++            throw new IllegalStateException("Invalid caller context: " + callerContext);
++        }
++        return (Entity) callerContext;
++    }
++
++    private static class ImageMetadata {
++        private final OsFamily osFamily;
++        private final String osVersion;
++        private final String imageDescription;
++
++        public ImageMetadata(OsFamily osFamily, String osVersion, String imageDescription) {
++            this.osFamily = checkNotNull(osFamily, "osFamily");
++            this.osVersion = checkNotNull(osVersion, "osVersion");
++            this.imageDescription = checkNotNull(imageDescription, "imageDescription");
++        }
++
++        public boolean matches(@Nullable OsFamily osFamily, @Nullable String osVersionRegex) {
++            if (osFamily != null && osFamily != this.osFamily) return false;
++            if (osVersionRegex != null && !osVersion.matches(osVersionRegex)) return false;
++            return true;
++        }
++
++        public String getImageDescription() {
++            return imageDescription;
++        }
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/docker/DockerLocationResolver.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/docker/DockerLocationResolver.java
index 0000000,0000000..e76b12f
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/docker/DockerLocationResolver.java
@@@ -1,0 -1,0 +1,89 @@@
++/*
++ * 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.location.docker;
++
++import org.apache.brooklyn.api.location.Location;
++import org.apache.brooklyn.api.location.LocationResolver;
++import org.apache.brooklyn.core.location.AbstractLocationResolver;
++import org.apache.brooklyn.core.location.LocationConfigUtils;
++import org.apache.brooklyn.core.location.LocationPropertiesFromBrooklynProperties;
++import org.apache.brooklyn.location.jclouds.JcloudsPropertiesFromBrooklynProperties;
++import org.apache.brooklyn.util.collections.MutableMap;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++import java.util.Map;
++
++/**
++ * Locations starting with the given prefix (@code "docker") will use this resolver, to instantiate
++ * a {@link DockerJcloudsLocation}.
++ * <p>
++ * We ensure that config will be picked up from brooklyn.properties using the appropriate precedence:
++ * <ol>
++ * <li>named location config
++ * <li>Prefix {@code brooklyn.location.docker.}
++ * <li>Prefix {@code brooklyn.jclouds.docker.}
++ * <li>Prefix {@code brooklyn.jclouds.}
++ * </ol>
++ */
++public class DockerLocationResolver extends AbstractLocationResolver implements LocationResolver {
++
++    public static final Logger log = LoggerFactory.getLogger(DockerLocationResolver.class);
++
++    public static final String PREFIX = "docker";
++
++    @Override
++    public boolean isEnabled() {
++        return LocationConfigUtils.isResolverPrefixEnabled(managementContext, getPrefix());
++    }
++
++    @Override
++    public String getPrefix() {
++        return PREFIX;
++    }
++
++    @Override
++    protected Class<? extends Location> getLocationType() {
++        return DockerJcloudsLocation.class;
++    }
++
++    @Override
++    protected SpecParser getSpecParser() {
++        return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"docker\"");
++    }
++
++    @Override
++    protected Map<String, Object> getFilteredLocationProperties(String provider, String namedLocation, Map<String, ?> prioritisedProperties, Map<String, ?> globalProperties) {
++        Map<String, Object> dockerConf = new LocationPropertiesFromBrooklynProperties().getLocationProperties(getPrefix(), namedLocation, globalProperties);
++
++        Object providerInConf = dockerConf.get("provider");
++        if (providerInConf != null && !provider.equals(providerInConf)) {
++            throw new IllegalArgumentException(provider + " location configured with provider '" + providerInConf + "', but must be blank or '" + provider + "'");
++        }
++
++        String providerOrApi = "docker";
++        String regionName = (String) prioritisedProperties.get("region");
++        Map<String, Object> jcloudsConf = new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi, regionName, namedLocation, globalProperties);
++        return MutableMap.<String, Object>builder()
++                .putAll(jcloudsConf)
++                .putAll(dockerConf)
++                .put("provider", providerOrApi)
++                .build();
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
index 0000000,0000000..5536b82
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
@@@ -1,0 -1,0 +1,85 @@@
++/*
++ * 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.location.kubernetes;
++
++import com.google.common.base.Optional;
++import com.google.common.collect.ImmutableList;
++import org.jclouds.compute.domain.OsFamily;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++import javax.annotation.Nullable;
++import java.util.List;
++
++import static com.google.common.base.Preconditions.checkNotNull;
++
++public class ImageChooser {
++
++    private static final Logger LOG = LoggerFactory.getLogger(ImageChooser.class);
++    private static final List<ImageMetadata> DEFAULT_IMAGES = ImmutableList.of(
++            new ImageMetadata(OsFamily.CENTOS, "7", "cloudsoft/centos:7"),
++            new ImageMetadata(OsFamily.UBUNTU, "14.04", "cloudsoft/ubuntu:14.04"),
++            new ImageMetadata(OsFamily.UBUNTU, "16.04", "cloudsoft/ubuntu:16.04"));
++    private final List<ImageMetadata> images;
++
++    public ImageChooser() {
++        this.images = DEFAULT_IMAGES;
++    }
++
++    public ImageChooser(List<? extends ImageMetadata> images) {
++        this.images = ImmutableList.copyOf(images);
++    }
++
++    public Optional<String> chooseImage(String osFamily, String osVersionRegex) {
++        return chooseImage((osFamily == null ? (OsFamily) null : OsFamily.fromValue(osFamily)), osVersionRegex);
++    }
++
++    public Optional<String> chooseImage(OsFamily osFamily, String osVersionRegex) {
++        for (ImageMetadata imageMetadata : images) {
++            if (imageMetadata.matches(osFamily, osVersionRegex)) {
++                String imageName = imageMetadata.getImageName();
++                LOG.debug("Choosing container image {}, for osFamily={} and osVersionRegex={}", new Object[]{imageName, osFamily, osVersionRegex});
++                return Optional.of(imageName);
++            }
++        }
++        return Optional.absent();
++    }
++
++    public static class ImageMetadata {
++        private final OsFamily osFamily;
++        private final String osVersion;
++        private final String imageName;
++
++        public ImageMetadata(OsFamily osFamily, String osVersion, String imageName) {
++            this.osFamily = checkNotNull(osFamily, "osFamily");
++            this.osVersion = checkNotNull(osVersion, "osVersion");
++            this.imageName = checkNotNull(imageName, "imageName");
++        }
++
++        public boolean matches(@Nullable OsFamily osFamily, @Nullable String osVersionRegex) {
++            if (osFamily != null && osFamily != this.osFamily) return false;
++            if (osVersionRegex != null && !osVersion.matches(osVersionRegex)) return false;
++            return true;
++        }
++
++        public String getImageName() {
++            return imageName;
++        }
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
index 0000000,0000000..5bb7e2d
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
@@@ -1,0 -1,0 +1,74 @@@
++/*
++ * 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.location.kubernetes;
++
++import com.google.common.base.Optional;
++import org.apache.brooklyn.config.ConfigKey;
++import org.apache.brooklyn.util.core.ResourceUtils;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++import org.apache.brooklyn.util.text.Strings;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++import static org.apache.brooklyn.container.location.kubernetes.KubernetesLocationConfig.*;
++
++class KubernetesCerts {
++
++    private static final Logger LOG = LoggerFactory.getLogger(KubernetesCerts.class);
++
++    public final Optional<String> caCertData;
++    public final Optional<String> clientCertData;
++    public final Optional<String> clientKeyData;
++    public final Optional<String> clientKeyAlgo;
++    public final Optional<String> clientKeyPassphrase;
++
++    public KubernetesCerts(ConfigBag config) {
++        caCertData = getData(CA_CERT_DATA, CA_CERT_FILE, config);
++        clientCertData = getData(CLIENT_CERT_DATA, CLIENT_CERT_FILE, config);
++        clientKeyData = getData(CLIENT_KEY_DATA, CLIENT_KEY_FILE, config);
++        clientKeyAlgo = getNonBlankOptional(CLIENT_KEY_ALGO, config);
++        clientKeyPassphrase = getNonBlankOptional(CLIENT_KEY_PASSPHRASE, config);
++    }
++
++    protected Optional<String> getData(ConfigKey<String> dataKey, ConfigKey<String> fileKey, ConfigBag config) {
++        String data = Strings.isNonBlank(config.get(dataKey)) ? config.get(dataKey).trim() : null;
++        String file = config.get(fileKey);
++        String fileData = Strings.isNonBlank(file) ? getFileContents(file).trim() : null;
++
++        if (Strings.isNonBlank(data) && Strings.isNonBlank(fileData)) {
++            if (data.equals(fileData)) {
++                LOG.warn("Duplicate (matching) configuration for " + dataKey.getName() + " and " + fileKey.getName() + " (continuing)");
++            } else {
++                throw new IllegalStateException("Duplicate conflicting configuration for " + dataKey.getName() + " and " + fileKey.getName());
++            }
++        }
++
++        String result = Strings.isNonBlank(data) ? data : (Strings.isNonBlank(fileData) ? fileData : null);
++        return Optional.fromNullable(result);
++    }
++
++    protected Optional<String> getNonBlankOptional(ConfigKey<? extends String> key, ConfigBag config) {
++        String result = config.get(key);
++        return Optional.fromNullable(Strings.isNonBlank(result) ? result : null);
++    }
++
++    protected String getFileContents(String file) {
++        return ResourceUtils.create(this).getResourceAsString(file);
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
index 0000000,0000000..9cbf0b1
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
@@@ -1,0 -1,0 +1,28 @@@
++/*
++ * 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.location.kubernetes;
++
++import io.fabric8.kubernetes.client.KubernetesClient;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++
++public interface KubernetesClientRegistry {
++
++    KubernetesClient getKubernetesClient(ConfigBag conf);
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
----------------------------------------------------------------------
diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
index 0000000,0000000..27ab589
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
@@@ -1,0 -1,0 +1,97 @@@
++/*
++ * 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.location.kubernetes;
++
++import com.google.common.base.Throwables;
++import com.google.common.io.BaseEncoding;
++import io.fabric8.kubernetes.client.ConfigBuilder;
++import io.fabric8.kubernetes.client.DefaultKubernetesClient;
++import io.fabric8.kubernetes.client.KubernetesClient;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++import org.apache.brooklyn.util.text.Strings;
++import org.apache.brooklyn.util.time.Duration;
++
++import java.net.MalformedURLException;
++import java.net.URL;
++
++import static com.google.common.base.Preconditions.checkNotNull;
++
++public class KubernetesClientRegistryImpl implements KubernetesClientRegistry {
++
++    public static final KubernetesClientRegistryImpl INSTANCE = new KubernetesClientRegistryImpl();
++
++    @Override
++    public KubernetesClient getKubernetesClient(ConfigBag conf) {
++        String masterUrl = checkNotNull(conf.get(KubernetesLocationConfig.MASTER_URL), "master url must not be null");
++
++        URL url;
++        try {
++            url = new URL(masterUrl);
++        } catch (MalformedURLException e) {
++            throw Throwables.propagate(e);
++        }
++
++        ConfigBuilder configBuilder = new ConfigBuilder()
++                .withMasterUrl(masterUrl)
++                .withTrustCerts(false);
++
++        if (url.getProtocol().equals("https")) {
++            KubernetesCerts certs = new KubernetesCerts(conf);
++            if (certs.caCertData.isPresent()) configBuilder.withCaCertData(toBase64Encoding(certs.caCertData.get()));
++            if (certs.clientCertData.isPresent())
++                configBuilder.withClientCertData(toBase64Encoding(certs.clientCertData.get()));
++            if (certs.clientKeyData.isPresent())
++                configBuilder.withClientKeyData(toBase64Encoding(certs.clientKeyData.get()));
++            if (certs.clientKeyAlgo.isPresent()) configBuilder.withClientKeyAlgo(certs.clientKeyAlgo.get());
++            if (certs.clientKeyPassphrase.isPresent())
++                configBuilder.withClientKeyPassphrase(certs.clientKeyPassphrase.get());
++            // TODO Should we also set configBuilder.withTrustCerts(true) here?
++        }
++
++        String username = conf.get(KubernetesLocationConfig.ACCESS_IDENTITY);
++        if (Strings.isNonBlank(username)) configBuilder.withUsername(username);
++
++        String password = conf.get(KubernetesLocationConfig.ACCESS_CREDENTIAL);
++        if (Strings.isNonBlank(password)) configBuilder.withPassword(password);
++
++        String token = conf.get(KubernetesLocationConfig.OAUTH_TOKEN);
++        if (Strings.isNonBlank(token)) configBuilder.withOauthToken(token);
++
++        Duration clientTimeout = conf.get(KubernetesLocationConfig.CLIENT_TIMEOUT);
++        if (clientTimeout.isPositive()) {
++            configBuilder.withConnectionTimeout((int) clientTimeout.toMilliseconds());
++            configBuilder.withRequestTimeout((int) clientTimeout.toMilliseconds());
++        } else {
++            throw new IllegalArgumentException("Kubernetes client timeout should be a positive duration: " + clientTimeout.toString());
++        }
++        Duration actionTimeout = conf.get(KubernetesLocationConfig.ACTION_TIMEOUT);
++        if (actionTimeout.isPositive()) {
++            configBuilder.withRollingTimeout(actionTimeout.toMilliseconds());
++            configBuilder.withScaleTimeout(actionTimeout.toMilliseconds());
++        } else {
++            throw new IllegalArgumentException("Kubernetes action timeout should be a positive duration: " + actionTimeout.toString());
++        }
++
++        return new DefaultKubernetesClient(configBuilder.build());
++    }
++
++    private String toBase64Encoding(String val) {
++        return BaseEncoding.base64().encode(val.getBytes());
++    }
++}