http://git-wip-us.apache.org/repos/asf/stratos/blob/3414e7ce/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Entity.java ---------------------------------------------------------------------- diff --git a/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Entity.java b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Entity.java new file mode 100644 index 0000000..1b37892 --- /dev/null +++ b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Entity.java @@ -0,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 io.fabric8.kubernetes.api; + +/** + * The base interface for all entities which have an ID and a kind + */ +public interface Entity { + + String getId(); + + String getKind(); +}
http://git-wip-us.apache.org/repos/asf/stratos/blob/3414e7ce/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/ExceptionResponseMapper.java ---------------------------------------------------------------------- diff --git a/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/ExceptionResponseMapper.java b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/ExceptionResponseMapper.java new file mode 100644 index 0000000..263dfee --- /dev/null +++ b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/ExceptionResponseMapper.java @@ -0,0 +1,69 @@ +package io.fabric8.kubernetes.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.fabric8.kubernetes.api.model.base.Status; +import io.fabric8.utils.IOHelpers; +import io.fabric8.utils.Strings; +import org.apache.cxf.jaxrs.client.ResponseExceptionMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; + +public class ExceptionResponseMapper implements ResponseExceptionMapper<Exception> { + private static final transient Logger LOG = LoggerFactory.getLogger(ExceptionResponseMapper.class); + + @Override + public Exception fromResponse(Response response) { + try { + Object entity = response.getEntity(); + String message = extractErrorMessage(entity); + message = "HTTP " + response.getStatus() + " " + message; + return new WebApplicationException(message, response); + } catch (Exception ex) { + return new Exception( + "Could not deserialize server side exception: " + ex.getMessage()); + } + } + + public static String extractErrorMessage(Object entity) { + String message = "No message"; + ObjectMapper mapper = KubernetesFactory.createObjectMapper(); + try { + String json = null; + if (entity instanceof InputStream) { + InputStream inputStream = (InputStream) entity; + json = IOHelpers.readFully(inputStream); + } else if (entity != null) { + json = entity.toString(); + } + if (Strings.isNotBlank(json)) { + message = json; + if (textLooksLikeJsonObject(json)) { + try { + Status error = mapper.readValue(json, Status.class); + if (error != null) { + message = error.getMessage(); + if (Strings.isNullOrBlank(message)) { + message = error.getReason(); + } + } + } catch (IOException e) { + LOG.warn("Failed to parse Status from JSON:" + json + ". Reason: " + e, e); + } + } + } + } catch (Exception e) { + message = "Failed to extract message from request: " + e; + } + return message; + } + + protected static boolean textLooksLikeJsonObject(String text) { + text = text.trim(); + return text.startsWith("{") && text.endsWith("}"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/stratos/blob/3414e7ce/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Kubernetes.java ---------------------------------------------------------------------- diff --git a/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Kubernetes.java b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Kubernetes.java new file mode 100644 index 0000000..49a34ef --- /dev/null +++ b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/Kubernetes.java @@ -0,0 +1,283 @@ +/** + * Copyright 2005-2014 Red Hat, Inc. + * + * Red Hat 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 io.fabric8.kubernetes.api; + +import io.fabric8.kubernetes.api.model.Endpoints; +import io.fabric8.kubernetes.api.model.EndpointsList; +import io.fabric8.kubernetes.api.model.Namespace; +import io.fabric8.kubernetes.api.model.NamespaceList; +import io.fabric8.kubernetes.api.model.Node; +import io.fabric8.kubernetes.api.model.NodeList; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodList; +import io.fabric8.kubernetes.api.model.ReplicationController; +import io.fabric8.kubernetes.api.model.ReplicationControllerList; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretList; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceList; + +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +/** + * Represents the Remote API to working with <a href="http://kubernetes.io/">Kubernetes</a> providing a facade + * over the generated JAXRS client. + */ +@Path(Kubernetes.ROOT_API_PATH) +@Produces("application/json") +@Consumes("application/json") +public interface Kubernetes { + + static final String ROOT_API_PATH = "api/v1beta3"; + + static final String NAMESPACE_ALL = ""; + static final String NAMESPACE_DEFAULT = "default"; + + static final String SERVICE_ACCOUNT_TOKEN_FILE = "/var/run/secrets/kubernetes.io/servicaccount/token"; + + /** + * List all namespaces on this cluster + */ + @GET + @Path("namespaces") + NamespaceList getNamespaces(); + + + @POST + @Path("namespaces") + @Consumes("application/json") + String createNamespace(Namespace entity) throws Exception; + + /** + * Get a specific Namespace + */ + @GET + @Path("namespaces/{name}") + Namespace getNamespace(@PathParam("name") @NotNull String name); + + /** + * Update a namespace + * @param namespaceId + * @param entity + */ + @PUT + @Path("namespaces/{name}") + @Consumes("application/json") + String updateNamespace(@PathParam("name") @NotNull String namespaceId, Namespace entity) throws Exception; + + @DELETE + @Path("namespaces/{name}") + @Consumes("text/plain") + String deleteNamespace(@PathParam("name") @NotNull String name) throws Exception; + + /** + * List all pods on this cluster + * @param namespace + */ + @GET + @Path("namespaces/{namespace}/pods") + PodList getPods(@PathParam("namespace") String namespace); + + @POST + @Path("namespaces/{namespace}/pods") + @Consumes("application/json") + String createPod(Pod entity, @PathParam("namespace") String namespace) throws Exception; + + /** + * Get a specific pod + * + * @param podId + * @param namespace + */ + @GET + @Path("namespaces/{namespace}/pods/{podId}") + Pod getPod(@PathParam("podId") @NotNull String podId, @PathParam("namespace") String namespace); + + /** + * Update a pod + * @param podId + * @param entity + * @param namespace + */ + @PUT + @Path("namespaces/{namespace}/pods/{podId}") + @Consumes("application/json") + String updatePod(@PathParam("podId") @NotNull String podId, Pod entity, @PathParam("namespace") String namespace) throws Exception; + + @DELETE + @Path("namespaces/{namespace}/pods/{podId}") + @Consumes("text/plain") + String deletePod(@PathParam("podId") @NotNull String podId, @PathParam("namespace") String namespace) throws Exception; + + /** + * List all services on this cluster + * @param namespace + */ + @Path("namespaces/{namespace}/services") + @GET + @Produces("application/json") + ServiceList getServices(@PathParam("namespace") String namespace); + + @Path("namespaces/{namespace}/services") + @POST + @Consumes("application/json") + String createService(Service entity, @PathParam("namespace") String namespace) throws Exception; + + /** + * Get a specific service + * + * @param serviceId + * @param namespace + */ + @GET + @Path("namespaces/{namespace}/services/{serviceId}") + @Produces("application/json") + Service getService(@PathParam("serviceId") @NotNull String serviceId, @PathParam("namespace") String namespace); + + /** + * Update a service + */ + @PUT + @Path("namespaces/{namespace}/services/{serviceId}") + @Consumes("application/json") + String updateService(@PathParam("serviceId") @NotNull String serviceId, Service entity, @PathParam("namespace") String namespace) throws Exception; + + @DELETE + @Path("namespaces/{namespace}/services/{serviceId}") + @Produces("application/json") + @Consumes("text/plain") + String deleteService(@PathParam("serviceId") @NotNull String serviceId, @PathParam("namespace") String namespace) throws Exception; + + /** + * List all replicationControllers on this cluster + * @param namespace + */ + @Path("namespaces/{namespace}/replicationcontrollers") + @GET + @Produces("application/json") + ReplicationControllerList getReplicationControllers(@PathParam("namespace") String namespace); + + @Path("namespaces/{namespace}/replicationcontrollers") + @POST + @Consumes("application/json") + String createReplicationController(ReplicationController entity, @PathParam("namespace") String namespace) throws Exception; + + @PUT + @Path("namespaces/{namespace}/replicationcontrollers/{controllerId}") + @Consumes("application/json") + String updateReplicationController(@PathParam("controllerId") @NotNull String controllerId, ReplicationController entity, @PathParam("namespace") String namespace) throws Exception; + + /** + * Get a specific controller + * + * @param controllerId + * @param namespace + */ + @GET + @Path("namespaces/{namespace}/replicationcontrollers/{controllerId}") + @Produces("application/json") + ReplicationController getReplicationController(@PathParam("controllerId") @NotNull String controllerId, @PathParam("namespace") String namespace); + + /** + * Delete a specific controller + * + * @param controllerId + */ + @DELETE + @Path("namespaces/{namespace}/replicationcontrollers/{controllerId}") + @Produces("application/json") + @Consumes("text/plain") + String deleteReplicationController(@PathParam("controllerId") @NotNull String controllerId, @PathParam("namespace") String namespace) throws Exception; + + /** + * List all service endpoints on this cluster + */ + @GET + @Path("namespaces/{namespace}/endpoints") + EndpointsList getEndpoints(@PathParam("namespace") String namespace); + + /** + * List all endpoints for a service + */ + @GET + @Path("namespaces/{namespace}/endpoints/{serviceId}") + Endpoints endpointsForService(@PathParam("serviceId") @NotNull String serviceId, @PathParam("namespace") String namespace); + + + /** + * List all secrets on this cluster + * @param namespace + */ + @Path("namespaces/{namespace}/secrets") + @GET + @Produces("application/json") + SecretList getSecrets(@PathParam("namespace") String namespace); + + @Path("namespaces/{namespace}/secrets") + @POST + @Consumes("application/json") + String createSecret(Secret entity, @PathParam("namespace") String namespace) throws Exception; + + /** + * Get a specific secret + * + * @param secretId + * @param namespace + */ + @GET + @Path("namespaces/{namespace}/secrets/{secretId}") + @Produces("application/json") + Secret getSecret(@PathParam("secretId") @NotNull String secretId, @PathParam("namespace") String namespace); + + /** + * Update a secret + */ + @PUT + @Path("namespaces/{namespace}/secrets/{secretId}") + @Consumes("application/json") + String updateSecret(@PathParam("secretId") @NotNull String secretId, Secret entity, @PathParam("namespace") String namespace) throws Exception; + + @DELETE + @Path("namespaces/{namespace}/secrets/{secretId}") + @Produces("application/json") + @Consumes("text/plain") + String deleteSecret(@PathParam("secretId") @NotNull String secretId, @PathParam("namespace") String namespace) throws Exception; + + + + /** + * List all the minions on this cluster + */ + @GET + @Path("nodes") + NodeList getNodes(); + + /** + * List all endpoints for a service + */ + @GET + @Path("nodes/{nodeId}") + Node node(@PathParam("nodeId") @NotNull String nodeId); + +} http://git-wip-us.apache.org/repos/asf/stratos/blob/3414e7ce/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesApiException.java ---------------------------------------------------------------------- diff --git a/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesApiException.java b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesApiException.java new file mode 100644 index 0000000..d04c863 --- /dev/null +++ b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesApiException.java @@ -0,0 +1,11 @@ +package io.fabric8.kubernetes.api; + +public class KubernetesApiException extends Exception { + + public KubernetesApiException() { + } + + public KubernetesApiException(String msg) { + super(msg); + } +} http://git-wip-us.apache.org/repos/asf/stratos/blob/3414e7ce/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesClient.java ---------------------------------------------------------------------- diff --git a/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesClient.java b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesClient.java new file mode 100644 index 0000000..78faa40 --- /dev/null +++ b/dependencies/fabric8/kubernetes-api/src/main/java/io/fabric8/kubernetes/api/KubernetesClient.java @@ -0,0 +1,1618 @@ +/** + * Copyright 2005-2014 Red Hat, Inc. + * + * Red Hat 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 io.fabric8.kubernetes.api; + +import io.fabric8.kubernetes.api.builds.Builds; +import io.fabric8.kubernetes.api.extensions.Configs; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.config.Config; +import io.fabric8.kubernetes.api.model.config.Context; +import io.fabric8.openshift.api.model.*; +import io.fabric8.openshift.api.model.template.Template; +import io.fabric8.utils.*; +import org.apache.cxf.jaxrs.client.WebClient; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import javax.ws.rs.*; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.*; +import java.util.Objects; +import java.util.concurrent.Callable; + +import static io.fabric8.kubernetes.api.KubernetesHelper.*; + +/** + * A simple client interface abstracting away the details of working with + * the {@link io.fabric8.kubernetes.api.KubernetesFactory} and the differences between + * the core {@link io.fabric8.kubernetes.api.Kubernetes} API and the {@link io.fabric8.kubernetes.api.KubernetesExtensions} + */ +public class KubernetesClient implements Kubernetes, KubernetesExtensions, KubernetesGlobalExtensions { + private static final transient Logger LOG = LoggerFactory.getLogger(KubernetesClient.class); + private static final long DEFAULT_TRIGGER_TIMEOUT = 60 * 1000; + + private KubernetesFactory factory; + private Kubernetes kubernetes; + private KubernetesExtensions kubernetesExtensions; + private KubernetesGlobalExtensions kubernetesGlobalExtensions; + private String namespace = defaultNamespace(); + + public static String defaultNamespace() { + String namespace = System.getenv("KUBERNETES_NAMESPACE"); + if (Strings.isNullOrBlank(namespace)) { + namespace = findDefaultOpenShiftNamespace(); + } + if (Strings.isNotBlank(namespace)) { + return namespace; + } + return "default"; + } + + public static String findDefaultOpenShiftNamespace() { + Config config = Configs.parseConfigs(); + if (config != null) { + Context context = Configs.getCurrentContext(config); + if (context != null) { + return context.getNamespace(); + } + } + return null; + } + + public KubernetesClient() { + this(new KubernetesFactory()); + } + + public KubernetesClient(String url) { + this(new KubernetesFactory(url)); + } + + public KubernetesClient(KubernetesFactory factory) { + this.factory = factory; + } + + // Properties + //------------------------------------------------------------------------- + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public Kubernetes getKubernetes() { + if (kubernetes == null) { + kubernetes = getFactory().createKubernetes(); + } + return kubernetes; + } + + public KubernetesExtensions getKubernetesExtensions() { + if (kubernetesExtensions == null) { + kubernetesExtensions = getFactory().createKubernetesExtensions(); + } + return kubernetesExtensions; + } + + public KubernetesGlobalExtensions getKubernetesGlobalExtensions() { + if (kubernetesGlobalExtensions == null) { + kubernetesGlobalExtensions = getFactory().createKubernetesGlobalExtensions(); + } + return kubernetesGlobalExtensions; + } + + public KubernetesFactory getFactory() { + if (factory == null) { + factory = new KubernetesFactory(); + } + return factory; + } + + public void setFactory(KubernetesFactory factory) { + this.factory = factory; + } + + public String getAddress() { + return getFactory().getAddress(); + } + + public String getWriteableAddress() { + return getFactory().getAddress(); + } + + + // Delegated Kubernetes API + //------------------------------------------------------------------------- + + @Override + @GET + @Path("namespaces") + public NamespaceList getNamespaces() { + return getKubernetes().getNamespaces(); + } + + @Override + public String createNamespace(Namespace entity) throws Exception { + return getKubernetes().createNamespace(entity); + } + + @Override + @GET + @Path("namespaces/{name}") + public Namespace getNamespace(final @NotNull String name) { + return handle404ByReturningNull(new Callable<Namespace>() { + @Override + public Namespace call() throws Exception { + return getKubernetes().getNamespace(name); + } + }); + } + + @Override + @PUT + @Path("namespaces/{name}") + @Consumes("application/json") + public String updateNamespace(@NotNull String namespaceId, Namespace entity) throws Exception { + return getKubernetes().updateNamespace(namespaceId, entity); + } + + @Override + @DELETE + @Path("namespaces/{name}") + @Consumes("text/plain") + public String deleteNamespace(@NotNull String name) throws Exception { + return getKubernetes().deleteNamespace(name); + } + + @GET + @Path("pods") + public PodList getPods() { + return getPods(getNamespace()); + } + + @Override + public PodList getPods(@QueryParam("namespace") String namespace) { + validateNamespace(namespace, null); + return getKubernetes().getPods(namespace); + } + + @DELETE + @Path("pods/{podId}") + public String deletePod(@NotNull String podId) throws Exception { + validateNamespace(namespace, podId); + return getKubernetes().deletePod(podId, getNamespace()); + } + + @Override + @DELETE + @Path("pods/{podId}") + @Consumes("text/plain") + public String deletePod(@NotNull String podId, String namespace) throws Exception { + validateNamespace(namespace, podId); + return getKubernetes().deletePod(podId, namespace); + } + + @GET + @Path("replicationControllers/{controllerId}") + @Produces("application/json") + public ReplicationController getReplicationController(@NotNull String controllerId) { + validateNamespace(namespace, controllerId); + return getReplicationController(controllerId, getNamespace()); + } + + @Override + public ReplicationController getReplicationController(final @PathParam("controllerId") @NotNull String controllerId, final @QueryParam("namespace") String namespace) { + validateNamespace(namespace, controllerId); + return handle404ByReturningNull(new Callable<ReplicationController>() { + @Override + public ReplicationController call() throws Exception { + return getKubernetes().getReplicationController(controllerId, namespace); + } + }); + } + + @DELETE + @Path("replicationControllers/{controllerId}") + @Produces("application/json") + public String deleteReplicationController(@NotNull String controllerId) throws Exception { + validateNamespace(namespace, controllerId); + return getKubernetes().deleteReplicationController(controllerId, getNamespace()); + } + + @Override + @DELETE + @Path("replicationControllers/{controllerId}") + @Produces("application/json") + @Consumes("text/plain") + public String deleteReplicationController(@NotNull String controllerId, String namespace) throws Exception { + validateNamespace(namespace, controllerId); + return getKubernetes().deleteReplicationController(controllerId, namespace); + } + + @Override + @DELETE + @Path("services/{serviceId}") + @Produces("application/json") + @Consumes("text/plain") + public String deleteService(@NotNull String serviceId, String namespace) throws Exception { + validateNamespace(namespace, serviceId); + return getKubernetes().deleteService(serviceId, namespace); + } + + @Path("replicationControllers") + @GET + @Produces("application/json") + public ReplicationControllerList getReplicationControllers() { + return getReplicationControllers(getNamespace()); + } + + @Override + public ReplicationControllerList getReplicationControllers(@QueryParam("namespace") String namespace) { + validateNamespace(namespace, null); + return getKubernetes().getReplicationControllers(namespace); + } + + @PUT + @Path("replicationControllers/{controllerId}") + @Consumes("application/json") + public String updateReplicationController(@NotNull String controllerId, ReplicationController entity) throws Exception { + validateNamespace(namespace, entity); + return updateReplicationController(controllerId, entity, getNamespace()); + } + + @PUT + @Path("replicationControllers/{controllerId}") + @Consumes("application/json") + public String updateReplicationController(@NotNull String controllerId, ReplicationController entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + if (!KubernetesHelper.hasResourceVersion(entity)) { + // lets load it from the oldEntity + ReplicationController oldEntity = getReplicationController(controllerId, namespace); + if (oldEntity == null) { + // no entity exists so lets create a new one + return createReplicationController(entity, namespace); + } + String resourceVersion = KubernetesHelper.getResourceVersion(oldEntity); + KubernetesHelper.getOrCreateMetadata(entity).setResourceVersion(resourceVersion); + } + return getKubernetes().updateReplicationController(controllerId, entity, namespace); + } + + @PUT + @Path("services/{serviceId}") + @Consumes("application/json") + public String updateService(@NotNull String serviceId, Service entity) throws Exception { + validateNamespace(namespace, entity); + return updateService(serviceId, entity, getNamespace()); + } + + @Override + public String updateService(@PathParam("serviceId") @NotNull String serviceId, Service entity, @QueryParam("namespace") String namespace) throws Exception { + validateNamespace(namespace, entity); + if (!KubernetesHelper.hasResourceVersion(entity)) { + // lets load it from the old service + Service service = getService(serviceId, namespace); + if (service == null) { + // no service so lets create the service + return createService(entity, namespace); + } + String resourceVersion = KubernetesHelper.getResourceVersion(service); + KubernetesHelper.getOrCreateMetadata(entity).setResourceVersion(resourceVersion); + + // lets copy over some fields set on the spec by kubernetes + ServiceSpec oldSpec = service.getSpec(); + ServiceSpec newSpec = entity.getSpec(); + if (oldSpec != null && newSpec != null) { + if (Strings.isNullOrBlank(newSpec.getPortalIP())) { + newSpec.setPortalIP(oldSpec.getPortalIP()); + } + } + + } + return getKubernetes().updateService(serviceId, entity, namespace); + } + + @GET + @Path("services/{serviceId}") + @Produces("application/json") + public Service getService(@NotNull String serviceId) { + return getService(serviceId, getNamespace()); + } + + @Override + public Service getService(final @PathParam("serviceId") @NotNull String serviceId, final @QueryParam("namespace") String namespace) { + validateNamespace(namespace, serviceId); + return handle404ByReturningNull(new Callable<Service>() { + @Override + public Service call() throws Exception { + return getKubernetes().getService(serviceId, namespace); + } + }); + } + + @DELETE + @Path("services/{serviceId}") + @Produces("application/json") + public String deleteService(@NotNull String serviceId) throws Exception { + validateNamespace(namespace, serviceId); + return deleteService(serviceId, getNamespace()); + } + + @GET + @Path("pods/{podId}") + public Pod getPod(@NotNull String podId) { + return getPod(podId, getNamespace()); + } + + @Override + public Pod getPod(final @PathParam("podId") @NotNull String podId, final @QueryParam("namespace") String namespace) { + validateNamespace(namespace, podId); + return handle404ByReturningNull(new Callable<Pod>() { + @Override + public Pod call() throws Exception { + return getKubernetes().getPod(podId, namespace); + } + }); + } + + @PUT + @Path("pods/{podId}") + @Consumes("application/json") + public String updatePod(@NotNull String podId, Pod entity) throws Exception { + return updatePod(podId, entity, getNamespace()); + } + + @Override + public String updatePod(@PathParam("podId") @NotNull String podId, Pod entity, @QueryParam("namespace") String namespace) throws Exception { + validateNamespace(namespace, podId); + return getKubernetes().updatePod(podId, entity, namespace); + } + + @Path("services") + @GET + @Produces("application/json") + public ServiceList getServices() { + validateNamespace(namespace, null); + return getServices(getNamespace()); + } + + @Override + public ServiceList getServices(@QueryParam("namespace") String namespace) { + validateNamespace(namespace, null); + return getKubernetes().getServices(namespace); + } + + @POST + @Path("pods") + @Consumes("application/json") + public String createPod(Pod entity) throws Exception { + validateNamespace(namespace, entity); + return createPod(entity, getNamespace()); + } + + @Override + @POST + @Path("pods") + @Consumes("application/json") + public String createPod(Pod entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetes().createPod(entity, namespace); + } + + @Path("services") + @POST + @Consumes("application/json") + public String createService(Service entity) throws Exception { + validateNamespace(namespace, entity); + return createService(entity, getNamespace()); + } + + @Override + @Path("services") + @POST + @Consumes("application/json") + public String createService(Service entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetes().createService(entity, namespace); + } + + @Path("replicationControllers") + @POST + @Consumes("application/json") + public String createReplicationController(ReplicationController entity) throws Exception { + validateNamespace(namespace, entity); + return createReplicationController(entity, getNamespace()); + } + + @Override + @Path("replicationControllers") + @POST + @Consumes("application/json") + public String createReplicationController(ReplicationController entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetes().createReplicationController(entity, namespace); + } + + @GET + @Path("endpoints") + public EndpointsList getEndpoints() { + return getEndpoints(getNamespace()); + } + + @Override + @GET + @Path("endpoints") + public EndpointsList getEndpoints(String namespace) { + validateNamespace(namespace, null); + return getKubernetes().getEndpoints(namespace); + } + + @Override + @GET + @Path("endpoints/{serviceId}") + public Endpoints endpointsForService(@NotNull String serviceId, String namespace) { + try { + validateNamespace(namespace, serviceId); + return getKubernetes().endpointsForService(serviceId, namespace); + } catch (WebApplicationException e) { + if (e.getResponse().getStatus() == 404) { + // does not exist + Endpoints answer = new Endpoints(); + answer.setSubsets(new ArrayList<EndpointSubset>()); + return answer; + } else { + throw e; + } + } + } + + @Override + @Path("namespaces/{namespace}/secrets") + @POST + @Consumes("application/json") + public String createSecret(Secret entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetes().createSecret(entity, namespace); + } + + @Override + @DELETE + @Path("namespaces/{namespace}/secrets/{secretId}") + @Produces("application/json") + @Consumes("text/plain") + public String deleteSecret(@NotNull String secretId, String namespace) throws Exception { + validateNamespace(namespace, secretId); + return getKubernetes().deleteSecret(secretId, namespace); + } + + @Override + @GET + @Path("namespaces/{namespace}/secrets/{secretId}") + @Produces("application/json") + public Secret getSecret(final @NotNull String secretId, final String namespace) { + validateNamespace(namespace, secretId); + return handle404ByReturningNull(new Callable<Secret>() { + @Override + public Secret call() throws Exception { + return getKubernetes().getSecret(secretId, namespace); + } + }); + } + + @Override + @Path("namespaces/{namespace}/secrets") + @GET + @Produces("application/json") + public SecretList getSecrets(final String namespace) { + validateNamespace(namespace, null); + SecretList answer = handle404ByReturningNull(new Callable<SecretList>() { + @Override + public SecretList call() throws Exception { + return getKubernetes().getSecrets(namespace); + } + }); + if (answer == null) { + answer = new SecretList(); + } + return answer; + } + + @Override + @PUT + @Path("namespaces/{namespace}/secrets/{secretId}") + @Consumes("application/json") + public String updateSecret(@NotNull String secretId, Secret entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetes().updateSecret(secretId, entity, namespace); + } + + @Override + @GET + @Path("nodes") + public NodeList getNodes() { + return getKubernetes().getNodes(); + } + + @Override + @GET + @Path("nodes/{nodeId}") + public Node node(@NotNull String nodeId) { + return getKubernetes().node(nodeId); + } + + // Delegated KubernetesExtensions API + //------------------------------------------------------------------------- + + + @Override + @POST + @Path("oauthclients") + @Consumes("application/json") + public String createOAuthClient(OAuthClient entity) throws Exception { + getOrCreateMetadata(entity).setNamespace(namespace); + String id = getName(entity); + LOG.info("Creating OAuthClient " + id + " " + summaryText(entity)); + return getKubernetesGlobalExtensions().createOAuthClient(entity); + } + + @Override + @DELETE + @Path("oauthclients/{name}") + public String deleteOAuthClient(@NotNull String name) { + LOG.info("Deleting OAuthClient " + name); + return getKubernetesGlobalExtensions().deleteOAuthClient(name); + } + + @Override + @GET + @Path("oauthclients/{name}") + public OAuthClient getOAuthClient(final @NotNull String name) { + return handle404ByReturningNull(new Callable<OAuthClient>() { + @Override + public OAuthClient call() throws Exception { + return getKubernetesGlobalExtensions().getOAuthClient(name); + } + }); + } + + @Override + @PUT + @Path("oauthclients/{name}") + @Consumes("application/json") + public String updateOAuthClient(@NotNull String name, OAuthClient entity) throws Exception { + LOG.info("Updating OAuthClient " + name + " " + summaryText(entity)); + return getKubernetesGlobalExtensions().updateOAuthClient(name, entity); + } + + @Override + @POST + @Path("routes") + public String createRoute(Route entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().createRoute(entity, namespace); + } + + /** + * Temporary workaround for 0.4.x of Openshift not having osapi/v1beta3 + * @param entity + * @param namespace + */ + public void createRouteOldAPi(Route entity, String namespace) { + validateNamespace(namespace, entity); + + WebClient webClient = getFactory().createWebClient(); + String name = getName(entity); + RouteSpec spec = entity.getSpec(); + String host = null; + String serviceName = null; + if (spec != null) { + host = spec.getHost(); + ObjectReference to = spec.getTo(); + if (to != null) { + serviceName = to.getName(); + } + } + if (Strings.isNullOrBlank(host)) { + throw new IllegalArgumentException("No host defined!"); + } + if (Strings.isNullOrBlank(serviceName)) { + throw new IllegalArgumentException("No to.name defined!"); + } + String json = "{ \"kind\": \"Route\", \"apiVersion\": \"v1beta1\", \"metadata\": { \"name\": \"" + name + "\"}, \"host\": \"" + host + "\", \"serviceName\": \"" + serviceName + "\"}"; + System.out.println("Posting JSON: " + json); + Response response = webClient.path("/osapi/v1beta1/routes").query("namespace", namespace).post(json); + Object responseEntity = response.getEntity(); + if (responseEntity instanceof InputStream) { + InputStream inputStream = (InputStream) responseEntity; + try { + responseEntity = IOHelpers.readFully(inputStream); + } catch (IOException e) { + LOG.error("Failed to parse response: " + e, e); + } + } + System.out.println("Result: " + responseEntity); + int status = response.getStatus(); + System.out.println("Posted and got result: " + status); + } + + @Override + @POST + @Path("deploymentConfigs") + public String createDeploymentConfig(DeploymentConfig entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetesExtensions().createDeploymentConfig(entity, namespace); + } + + @Override + @POST + @Path("templates") + @Consumes("application/json") + public String processTemplate(Template entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().processTemplate(entity, namespace); + } + + @Override + @Path("templates") + @POST + @Consumes("application/json") + public String createTemplate(Template entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().createTemplate(entity, namespace); + } + + @Override + @GET + @Path("templates/{name}") + @Produces("application/json") + public Template getTemplate(final @NotNull String name, final String namespace) { + return handle404ByReturningNull(new Callable<Template>() { + @Override + public Template call() throws Exception { + return getKubernetesExtensions().getTemplate(name, namespace); + } + }); + } + + @Override + @PUT + @Path("templates/{name}") + @Consumes("application/json") + public String updateTemplate(@NotNull String name, Template entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + if (!KubernetesHelper.hasResourceVersion(entity)) { + // lets load it from the oldEntity + ReplicationController oldEntity = getReplicationController(name, namespace); + if (oldEntity == null) { + // no entity exists so lets create a new one + return createTemplate(entity, namespace); + } + String resourceVersion = KubernetesHelper.getResourceVersion(oldEntity); + KubernetesHelper.getOrCreateMetadata(entity).setResourceVersion(resourceVersion); + } + return getKubernetesExtensions().updateTemplate(name, entity, namespace); + } + + @Override + @DELETE + @Path("templates/{name}") + @Produces("application/json") + @Consumes("text/plain") + public String deleteTemplate(@NotNull String name, String namespace) throws Exception { + return getKubernetesExtensions().deleteTemplate(name, namespace); + } + + @Override + @DELETE + @Path("buildConfigs/{name}") + public String deleteBuildConfig(@NotNull String name, String namespace) { + validateNamespace(namespace, name); + return getKubernetesExtensions().deleteBuildConfig(name, namespace); + } + + @Override + @DELETE + @Path("deploymentConfigs/{name}") + public String deleteDeploymentConfig(@NotNull String name, String namespace) { + validateNamespace(namespace, name); + return getKubernetesExtensions().deleteDeploymentConfig(name, namespace); + } + + @GET + @Path("routes") + @Override + public RouteList getRoutes(final @QueryParam("namespace") String namespace) { + validateNamespace(namespace, null); + RouteList answer = handle404ByReturningNull(new Callable<RouteList>() { + @Override + public RouteList call() throws Exception { + return getKubernetesExtensions().getRoutes(namespace); + } + }); + if (answer == null) { + answer = new RouteList(); + } + return answer; + } + + @GET + @Path("routes/{name}") + @Override + public Route getRoute(final @PathParam("name") @NotNull String name, final @QueryParam("namespace") String namespace) { + validateNamespace(namespace, name); + return handle404ByReturningNull(new Callable<Route>() { + @Override + public Route call() throws Exception { + return getKubernetesExtensions().getRoute(name, namespace); + } + }); + } + + @Override + @PUT + @Path("routes/{name}") + @Consumes("application/json") + public String updateRoute(@NotNull String name, Route entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().updateRoute(name, entity, namespace); + } + + @Override + @DELETE + @Path("routes/{name}") + public String deleteRoute(@NotNull String name, String namespace) { + validateNamespace(namespace, name); + return getKubernetesExtensions().deleteRoute(name, namespace); + } + + @Override + @POST + @Path("builds") + public String createBuild(Build entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetesExtensions().createBuild(entity, namespace); + } + + @Override + @DELETE + @Path("builds/{name}") + public String deleteBuild(@NotNull String name, String namespace) { + validateNamespace(namespace, name); + return getKubernetesExtensions().deleteBuild(name, namespace); + } + + @Override + @GET + @Path("builds/{name}") + public Build getBuild(final @NotNull String name, final String namespace) { + validateNamespace(namespace, name); + return handle404ByReturningNull(new Callable<Build>() { + @Override + public Build call() throws Exception { + return getKubernetesExtensions().getBuild(name, namespace); + } + }); + } + + @Override + @GET + @Path("builds") + @Produces("application/json") + public BuildList getBuilds(final String namespace) { + validateNamespace(namespace, null); + BuildList answer = handle404ByReturningNull(new Callable<BuildList>() { + @Override + public BuildList call() throws Exception { + return getKubernetesExtensions().getBuilds(namespace); + } + }); + if (answer == null) { + answer = new BuildList(); + } + return answer; + } + + @Override + @PUT + @Path("builds/{name}") + @Consumes("application/json") + public String updateBuild(@NotNull String name, Build entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().updateBuild(name, entity, namespace); + } + + @Override + @GET + @Path("buildConfigs/{name}") + public BuildConfig getBuildConfig(final @NotNull String name, final String namespace) { + validateNamespace(namespace, name); + return handle404ByReturningNull(new Callable<BuildConfig>() { + @Override + public BuildConfig call() throws Exception { + return getKubernetesExtensions().getBuildConfig(name, namespace); + } + }); + } + + @Override + @GET + @Path("buildConfigs") + public BuildConfigList getBuildConfigs(String namespace) { + validateNamespace(namespace, null); + return getKubernetesExtensions().getBuildConfigs(namespace); + } + + @Override + @GET + @Path("deploymentConfigs/{name}") + public DeploymentConfig getDeploymentConfig(final @NotNull String name, final String namespace) { + validateNamespace(namespace, name); + return handle404ByReturningNull(new Callable<DeploymentConfig>() { + @Override + public DeploymentConfig call() throws Exception { + return getKubernetesExtensions().getDeploymentConfig(name, namespace); + } + }); + } + + @Override + @GET + @Path("deploymentConfigs") + public DeploymentConfigList getDeploymentConfigs(final String namespace) { + validateNamespace(namespace, null); + DeploymentConfigList answer = handle404ByReturningNull(new Callable<DeploymentConfigList>() { + @Override + public DeploymentConfigList call() throws Exception { + return getKubernetesExtensions().getDeploymentConfigs(namespace); + } + }); + if (answer == null) { + answer = new DeploymentConfigList(); + } + return answer; + } + + @Override + @PUT + @Path("buildConfigs/{name}") + @Consumes("application/json") + public String updateBuildConfig(@NotNull String name, BuildConfig entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().updateBuildConfig(name, entity, namespace); + } + + @Override + @PUT + @Path("deploymentConfigs/{name}") + @Consumes("application/json") + public String updateDeploymentConfig(@NotNull String name, DeploymentConfig entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().updateDeploymentConfig(name, entity, namespace); + } + + @Override + @POST + @Path("buildConfigs") + public String createBuildConfig(BuildConfig entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetesExtensions().createBuildConfig(entity, namespace); + } + + @Override + @GET + @Path("imageStreams/{name}") + public ImageStream getImageStream(final @NotNull String name, final String namespace) { + validateNamespace(namespace, name); + return handle404ByReturningNull(new Callable<ImageStream>() { + @Override + public ImageStream call() throws Exception { + return getKubernetesExtensions().getImageStream(name, namespace); + } + }); + } + + @Override + @GET + @Path("imageStreams") + public ImageStreamList getImageStreams(final String namespace) { + validateNamespace(namespace, null); + ImageStreamList answer = handle404ByReturningNull(new Callable<ImageStreamList>() { + @Override + public ImageStreamList call() throws Exception { + return getKubernetesExtensions().getImageStreams(namespace); + } + }); + if (answer == null) { + answer = new ImageStreamList(); + } + return answer; + } + + @Override + @PUT + @Path("imageStreams/{name}") + @Consumes("application/json") + public String updateImageStream(@NotNull String name, ImageStream entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + return getKubernetesExtensions().updateImageStream(name, entity, namespace); + } + + @Override + @DELETE + @Path("imageStreams/{name}") + public String deleteImageStream(@NotNull String name, String namespace) { + validateNamespace(namespace, name); + return getKubernetesExtensions().deleteImageStream(name, namespace); + } + + @Override + @POST + @Path("imageStreams") + public String createImageStream(ImageStream entity, String namespace) throws Exception { + validateNamespace(namespace, entity); + getOrCreateMetadata(entity).setNamespace(namespace); + return getKubernetesExtensions().createImageStream(entity, namespace); + } + + @Override + @POST + @Path("buildConfigHooks/{name}/{secret}/{type}") + public String triggerBuild(@NotNull String name, String namespace, @NotNull String secret, @NotNull String type, byte[] body) { + validateNamespace(namespace, name); + return getKubernetesExtensions().triggerBuild(name, namespace, secret, type, body); + } + + // Helper methods + //------------------------------------------------------------------------- + public void deletePod(Pod entity, String namespace) throws Exception { + if (Strings.isNotBlank(namespace)) { + entity.getMetadata().setNamespace(namespace); + } + deletePod(entity); + } + + public void deletePod(Pod entity) throws Exception { + String namespace = KubernetesHelper.getNamespace(entity); + String id = getName(entity); + validateNamespace(namespace, entity); + LOG.info("Deleting Pod: " + id + " namespace: " + namespace); + if (Strings.isNotBlank(namespace)) { + deletePod(id, namespace); + } else { + deletePod(id); + } + } + + public void deleteService(Service entity, String namespace) throws Exception { + if (Strings.isNotBlank(namespace)) { + entity.getMetadata().setNamespace(namespace); + } + deleteService(entity); + } + + public void deleteService(Service entity) throws Exception { + String namespace = KubernetesHelper.getNamespace(entity); + String id = getName(entity); + validateNamespace(namespace, entity); + LOG.info("Deleting Service: " + id + " namespace: " + namespace); + if (Strings.isNotBlank(namespace)) { + deleteService(id, namespace); + } else { + deleteService(id); + } + } + + public void deleteReplicationControllerAndPods(ReplicationController replicationController, String namespace) throws Exception { + if (Strings.isNotBlank(namespace)) { + replicationController.getMetadata().setNamespace(namespace); + } + deleteReplicationControllerAndPods(replicationController); + } + + public void deleteReplicationControllerAndPods(ReplicationController replicationController) throws Exception { + String id = getName(replicationController); + String namespace = KubernetesHelper.getNamespace(replicationController); + validateNamespace(namespace, replicationController); + LOG.info("Deleting ReplicationController: " + id + " namespace: " + namespace); + deleteReplicationController(replicationController); + List<Pod> podsToDelete = getPodsForReplicationController(replicationController); + for (Pod pod : podsToDelete) { + deletePod(pod); + } + } + + /** + * Validates a namespace is supplied giving a meaningful error if not + */ + protected void validateNamespace(String namespace, Object entity) { + if (Strings.isNullOrBlank(namespace)) { + String message = "No namespace supported"; + if (entity != null) { + message += " for " + KubernetesHelper.summaryText(entity); + } + throw new IllegalArgumentException(message); + } + } + + public void deleteReplicationController(ReplicationController replicationController, String namespace) throws Exception { + if (Strings.isNotBlank(namespace)) { + replicationController.getMetadata().setNamespace(namespace); + } + deleteReplicationController(replicationController); + } + + public void deleteReplicationController(ReplicationController entity) throws Exception { + String namespace = KubernetesHelper.getNamespace(entity); + String id = getName(entity); + if (Strings.isNotBlank(namespace)) { + deleteReplicationController(id, namespace); + } else { + deleteReplicationController(id); + } + } + + public ReplicationController getReplicationControllerForPod(String podId) { + Pod pod = getPod(podId); + return getReplicationControllerForPod(pod); + } + + public ReplicationController getReplicationControllerForPod(Pod pod) { + if (pod != null) { + Map<String, String> labels = pod.getMetadata().getLabels(); + if (labels != null && labels.size() > 0) { + ReplicationControllerList replicationControllers = getReplicationControllers(); + List<ReplicationController> items = replicationControllers.getItems(); + if (items != null) { + List<ReplicationController> matched = new ArrayList<>(); + for (ReplicationController item : items) { + if (filterLabels(labels, item.getMetadata().getLabels())) { + matched.add(item); + } + } + int matchedSize = matched.size(); + if (matchedSize > 1) { + // lets remove all the RCs with no current replicas and hope there's only 1 left + List<ReplicationController> nonZeroReplicas = Filters.filter(matched, new Filter<ReplicationController>() { + @Override + public boolean matches(ReplicationController replicationController) { + ReplicationControllerSpec replicationControllerSpec = replicationController.getSpec(); + if (replicationControllerSpec != null) { + Integer desiredReplicas = replicationControllerSpec.getReplicas(); + if (desiredReplicas != null && desiredReplicas.intValue() > 0) { + ReplicationControllerStatus currentStatus = replicationController.getStatus(); + if (currentStatus != null) { + Integer replicas = currentStatus.getReplicas(); + if (replicas != null && replicas.intValue() > 0) { + return true; + } + } + } + } + return false; + } + }); + int size = nonZeroReplicas.size(); + if (size > 0) { + // lets pick the first one for now :) + return nonZeroReplicas.get(0); + } + } + if (matchedSize >= 1) { + // otherwise lets pick the first one we found + return matched.get(0); + } + } + } + } + return null; + } + + public List<Pod> getPodsForReplicationController(ReplicationController service) { + return KubernetesHelper.getPodsForReplicationController(service, getPodList()); + } + + public List<Pod> getPodsForReplicationController(String replicationControllerId) { + ReplicationController replicationController = getReplicationController(replicationControllerId); + if (replicationController == null) { + return Collections.EMPTY_LIST; + } else { + return getPodsForReplicationController(replicationController); + } + } + + public List<Pod> getPodsForService(Service service) { + return KubernetesHelper.getPodsForService(service, getPodList()); + } + + public List<Pod> getPodsForService(String serviceId) { + Service service = getService(serviceId); + if (service == null) { + return Collections.EMPTY_LIST; + } else { + return getPodsForService(service); + } + } + + public WebSocketClient watchPods(Watcher<Pod> watcher) throws Exception { + return watchPods(null, watcher); + } + + public WebSocketClient watchPods(Map<String, String> labels, Watcher<Pod> watcher) throws Exception { + return watchPods(getNamespace(), labels, watcher); + } + + public WebSocketClient watchPods(String namespace, Map<String, String> labels, Watcher<Pod> watcher) throws Exception { + PodList currentPodList = getPods(namespace); + return watchPods(namespace, labels, watcher, + currentPodList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchPods(String namespace, Map<String, String> labels, Watcher<Pod> watcher, String resourceVersion) throws Exception { + return watchKubernetesEntities("pods", namespace, labels, watcher, resourceVersion); + } + + public WebSocketClient watchServices(Watcher<Service> watcher) throws Exception { + return watchServices(null, watcher); + } + + public WebSocketClient watchServices(Map<String, String> labels, Watcher<Service> watcher) throws Exception { + return watchServices(getNamespace(), labels, watcher); + } + + public WebSocketClient watchServices(String namespace, Map<String, String> labels, Watcher<Service> watcher) throws Exception { + ServiceList currentServiceList = getServices(namespace); + return watchServices(namespace, labels, watcher, + currentServiceList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchServices(String namespace, Map<String, String> labels, Watcher<Service> watcher, String resourceVersion) throws Exception { + return watchKubernetesEntities("services", namespace, labels, watcher, resourceVersion); + } + + public WebSocketClient watchEndpoints(Watcher<Endpoints> watcher) throws Exception { + return watchEndpoints(null, watcher); + } + + public WebSocketClient watchEndpoints(Map<String, String> labels, Watcher<Endpoints> watcher) throws Exception { + return watchEndpoints(getNamespace(), labels, watcher); + } + + public WebSocketClient watchEndpoints(String namespace, Map<String, String> labels, Watcher<Endpoints> watcher) throws Exception { + EndpointsList currentEndpointList = getEndpoints(namespace); + return watchEndpoints(namespace, labels, watcher, + currentEndpointList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchEndpoints(String namespace, Map<String, String> labels, Watcher<Endpoints> watcher, String resourceVersion) throws Exception { + return watchKubernetesEntities("endpoints", namespace, labels, watcher, resourceVersion); + } + + public WebSocketClient watchReplicationControllers(Watcher<ReplicationController> watcher) throws Exception { + return watchReplicationControllers(null, watcher); + } + + public WebSocketClient watchReplicationControllers(Map<String, String> labels, Watcher<ReplicationController> watcher) throws Exception { + return watchReplicationControllers(getNamespace(), labels, watcher); + } + + public WebSocketClient watchReplicationControllers(String namespace, Map<String, String> labels, Watcher<ReplicationController> watcher) throws Exception { + ReplicationControllerList currentReplicationControllerList = getReplicationControllers(namespace); + return watchReplicationControllers(namespace, labels, watcher, + currentReplicationControllerList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchReplicationControllers(String namespace, Map<String, String> labels, Watcher<ReplicationController> watcher, String resourceVersion) throws Exception { + return watchKubernetesEntities("replicationcontrollers", namespace, labels, watcher, resourceVersion); + } + + public WebSocketClient watchBuilds(Watcher<Build> watcher) throws Exception { + return watchBuilds(null, watcher); + } + + public WebSocketClient watchBuilds(Map<String, String> labels, Watcher<Build> watcher) throws Exception { + return watchBuilds(getNamespace(), labels, watcher); + } + + public WebSocketClient watchBuilds(String namespace, Map<String, String> labels, Watcher<Build> watcher) throws Exception { + BuildList currentList = getBuilds(namespace); + return watchBuilds(namespace, labels, watcher, + currentList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchBuilds(String namespace, Map<String, String> labels, Watcher<Build> watcher, String resourceVersion) throws Exception { + return watchOpenShiftEntities("builds", namespace, labels, watcher, resourceVersion); + } + + public WebSocketClient watchRoutes(Watcher<Route> watcher) throws Exception { + return watchRoutes(null, watcher); + } + + public WebSocketClient watchRoutes(Map<String, String> labels, Watcher<Route> watcher) throws Exception { + return watchRoutes(getNamespace(), labels, watcher); + } + + public WebSocketClient watchRoutes(String namespace, Map<String, String> labels, Watcher<Route> watcher) throws Exception { + RouteList currentList = getRoutes(namespace); + return watchRoutes(namespace, labels, watcher, + currentList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchRoutes(String namespace, Map<String, String> labels, Watcher<Route> watcher, String resourceVersion) throws Exception { + return watchOpenShiftEntities("routes", namespace, labels, watcher, resourceVersion); + } + + public WebSocketClient watchDeploymentConfigs(Watcher<DeploymentConfig> watcher) throws Exception { + return watchDeploymentConfigs(null, watcher); + } + + public WebSocketClient watchDeploymentConfigs(Map<String, String> labels, Watcher<DeploymentConfig> watcher) throws Exception { + return watchDeploymentConfigs(getNamespace(), labels, watcher); + } + + public WebSocketClient watchDeploymentConfigs(String namespace, Map<String, String> labels, Watcher<DeploymentConfig> watcher) throws Exception { + DeploymentConfigList currentList = getDeploymentConfigs(namespace); + return watchDeploymentConfigs(namespace, labels, watcher, + currentList.getMetadata().getResourceVersion()); + } + + public WebSocketClient watchDeploymentConfigs(String namespace, Map<String, String> labels, Watcher<DeploymentConfig> watcher, String resourceVersion) throws Exception { + return watchOpenShiftEntities("deploymentconfigs", namespace, labels, watcher, resourceVersion); + } + + private WebSocketClient watchKubernetesEntities(String entityType, String namespace, Map<String, String> labels, Watcher<? extends HasMetadata> watcher, String resourceVersion) throws Exception { + return watchEntities(Kubernetes.ROOT_API_PATH, entityType, namespace, labels, watcher, resourceVersion); + } + + private WebSocketClient watchOpenShiftEntities(String entityType, String namespace, Map<String, String> labels, Watcher<? extends HasMetadata> watcher, String resourceVersion) throws Exception { + return watchEntities(KubernetesExtensions.OSAPI_ROOT_PATH, entityType, namespace, labels, watcher, resourceVersion); + } + + private WebSocketClient watchEntities(String apiPath, String entityType, String namespace, Map<String, String> labels, Watcher<? extends HasMetadata> watcher, String resourceVersion) throws Exception { + String watchUrl = getAddress().replaceFirst("^http", "ws") + "/" + apiPath + "/namespaces/" + namespace + "/" + entityType + "?watch=true&resourceVersion=" + resourceVersion; + String labelsString = toLabelsString(labels); + if (Strings.isNotBlank(labelsString)) { + watchUrl += "&labelSelector=" + labelsString; + } + LOG.debug("Connecting to {}", watchUrl); + + WebSocketClient client = getFactory().createWebSocketClient(); + try { + URI watchUri = URI.create(watchUrl); + ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); + upgradeRequest.setRequestURI(watchUri); + upgradeRequest.setHeader("Origin", watchUri.getHost() + ":" + watchUri.getPort()); + String token = getFactory().findToken(); + if (token != null) { + upgradeRequest.setHeader("Authorization", "Bearer " + token); + } + client.start(); + client.connect(watcher, watchUri, upgradeRequest); + return client; + } catch (Throwable t) { + LOG.error("Failed to watch pods", t); + return null; + } + } + + /** + * Returns the URL to access the service; using the environment variables, routes + * or service portalIP address + * + * @throws IllegalArgumentException if the URL cannot be found for the serviceName and namespace + */ + public String getServiceURL(String serviceName, String namespace, String serviceProtocol, boolean serviceExternal) { + Service srv = null; + String serviceHost = serviceToHost(serviceName); + String servicePort = serviceToPort(serviceName); + String serviceProto = serviceProtocol != null ? serviceProtocol : serviceToProtocol(serviceName, servicePort); + + //1. Inside Kubernetes: Services as ENV vars + if (!serviceExternal && Strings.isNotBlank(serviceHost) && Strings.isNotBlank(servicePort) && Strings.isNotBlank(serviceProtocol)) { + return serviceProtocol + "://" + serviceHost + ":" + servicePort; + //2. Anywhere: When namespace is passed System / Env var. Mostly needed for integration tests. + } else if (Strings.isNotBlank(namespace)) { + srv = getService(serviceName, namespace); + } else { + for (Service s : getServices().getItems()) { + String sid = getName(s); + if (serviceName.equals(sid)) { + srv = s; + break; + } + } + } + if (srv == null) { + throw new IllegalArgumentException("No kubernetes service could be found for name: " + serviceName + " in namespace: " + namespace); + } + RouteList routeList = getRoutes(namespace); + for (Route route : routeList.getItems()) { + if (route.getSpec().getTo().getName().equals(serviceName)) { + return (serviceProto + "://" + route.getSpec().getHost()).toLowerCase(); + } + } + return (serviceProto + "://" + srv.getSpec().getPortalIP() + ":" + srv.getSpec().getPorts().iterator().next().getPort()).toLowerCase(); + } + + + // Extension helper methods + //------------------------------------------------------------------------- + + /** + * Returns the route for the given id and namespace or null if it could not be found. + */ + public Route findRoute(String id, String namespace) { + Route route = null; + try { + route = getRoute(id, namespace); + } catch (WebApplicationException e) { + if (e.getResponse().getStatus() == 404) { + // does not exist + } else { + throw e; + } + } + return route; + } + + /** + * Triggers a build and returns the UID of the newly created build if it can be found within the default time period + */ + public String triggerBuildAndGetUuid(@NotNull String name, String namespace) { + return triggerBuildAndGetUuid(name, namespace, DEFAULT_TRIGGER_TIMEOUT); + } + + /** + * Triggers a build and returns the UID of the newly created build if it can be found within the given time period + */ + public String triggerBuildAndGetUuid(@NotNull String name, String namespace, long maxTimeoutMs) { + String answer = triggerBuild(name, namespace); + if (Strings.isNullOrBlank(answer)) { + // lets poll the builds to find the latest build for this name + int sleepMillis = 2000; + long endTime = System.currentTimeMillis() + maxTimeoutMs; + while (true) { + Build build = findLatestBuild(name, namespace); + // lets assume that the build is created immediately on the webhook + if (build != null) { + String uid = Builds.getUid(build); + answer = uid; + break; + } + if (System.currentTimeMillis() > endTime) { + break; + } else { + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + } + return answer; + } + + /** + * Returns all the builds for the given buildConfigName and namespace + */ + public List<Build> findBuilds(String buildConfigName, String namespace) { + List<Build> answer = new ArrayList<>(); + BuildList buildList = getBuilds(namespace); + if (buildList != null) { + List<Build> items = buildList.getItems(); + if (items != null) { + for (Build build : items) { + String namespace2 = Builds.getNamespace(build); + String name2 = Builds.getBuildConfigName(build); + if (Objects.equals(namespace, namespace2) && Objects.equals(buildConfigName, name2)) { + answer.add(build); + } + } + } + } + return answer; + } + + public Build findLatestBuild(String name, String namespace) { + List<Build> builds = findBuilds(name, namespace); + int size = builds.size(); + if (size < 1) { + return null; + } else if (size == 1) { + return builds.get(0); + } else { + // TODO add each build and sort by date... + SortedMap<Date, Build> map = new TreeMap<>(); + for (Build build : builds) { + Date date = Builds.getCreationTimestampDate(build); + if (date != null) { + Build otherBuild = map.get(date); + if (otherBuild != null) { + LOG.warn("Got 2 builds at the same time: " + build + " and " + otherBuild); + } else { + map.put(date, build); + } + } + } + Date lastKey = map.lastKey(); + Build build = map.get(lastKey); + if (build == null) { + LOG.warn("Should have a value for the last key " + lastKey + " for builds " + map); + } + return build; + } + } + + public String triggerBuild(@NotNull String name, String namespace) { + BuildConfig buildConfig = getBuildConfig(name, namespace); + if (buildConfig != null) { + List<BuildTriggerPolicy> triggers = buildConfig.getSpec().getTriggers(); + String type = null; + String secret = null; + for (BuildTriggerPolicy trigger : triggers) { + WebHookTrigger hook = trigger.getGeneric(); + if (hook != null) { + secret = hook.getSecret(); + String aType = trigger.getType(); + if (Strings.isNotBlank(secret) && Strings.isNotBlank(aType)) { + type = aType; + } + } + } + if (Strings.isNullOrBlank(secret) || Strings.isNullOrBlank(type)) { + for (BuildTriggerPolicy trigger : triggers) { + WebHookTrigger hook = trigger.getGithub(); + if (hook != null) { + secret = hook.getSecret(); + String aType = trigger.getType(); + if (Strings.isNotBlank(secret) && Strings.isNotBlank(aType)) { + type = aType; + } + } + } + } + if (Strings.isNullOrBlank(type)) { + throw new IllegalArgumentException("BuildConfig does not have a generic or github trigger for build: " + name + " namespace: "+ namespace); + } + if (Strings.isNullOrBlank(secret)) { + throw new IllegalArgumentException("BuildConfig does not have secret for build: " + name + " namespace: "+ namespace); + } + LOG.info("Triggering build " + name + " namespace: " + namespace + " type: " + type); + return doTriggerBuild(name, namespace, type, secret); + } else { + throw new IllegalArgumentException("No BuildConfig for build: " + name + " namespace: "+ namespace); + } + } + + protected String doTriggerBuild(String name, String namespace, String type, String secret) { + String baseUrl; + String url; + WebClient webClient; + boolean useVanillaUrl = true; + boolean useFabric8Console = true; + if (useFabric8Console) { + // lets proxy through the fabric8 console REST API to work around bugs in OpenShift... + baseUrl = getServiceURL(ServiceNames.FABRIC8_CONSOLE, namespace, "http", false); + url = URLUtils.pathJoin("/kubernetes/osapi", defaultOsApiVersion, "buildConfigHooks", name, secret, type); + webClient = new KubernetesFactory(baseUrl, true).createWebClient(); + } else { + // using the direct REST API... + KubernetesFactory factory = getFactory(); + baseUrl = factory.getAddress(); + webClient = factory.createWebClient(); + url = URLUtils.pathJoin("/osapi", defaultOsApiVersion, "buildConfigHooks", name, secret, type); + } + if (Strings.isNotBlank(namespace)) { + url += "?namespace=" + namespace; + } + if (useVanillaUrl) { + String triggerBuildUrlText = URLUtils.pathJoin(baseUrl, url); + LOG.info("Using a URL to trigger: " + triggerBuildUrlText); + try { + URL triggerBuildURL = new URL(triggerBuildUrlText); + HttpURLConnection connection = (HttpURLConnection) triggerBuildURL.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + connection.setDoOutput(true); + + OutputStreamWriter out = new OutputStreamWriter( + connection.getOutputStream()); + out.close(); + + int status = connection.getResponseCode(); + String message = connection.getResponseMessage(); + System.out.println("Got response code: " + status + " message: " + message); + if (status != 200) { + throw new WebApplicationException(status + ": " + message, status); + } else { + return null; + } + } catch (IOException e) { + throw new WebApplicationException(e, 400); + } + + } else { + + LOG.info("Triggering build by posting to: " + url); + + webClient.getHeaders().remove(HttpHeaders.ACCEPT); + webClient = webClient.path(url). + header(HttpHeaders.CONTENT_TYPE, "application/json"); + + Response response = webClient. + post(new HashMap()); + int status = response.getStatus(); + if (status != 200) { + Object entity = response.getEntity(); + if (entity != null) { + String message = ExceptionResponseMapper.extractErrorMessage(entity); + throw new WebApplicationException(status + ": " + message, status); + } else { + throw new WebApplicationException(status); + } + } + return null; + } + //return getKubernetesExtensions().triggerBuild(name, namespace, secret, type, new byte[0]); + } + + + + /** + * A helper method to handle REST APIs which throw a 404 by just returning null + */ + protected static <T> T handle404ByReturningNull(Callable<T> callable) { + try { + return callable.call(); + } catch (WebApplicationException e) { + if (e.getResponse().getStatus() == 404) { + return null; + } else { + throw e; + } + } catch (Exception e) { + throw new WebApplicationException(e); + } + } + + + // implementation methods + //------------------------------------------------------------------------- + + protected Collection<Pod> getPodList() { + return getPodMap(this, namespace).values(); + } + +}
