From b113cebbbd9ab3251f2e240dce8987a09d7eab35 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Wed, 4 Jun 2014 11:10:28 -0400 Subject: [PATCH] initial commit --- .gitignore | 6 + LICENSE | 202 ++++++ NOTICE | 2 + README.md | 32 + checkstyle.xml | 287 ++++++++ findbugs-exclude.xml | 8 + java.header | 21 + pom.xml | 212 ++++++ push-release.sh | 4 + release.sh | 18 + .../docker/ContainerNotFoundException.java | 40 ++ .../spotify/docker/DefaultDockerClient.java | 404 +++++++++++ .../java/com/spotify/docker/DockerClient.java | 253 +++++++ .../com/spotify/docker/DockerException.java | 37 + .../docker/DockerRequestException.java | 82 +++ .../docker/DockerTimeoutException.java | 44 ++ .../docker/ImageNotFoundException.java | 46 ++ .../java/com/spotify/docker/ImagePull.java | 91 +++ .../docker/ImagePullFailedException.java | 41 ++ .../java/com/spotify/docker/ImageRef.java | 58 ++ .../java/com/spotify/docker/LogMessage.java | 78 +++ .../java/com/spotify/docker/LogReader.java | 68 ++ .../java/com/spotify/docker/LogStream.java | 87 +++ .../spotify/docker/LogsResponseReader.java | 52 ++ .../spotify/docker/ObjectMapperProvider.java | 125 ++++ .../spotify/docker/PullResponseReader.java | 52 ++ .../spotify/docker/messages/Container.java | 208 ++++++ .../docker/messages/ContainerConfig.java | 640 ++++++++++++++++++ .../docker/messages/ContainerCreation.java | 91 +++ .../docker/messages/ContainerExit.java | 77 +++ .../docker/messages/ContainerInfo.java | 251 +++++++ .../docker/messages/ContainerState.java | 133 ++++ .../spotify/docker/messages/HostConfig.java | 430 ++++++++++++ .../spotify/docker/messages/ImageInfo.java | 186 +++++ .../docker/messages/NetworkSettings.java | 205 ++++++ .../spotify/docker/messages/PortBinding.java | 100 +++ .../com/spotify/docker/DockerClientTest.java | 146 ++++ .../java/com/spotify/docker/ImageRefTest.java | 54 ++ 38 files changed, 4871 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 checkstyle.xml create mode 100644 findbugs-exclude.xml create mode 100644 java.header create mode 100644 pom.xml create mode 100755 push-release.sh create mode 100755 release.sh create mode 100644 src/main/java/com/spotify/docker/ContainerNotFoundException.java create mode 100644 src/main/java/com/spotify/docker/DefaultDockerClient.java create mode 100644 src/main/java/com/spotify/docker/DockerClient.java create mode 100644 src/main/java/com/spotify/docker/DockerException.java create mode 100644 src/main/java/com/spotify/docker/DockerRequestException.java create mode 100644 src/main/java/com/spotify/docker/DockerTimeoutException.java create mode 100644 src/main/java/com/spotify/docker/ImageNotFoundException.java create mode 100644 src/main/java/com/spotify/docker/ImagePull.java create mode 100644 src/main/java/com/spotify/docker/ImagePullFailedException.java create mode 100644 src/main/java/com/spotify/docker/ImageRef.java create mode 100644 src/main/java/com/spotify/docker/LogMessage.java create mode 100644 src/main/java/com/spotify/docker/LogReader.java create mode 100644 src/main/java/com/spotify/docker/LogStream.java create mode 100644 src/main/java/com/spotify/docker/LogsResponseReader.java create mode 100644 src/main/java/com/spotify/docker/ObjectMapperProvider.java create mode 100644 src/main/java/com/spotify/docker/PullResponseReader.java create mode 100644 src/main/java/com/spotify/docker/messages/Container.java create mode 100644 src/main/java/com/spotify/docker/messages/ContainerConfig.java create mode 100644 src/main/java/com/spotify/docker/messages/ContainerCreation.java create mode 100644 src/main/java/com/spotify/docker/messages/ContainerExit.java create mode 100644 src/main/java/com/spotify/docker/messages/ContainerInfo.java create mode 100644 src/main/java/com/spotify/docker/messages/ContainerState.java create mode 100644 src/main/java/com/spotify/docker/messages/HostConfig.java create mode 100644 src/main/java/com/spotify/docker/messages/ImageInfo.java create mode 100644 src/main/java/com/spotify/docker/messages/NetworkSettings.java create mode 100644 src/main/java/com/spotify/docker/messages/PortBinding.java create mode 100644 src/test/java/com/spotify/docker/DockerClientTest.java create mode 100644 src/test/java/com/spotify/docker/ImageRefTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..47e0870a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +target/ +pom.xml.versionsBackup +*.iml +*~ +.idea/* +.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..b678f65cd --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +Docker Client +Copyright (c) 2014 Spotify AB diff --git a/README.md b/README.md new file mode 100644 index 000000000..6f4bbe208 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +Docker Client +============= + +This is a simple [docker](https://github.com/dotcloud/docker) client written in Java. + +```java +final DockerClient docker = new DefaultDockerClient("http://localhost:4243"); + +// Pull image +docker.pull("busybox"); + +// Create container +final ContainerConfig config = ContainerConfig.builder() + .image("busybox") + .cmd("sh", "-c", "while :; do sleep 1; done") + .build(); +final ContainerCreation creation = docker.createContainer(config); +final String id = creation.id(); + +// Inspect container +final ContainerInfo info = docker.inspectContainer(id); + +// Start container +docker.startContainer(id); + +// Kill container +docker.killContainer(id); + +// Remove container +docker.removeContainer(id); +``` + diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 000000000..bfa5e1161 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml new file mode 100644 index 000000000..05279e545 --- /dev/null +++ b/findbugs-exclude.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/java.header b/java.header new file mode 100644 index 000000000..f1b6ad577 --- /dev/null +++ b/java.header @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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. + */ + diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..d23956e7b --- /dev/null +++ b/pom.xml @@ -0,0 +1,212 @@ + + + + 4.0.0 + + com.spotify + docker-client + 1.0-SNAPSHOT + jar + docker-client + https://github.com/spotify/docker-client + + + UTF-8 + + + + org.sonatype.oss + oss-parent + 7 + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + scm:git:https://github.com/spotify/docker-client + scm:git:git@github.com:spotify/docker-client + https://github.com/spotify/docker-client + + + + + + org.slf4j + slf4j-api + 1.7.6 + + + com.google.guava + guava + 17.0 + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + 2.2.3 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.2.3 + + + com.fasterxml.jackson.datatype + jackson-datatype-guava + 2.2.3 + + + com.fasterxml.jackson.core + jackson-databind + 2.2.3 + + + com.sun.jersey + jersey-client + 1.18.1 + + + com.sun.jersey + jersey-core + 1.18.1 + + + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5 + + -DskipITs + false + @{project.version} + + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.12.1 + + + validate + validate + + checkstyle.xml + UTF-8 + true + true + false + + + check + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.7 + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + Max + Low + true + ${project.build.directory}/findbugs + + ${project.basedir}/findbugs-exclude.xml + + + + verify + + check + + + + + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + Max + false + Low + true + ${project.build.directory}/findbugs + + ${project.basedir}/findbugs-exclude.xml + + + + + diff --git a/push-release.sh b/push-release.sh new file mode 100755 index 000000000..6c0403cf7 --- /dev/null +++ b/push-release.sh @@ -0,0 +1,4 @@ +#!/bin/bash -e +git push origin master +git push origin release +git push origin --tags diff --git a/release.sh b/release.sh new file mode 100755 index 000000000..e1512b143 --- /dev/null +++ b/release.sh @@ -0,0 +1,18 @@ +#!/bin/bash -e + +if [ `git symbolic-ref HEAD 2>/dev/null` != "refs/heads/master" ] +then + echo "This is not the master branch." + exit 1 +fi + +# Use the maven release plugin to update the pom versions and tag the release commit +mvn -B release:prepare release:clean + +# Fast-forward release branch to the tagged release commit +git checkout release +git merge --ff-only `git describe --abbrev=0 master` +git checkout master + +echo "Created release tag" `git describe --abbrev=0 master` +echo "Remember to: ./push-release.sh" diff --git a/src/main/java/com/spotify/docker/ContainerNotFoundException.java b/src/main/java/com/spotify/docker/ContainerNotFoundException.java new file mode 100644 index 000000000..1f913cf92 --- /dev/null +++ b/src/main/java/com/spotify/docker/ContainerNotFoundException.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +public class ContainerNotFoundException extends DockerException { + + private final String containerId; + + public ContainerNotFoundException(final String containerId, final Throwable cause) { + super("Container not found: " + containerId, cause); + this.containerId = containerId; + } + + public ContainerNotFoundException(final String containerId) { + this(containerId, null); + } + + public String getContainerId() { + return containerId; + } +} diff --git a/src/main/java/com/spotify/docker/DefaultDockerClient.java b/src/main/java/com/spotify/docker/DefaultDockerClient.java new file mode 100644 index 000000000..234ffcd8a --- /dev/null +++ b/src/main/java/com/spotify/docker/DefaultDockerClient.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.CharStreams; + +import com.spotify.docker.messages.Container; +import com.spotify.docker.messages.ContainerConfig; +import com.spotify.docker.messages.ContainerCreation; +import com.spotify.docker.messages.ContainerExit; +import com.spotify.docker.messages.ContainerInfo; +import com.spotify.docker.messages.HostConfig; +import com.spotify.docker.messages.ImageInfo; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.UniformInterface; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.core.util.MultivaluedMapImpl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.InterruptedIOException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.ws.rs.core.MultivaluedMap; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sun.jersey.api.client.config.ClientConfig.PROPERTY_READ_TIMEOUT; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; +import static javax.ws.rs.HttpMethod.DELETE; +import static javax.ws.rs.HttpMethod.GET; +import static javax.ws.rs.HttpMethod.POST; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; + +public class DefaultDockerClient implements DockerClient { + + private static final long CONNECT_TIMEOUT_MILLIS = SECONDS.toMillis(5); + private static final long READ_TIMEOUT_MILLIS = SECONDS.toMillis(30); + + private static final Logger log = LoggerFactory.getLogger(DefaultDockerClient.class); + + private static final String VERSION = "v1.11"; + private static final DefaultClientConfig CLIENT_CONFIG = new DefaultClientConfig( + ObjectMapperProvider.class, + LogsResponseReader.class, + PullResponseReader.class); + + private static final Pattern CONTAINER_NAME_PATTERN = Pattern.compile("/?[a-zA-Z0-9_-]+"); + + private static final GenericType> CONTAINER_LIST = + new GenericType>() {}; + + private final Client client; + private final URI uri; + + public DefaultDockerClient(final String uri) { + this(URI.create(uri)); + } + + public DefaultDockerClient(final URI uri) { + this.uri = uri; + this.client = Client.create(CLIENT_CONFIG); + this.client.setConnectTimeout((int) CONNECT_TIMEOUT_MILLIS); + this.client.setReadTimeout((int) READ_TIMEOUT_MILLIS); + } + + @Override + public List listContainers(final ListContainersParam... params) + throws DockerException, InterruptedException { + final Multimap paramMap = ArrayListMultimap.create(); + for (ListContainersParam param : params) { + paramMap.put(param.name(), param.value()); + } + final WebResource resource = resource() + .path("containers").path("json") + .queryParams(multivaluedMap(paramMap)); + return request(GET, CONTAINER_LIST, resource, resource.accept(APPLICATION_JSON_TYPE)); + } + + @Override + public ContainerCreation createContainer(final ContainerConfig config) + throws DockerException, InterruptedException { + return createContainer(config, null); + } + + public static interface ExceptionPropagator { + + void propagate(UniformInterfaceException e) throws DockerException; + } + + @Override + public ContainerCreation createContainer(final ContainerConfig config, + final String name) + throws DockerException, InterruptedException { + + final MultivaluedMap params = new MultivaluedMapImpl(); + if (name != null) { + checkArgument(CONTAINER_NAME_PATTERN.matcher(name).matches(), + "Invalid container name: \"%s\"", name); + params.add("name", name); + } + + try { + final WebResource resource = resource() + .path("containers").path("create") + .queryParams(params); + return request(POST, ContainerCreation.class, resource, resource + .entity(config) + .type(APPLICATION_JSON_TYPE) + .accept(APPLICATION_JSON_TYPE)); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ImageNotFoundException(config.image(), e); + default: + throw e; + } + } + } + + @Override + public void startContainer(final String containerId) + throws DockerException, InterruptedException { + startContainer(containerId, HostConfig.builder().build()); + } + + @Override + public void startContainer(final String containerId, final HostConfig hostConfig) + throws DockerException, InterruptedException { + checkNotNull(containerId, "containerId"); + checkNotNull(hostConfig, "hostConfig"); + try { + final WebResource resource = resource() + .path("containers").path(containerId).path("start"); + request(POST, resource, resource + .type(APPLICATION_JSON_TYPE) + .accept(APPLICATION_JSON_TYPE) + .entity(hostConfig)); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ContainerNotFoundException(containerId, e); + default: + throw e; + } + } + } + + @Override + public void killContainer(final String containerId) throws DockerException, InterruptedException { + try { + final WebResource resource = resource().path("containers").path(containerId).path("kill"); + request(POST, resource, resource); + } catch (UniformInterfaceException e) { + switch (e.getResponse().getStatus()) { + case 404: + throw new ContainerNotFoundException(containerId, e); + default: + throw new DockerException(e); + } + } + } + + @Override + public ContainerExit waitContainer(final String containerId) + throws DockerException, InterruptedException { + try { + final WebResource resource = resource() + .path("containers").path(containerId).path("wait"); + // Wait forever + resource.setProperty(PROPERTY_READ_TIMEOUT, 0); + return request(POST, ContainerExit.class, resource, resource.accept(APPLICATION_JSON_TYPE)); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ContainerNotFoundException(containerId, e); + default: + throw e; + } + } + } + + @Override + public void removeContainer(final String containerId) + throws DockerException, InterruptedException { + removeContainer(containerId, false); + } + + @Override + public void removeContainer(final String containerId, final boolean removeVolumes) + throws DockerException, InterruptedException { + try { + final WebResource resource = resource() + .path("containers").path(containerId); + request(DELETE, resource, resource + .queryParam("v", String.valueOf(removeVolumes)) + .accept(APPLICATION_JSON_TYPE)); + } catch (UniformInterfaceException e) { + switch (e.getResponse().getStatus()) { + case 404: + throw new ContainerNotFoundException(containerId); + default: + throw new DockerException(e); + } + } + } + + @Override + public ContainerInfo inspectContainer(final String containerId) + throws DockerException, InterruptedException { + try { + final WebResource resource = resource().path("containers").path(containerId).path("json"); + return request(GET, ContainerInfo.class, resource, resource.accept(APPLICATION_JSON_TYPE)); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ContainerNotFoundException(containerId, e); + default: + throw e; + } + } + } + + @Override + public void pull(final String image) throws DockerException, InterruptedException { + final ImageRef imageRef = new ImageRef(image); + + final MultivaluedMap params = new MultivaluedMapImpl(); + params.add("fromImage", imageRef.getImage()); + if (imageRef.getTag() != null) { + params.add("tag", imageRef.getTag()); + } + + final WebResource resource = resource().path("images").path("create").queryParams(params); + + try (ImagePull pull = request(POST, ImagePull.class, resource, + resource.accept(APPLICATION_OCTET_STREAM_TYPE))) { + pull.tail(image); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ImageNotFoundException(image, e); + default: + throw e; + } + } + } + + @Override + public ImageInfo inspectImage(final String image) throws DockerException, InterruptedException { + try { + final WebResource resource = resource().path("images").path(image).path("json"); + return request(GET, ImageInfo.class, resource, resource.accept(APPLICATION_JSON_TYPE)); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ImageNotFoundException(image, e); + default: + throw e; + } + } + } + + @Override + public LogStream logs(final String containerId, final LogsParameter... params) + throws DockerException, InterruptedException { + final Multimap paramMap = ArrayListMultimap.create(); + for (final LogsParameter param : params) { + paramMap.put(param.name().toLowerCase(Locale.ROOT), String.valueOf(true)); + } + final WebResource resource = resource() + .path("containers").path(containerId).path("logs") + .queryParams(multivaluedMap(paramMap)); + try { + return request(GET, LogStream.class, resource, + resource.accept("application/vnd.docker.raw-stream")); + } catch (DockerRequestException e) { + switch (e.status()) { + case 404: + throw new ContainerNotFoundException(containerId); + default: + throw e; + } + } + } + + private WebResource resource() { + return client.resource(uri).path(VERSION); + } + + private T request(final String method, final GenericType type, + final WebResource resource, final WebResource.Builder request) + throws DockerException, InterruptedException { + try { + return request.method(method, type); + } catch (ClientHandlerException e) { + throw propagate(method, resource, e); + } catch (UniformInterfaceException e) { + throw propagate(method, resource, e); + } + } + + private T request(final String method, final Class clazz, + final WebResource resource, final UniformInterface request) + throws DockerException, InterruptedException { + try { + return request.method(method, clazz); + } catch (ClientHandlerException e) { + throw propagate(method, resource, e); + } catch (UniformInterfaceException e) { + throw propagate(method, resource, e); + } + } + + private void request(final String method, + final WebResource resource, + final UniformInterface request) throws DockerException, + InterruptedException { + try { + request.method(method); + } catch (ClientHandlerException e) { + throw propagate(method, resource, e); + } catch (UniformInterfaceException e) { + throw propagate(method, resource, e); + } + } + + private DockerRequestException propagate(final String method, final WebResource resource, + final UniformInterfaceException e) { + return new DockerRequestException(method, resource.getURI(), + e.getResponse().getStatus(), message(e.getResponse()), + e); + } + + private RuntimeException propagate(final String method, final WebResource resource, + final ClientHandlerException e) + throws DockerException, InterruptedException { + final Throwable cause = e.getCause(); + if (cause instanceof SocketTimeoutException) { + throw new DockerTimeoutException(method, resource.getURI(), e); + } else if (cause instanceof InterruptedIOException) { + throw new InterruptedException("Interrupted: " + method + " " + resource); + } else { + throw new DockerException(e); + } + } + + private String message(final ClientResponse response) { + final Readable reader = new InputStreamReader(response.getEntityInputStream(), UTF_8); + try { + return CharStreams.toString(reader); + } catch (IOException ignore) { + return null; + } + } + + private MultivaluedMap multivaluedMap(final Multimap map) { + final MultivaluedMap multivaluedMap = new MultivaluedMapImpl(); + for (Map.Entry e : map.entries()) { + final String value = e.getValue(); + if (value != null) { + multivaluedMap.add(e.getKey(), value); + } + } + return multivaluedMap; + } + +} diff --git a/src/main/java/com/spotify/docker/DockerClient.java b/src/main/java/com/spotify/docker/DockerClient.java new file mode 100644 index 000000000..abece88b1 --- /dev/null +++ b/src/main/java/com/spotify/docker/DockerClient.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.spotify.docker.messages.Container; +import com.spotify.docker.messages.ContainerConfig; +import com.spotify.docker.messages.ContainerCreation; +import com.spotify.docker.messages.ContainerExit; +import com.spotify.docker.messages.ContainerInfo; +import com.spotify.docker.messages.HostConfig; +import com.spotify.docker.messages.ImageInfo; + +import java.util.List; + +/** + * A client for interacting with dockerd. + * + * Note: All methods throw DockerException on unexpected docker response status codes. + */ +public interface DockerClient { + + /** + * List docker containers. + * + * @param params Container listing and filtering options. + * @return A list of containers. + */ + List listContainers(ListContainersParam... params) + throws DockerException, InterruptedException; + + /** + * Inspect a docker container. + * + * @param containerId The id of the container to inspect. + * @return Info about the container. + * @throws ContainerNotFoundException if the container was not found (404). + */ + ContainerInfo inspectContainer(String containerId) + throws DockerException, InterruptedException; + + /** + * Inspect a docker container image. + * + * @param image The image to inspect. + * @return Info about the image. + * @throws ImageNotFoundException if the image was not found (404). + */ + ImageInfo inspectImage(String image) + throws DockerException, InterruptedException; + + /** + * Pull a docker container image. + * + * @param image The image to pull. + */ + void pull(String image) + throws DockerException, InterruptedException; + + /** + * Create a docker container. + * + * @param config The container configuration. + * @return Container creation result with container id and eventual warnings from docker. + * @throws ImageNotFoundException if the image was not found (404). + */ + ContainerCreation createContainer(ContainerConfig config) + throws DockerException, InterruptedException; + + /** + * Create a docker container. + * + * @param config The container configuration. + * @param name The container name. + * @return Container creation result with container id and eventual warnings from docker. + * @throws ImageNotFoundException if the image was not found (404). + */ + ContainerCreation createContainer(ContainerConfig config, String name) + throws DockerException, InterruptedException; + + /** + * Start a docker container. + * + * @param containerId The id of the container to start. + * @throws ContainerNotFoundException if the container was not found (404). + */ + void startContainer(String containerId) + throws DockerException, InterruptedException; + + /** + * Start a docker container. + * + * @param containerId The id of the container to start. + * @param hostConfig The docker host configuration to use when starting the container. + * @throws ContainerNotFoundException if the container was not found (404). + */ + void startContainer(String containerId, HostConfig hostConfig) + throws DockerException, InterruptedException; + + /** + * Wait for a docker container to exit. + * + * @param containerId The id of the container to wait for. + * @return Exit response with status code. + * @throws ContainerNotFoundException if the container was not found (404). + */ + ContainerExit waitContainer(String containerId) + throws DockerException, InterruptedException; + + /** + * Kill a docker container. + * + * @param containerId The id of the container to kill. + * @throws ContainerNotFoundException if the container was not found (404). + */ + void killContainer(String containerId) + throws DockerException, InterruptedException; + + /** + * Remove a docker container. + * + * @param containerId The id of the container to remove. + * @throws ContainerNotFoundException if the container was not found (404). + */ + void removeContainer(String containerId) + throws DockerException, InterruptedException; + + /** + * Remove a docker container. + * + * @param containerId The id of the container to remove. + * @param removeVolumes Whether to remove volumes as well. + * @throws ContainerNotFoundException if the container was not found (404). + */ + void removeContainer(String containerId, boolean removeVolumes) + throws DockerException, InterruptedException; + + /** + * Get docker container logs. + * + * @param containerId The id of the container to get logs for. + * @param params Params for controlling what streams to get and whether to tail or not. + * @return A log message stream. + * @throws ContainerNotFoundException if the container was not found (404). + */ + LogStream logs(String containerId, LogsParameter... params) + throws DockerException, InterruptedException; + + /** + * Parameters for {@link #logs(String, LogsParameter...)} + */ + public static enum LogsParameter { + FOLLOW, + STDOUT, + STDERR, + TIMESTAMPS, + } + + /** + * Parameters for {@link #listContainers(ListContainersParam...)} + */ + public static class ListContainersParam { + + private final String name; + private final String value; + + public ListContainersParam(final String name, final String value) { + this.name = name; + this.value = value; + } + + /** + * Parameter name. + */ + public String name() { + return name; + } + + /** + * Parameter value. + */ + public String value() { + return value; + } + + /** + * Show all containers. Only running containers are shown by default + */ + public static ListContainersParam allContainers() { + return allContainers(true); + } + + /** + * Show all containers. Only running containers are shown by default + */ + public static ListContainersParam allContainers(final boolean all) { + return create("all", String.valueOf(all)); + } + + /** + * Show limit last created containers, include non-running ones. + */ + public static ListContainersParam limitContainers(final Integer limit) { + return create("limit", String.valueOf(limit)); + } + + /** + * Show only containers created since id, include non-running ones. + */ + public static ListContainersParam containersCreatedSince(final String id) { + return create("since", String.valueOf(id)); + } + + /** + * Show only containers created before id, include non-running ones. + */ + public static ListContainersParam containersCreatedBefore(final String id) { + return create("before", String.valueOf(id)); + } + + /** + * Show the containers sizes. + */ + public static ListContainersParam withContainerSizes(final Boolean size) { + return create("size", String.valueOf(size)); + } + + /** + * Create a custom parameter. + */ + public static ListContainersParam create(final String name, final String value) { + return new ListContainersParam(name, value); + } + } +} diff --git a/src/main/java/com/spotify/docker/DockerException.java b/src/main/java/com/spotify/docker/DockerException.java new file mode 100644 index 000000000..f8d7f55ab --- /dev/null +++ b/src/main/java/com/spotify/docker/DockerException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +public class DockerException extends Exception { + + public DockerException(final String message) { + super(message); + } + + public DockerException(final String message, final Throwable cause) { + super(message, cause); + } + + public DockerException(final Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/spotify/docker/DockerRequestException.java b/src/main/java/com/spotify/docker/DockerRequestException.java new file mode 100644 index 000000000..e97d1305d --- /dev/null +++ b/src/main/java/com/spotify/docker/DockerRequestException.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import java.net.URI; + +public class DockerRequestException extends DockerException { + + private final String method; + private final URI uri; + private final int status; + private final String message; + + public DockerRequestException(final String method, final URI uri, final Throwable cause) { + this(method, uri, 0, null, cause); + } + + public DockerRequestException(final String method, final URI uri, + final int status, + final Throwable cause) { + this(method, uri, status, null, cause); + } + + public DockerRequestException(final String method, final URI uri) { + this(method, uri, 0, null, null); + } + + public DockerRequestException(final String method, final URI uri, + final int status) { + this(method, uri, status, null, null); + } + + public DockerRequestException(final String method, final URI uri, + final int status, final String message) { + this(method, uri, status, message, null); + } + + public DockerRequestException(final String method, final URI uri, + final int status, final String message, + final Throwable cause) { + super("Request error: " + method + " " + uri + ": " + status, cause); + this.method = method; + this.uri = uri; + this.status = status; + this.message = message; + } + + public String method() { + return method; + } + + public URI uri() { + return uri; + } + + public int status() { + return status; + } + + public String message() { + return message; + } +} diff --git a/src/main/java/com/spotify/docker/DockerTimeoutException.java b/src/main/java/com/spotify/docker/DockerTimeoutException.java new file mode 100644 index 000000000..65abfdb28 --- /dev/null +++ b/src/main/java/com/spotify/docker/DockerTimeoutException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import java.net.URI; + +public class DockerTimeoutException extends DockerException { + + private final String method; + private final URI uri; + + public DockerTimeoutException(final String method, final URI uri, final Throwable cause) { + super("Timeout: " + method + " " + uri, cause); + this.method = method; + this.uri = uri; + } + + public String method() { + return method; + } + + public URI uri() { + return uri; + } +} diff --git a/src/main/java/com/spotify/docker/ImageNotFoundException.java b/src/main/java/com/spotify/docker/ImageNotFoundException.java new file mode 100644 index 000000000..c3a3355e1 --- /dev/null +++ b/src/main/java/com/spotify/docker/ImageNotFoundException.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +public class ImageNotFoundException extends DockerException { + + private final String image; + + public ImageNotFoundException(final String image, final Throwable cause) { + super("Image not found: " + image, cause); + this.image = image; + } + + public ImageNotFoundException(final String image, final String message) { + super("Image not found: " + image + ": " + message); + this.image = image; + } + + public ImageNotFoundException(final String image) { + super("Image not found: " + image); + this.image = image; + } + + public String getImage() { + return image; + } +} diff --git a/src/main/java/com/spotify/docker/ImagePull.java b/src/main/java/com/spotify/docker/ImagePull.java new file mode 100644 index 000000000..33b208dc5 --- /dev/null +++ b/src/main/java/com/spotify/docker/ImagePull.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.google.common.base.Throwables; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MappingIterator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +import static com.spotify.docker.ObjectMapperProvider.objectMapper; + +class ImagePull implements Closeable { + + private static final Logger log = LoggerFactory.getLogger(ImagePull.class); + private final InputStream stream; + + private volatile boolean closed; + + ImagePull(final InputStream stream) { + this.stream = stream; + } + + void tail(final String image) + throws DockerException { + try { + final JsonParser parser = objectMapper().getFactory().createParser(stream); + final MappingIterator iterator = objectMapper().readValues(parser, JsonNode.class); + while (iterator.hasNextValue()) { + final JsonNode message; + message = iterator.nextValue(); + final JsonNode error = message.get("error"); + if (error != null) { + if (error.toString().contains("404")) { + throw new ImageNotFoundException(image, message.toString()); + } else { + throw new ImagePullFailedException(image, message.toString()); + } + } + log.info("pull {}: {}", image, message); + } + } catch (IOException e) { + throw new DockerException(e); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (!closed) { + log.warn(this + " not closed properly"); + close(); + } + } + + @Override + public void close() { + closed = true; + try { + stream.close(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } +} diff --git a/src/main/java/com/spotify/docker/ImagePullFailedException.java b/src/main/java/com/spotify/docker/ImagePullFailedException.java new file mode 100644 index 000000000..12c0c2b28 --- /dev/null +++ b/src/main/java/com/spotify/docker/ImagePullFailedException.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +public class ImagePullFailedException extends DockerException { + + private final String image; + + public ImagePullFailedException(final String image, final Throwable cause) { + super("Image pull failed: " + image, cause); + this.image = image; + } + + public ImagePullFailedException(final String image, final String message) { + super("Image pull failed: " + image + ": " + message); + this.image = image; + } + + public String getImage() { + return image; + } +} diff --git a/src/main/java/com/spotify/docker/ImageRef.java b/src/main/java/com/spotify/docker/ImageRef.java new file mode 100644 index 000000000..d1472e572 --- /dev/null +++ b/src/main/java/com/spotify/docker/ImageRef.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +public class ImageRef { + + private final String image; + private final String tag; + + public ImageRef(final String image) { + final int lastColon = image.lastIndexOf(':'); + if (lastColon < 0) { + this.image = image; + this.tag = null; + } else { + final String tag = image.substring(lastColon + 1); + if (tag.indexOf('/') < 0) { + this.image = image.substring(0, lastColon); + this.tag = tag; + } else { + this.image = image; + this.tag = null; + } + } + } + + public String getImage() { + return image; + } + + public String getTag() { + return tag; + } + + @Override + public String toString() { + return tag == null ? image : image + ':' + tag; + } +} diff --git a/src/main/java/com/spotify/docker/LogMessage.java b/src/main/java/com/spotify/docker/LogMessage.java new file mode 100644 index 000000000..3268dce88 --- /dev/null +++ b/src/main/java/com/spotify/docker/LogMessage.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import java.nio.ByteBuffer; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class LogMessage { + + final Stream stream; + final ByteBuffer content; + + public LogMessage(final int streamId, final ByteBuffer content) { + this(Stream.of(streamId), content); + } + + public LogMessage(final Stream stream, final ByteBuffer content) { + this.stream = checkNotNull(stream, "stream"); + this.content = checkNotNull(content, "content"); + } + + public Stream stream() { + return stream; + } + + public ByteBuffer content() { + return content.asReadOnlyBuffer(); + } + + public enum Stream { + STDIN(0), + STDOUT(1), + STDERR(2); + + private final int id; + + Stream(int id) { + this.id = id; + } + + public int id() { + return id; + } + + public static Stream of(final int id) { + switch (id) { + case 0: + return STDIN; + case 1: + return STDOUT; + case 2: + return STDERR; + default: + throw new IllegalArgumentException(); + } + } + } +} diff --git a/src/main/java/com/spotify/docker/LogReader.java b/src/main/java/com/spotify/docker/LogReader.java new file mode 100644 index 000000000..1612b05cd --- /dev/null +++ b/src/main/java/com/spotify/docker/LogReader.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.google.common.io.ByteStreams; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +public class LogReader implements Closeable { + + private final InputStream stream; + public static final int HEADER_SIZE = 8; + public static final int FRAME_SIZE_OFFSET = 4; + + public LogReader(final InputStream stream) { + this.stream = stream; + } + + @Override + public void close() throws IOException { + stream.close(); + } + + public LogMessage nextMessage() throws IOException { + + // Read header + final byte[] headerBytes = new byte[HEADER_SIZE]; + final int n = ByteStreams.read(stream, headerBytes, 0, HEADER_SIZE); + if (n == 0) { + return null; + } + if (n != HEADER_SIZE) { + throw new EOFException(); + } + final ByteBuffer header = ByteBuffer.wrap(headerBytes); + final int streamId = header.get(); + header.position(FRAME_SIZE_OFFSET); + final int frameSize = header.getInt(); + + // Read frame + final byte[] frame = new byte[frameSize]; + ByteStreams.readFully(stream, frame); + return new LogMessage(streamId, ByteBuffer.wrap(frame)); + } +} diff --git a/src/main/java/com/spotify/docker/LogStream.java b/src/main/java/com/spotify/docker/LogStream.java new file mode 100644 index 000000000..cbf92db99 --- /dev/null +++ b/src/main/java/com/spotify/docker/LogStream.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.google.common.base.Throwables; +import com.google.common.collect.AbstractIterator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +import static com.google.common.base.Charsets.UTF_8; + +public class LogStream extends AbstractIterator implements Closeable { + + private static final Logger log = LoggerFactory.getLogger(LogStream.class); + + private final LogReader reader; + private volatile boolean closed; + + LogStream(final InputStream stream) { + this.reader = new LogReader(stream); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (!closed) { + log.warn(this + " not closed properly"); + close(); + } + } + + @Override + protected LogMessage computeNext() { + final LogMessage message; + try { + message = reader.nextMessage(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + if (message == null) { + return endOfData(); + } + return message; + } + + @Override + public void close() { + closed = true; + try { + reader.close(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public String readFully() { + StringBuilder stringBuilder = new StringBuilder(); + while (hasNext()) { + stringBuilder.append(UTF_8.decode(next().content())); + } + return stringBuilder.toString(); + } +} diff --git a/src/main/java/com/spotify/docker/LogsResponseReader.java b/src/main/java/com/spotify/docker/LogsResponseReader.java new file mode 100644 index 000000000..060239bc5 --- /dev/null +++ b/src/main/java/com/spotify/docker/LogsResponseReader.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; + +public class LogsResponseReader implements MessageBodyReader { + + @Override + public boolean isReadable(final Class type, final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) { + return type == LogStream.class; + } + + @Override + public LogStream readFrom(final Class type, final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap httpHeaders, + final InputStream entityStream) + throws IOException, WebApplicationException { + return new LogStream(entityStream); + } +} diff --git a/src/main/java/com/spotify/docker/ObjectMapperProvider.java b/src/main/java/com/spotify/docker/ObjectMapperProvider.java new file mode 100644 index 000000000..310a6dc6f --- /dev/null +++ b/src/main/java/com/spotify/docker/ObjectMapperProvider.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.guava.GuavaModule; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; + +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class ObjectMapperProvider implements ContextResolver { + + private static final Function VOID_VALUE = + new Function() { + @Override + public Object apply(final Object input) { + return null; + } + }; + + private static final SimpleModule MODULE = new SimpleModule(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + static { + MODULE.addSerializer(Set.class, new SetSerializer()); + MODULE.addDeserializer(Set.class, new SetDeserializer()); + MODULE.addSerializer(ImmutableSet.class, new ImmutableSetSerializer()); + MODULE.addDeserializer(ImmutableSet.class, new ImmutableSetDeserializer()); + OBJECT_MAPPER.registerModule(new GuavaModule()); + OBJECT_MAPPER.registerModule(MODULE); + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + @Override + public ObjectMapper getContext(Class type) { + return OBJECT_MAPPER; + } + + static ObjectMapper objectMapper() { + return OBJECT_MAPPER; + } + + private static class SetSerializer extends JsonSerializer { + + @Override + public void serialize(final Set value, final JsonGenerator jgen, + final SerializerProvider provider) + throws IOException { + final Map map = (value == null) ? null : Maps.asMap(value, VOID_VALUE); + OBJECT_MAPPER.writeValue(jgen, map); + } + } + + private static class SetDeserializer extends JsonDeserializer { + + @Override + public Set deserialize(final JsonParser jp, final DeserializationContext ctxt) + throws IOException { + final Map map = OBJECT_MAPPER.readValue(jp, Map.class); + return (map == null) ? null : map.keySet(); + } + } + + private static class ImmutableSetSerializer extends JsonSerializer { + + @Override + public void serialize(final ImmutableSet value, final JsonGenerator jgen, + final SerializerProvider provider) + throws IOException { + final Map map = (value == null) ? null : Maps.asMap(value, VOID_VALUE); + OBJECT_MAPPER.writeValue(jgen, map); + } + } + + private static class ImmutableSetDeserializer extends JsonDeserializer { + + @Override + public ImmutableSet deserialize(final JsonParser jp, final DeserializationContext ctxt) + throws IOException { + final Map map = OBJECT_MAPPER.readValue(jp, Map.class); + return (map == null) ? null : ImmutableSet.copyOf(map.keySet()); + } + } +} diff --git a/src/main/java/com/spotify/docker/PullResponseReader.java b/src/main/java/com/spotify/docker/PullResponseReader.java new file mode 100644 index 000000000..a197b3616 --- /dev/null +++ b/src/main/java/com/spotify/docker/PullResponseReader.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; + +public class PullResponseReader implements MessageBodyReader { + + @Override + public boolean isReadable(final Class type, final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) { + return type == ImagePull.class; + } + + @Override + public ImagePull readFrom(final Class type, final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap httpHeaders, + final InputStream entityStream) + throws IOException, WebApplicationException { + return new ImagePull(entityStream); + } +} diff --git a/src/main/java/com/spotify/docker/messages/Container.java b/src/main/java/com/spotify/docker/messages/Container.java new file mode 100644 index 000000000..cc3c6607d --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/Container.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class Container { + + @JsonProperty("Id") private String id; + @JsonProperty("Names") private ImmutableList names; + @JsonProperty("Image") private String image; + @JsonProperty("Command") private String command; + @JsonProperty("Created") private Long created; + @JsonProperty("Status") private String status; + @JsonProperty("Ports") private ImmutableList ports; + @JsonProperty("SizeRw") private Long sizeRw; + @JsonProperty("SizeRootFs") private Long sizeRootFs; + + public String id() { + return id; + } + + public List names() { + return names; + } + + public String image() { + return image; + } + + public String command() { + return command; + } + + public Long created() { + return created; + } + + public String status() { + return status; + } + + public List ports() { + return ports; + } + + public Long sizeRw() { + return sizeRw; + } + + public Long sizeRootFs() { + return sizeRootFs; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Container container = (Container) o; + + if (command != null ? !command.equals(container.command) : container.command != null) { + return false; + } + if (created != null ? !created.equals(container.created) : container.created != null) { + return false; + } + if (id != null ? !id.equals(container.id) : container.id != null) { + return false; + } + if (image != null ? !image.equals(container.image) : container.image != null) { + return false; + } + if (names != null ? !names.equals(container.names) : container.names != null) { + return false; + } + if (ports != null ? !ports.equals(container.ports) : container.ports != null) { + return false; + } + if (sizeRootFs != null ? !sizeRootFs.equals(container.sizeRootFs) + : container.sizeRootFs != null) { + return false; + } + if (sizeRw != null ? !sizeRw.equals(container.sizeRw) : container.sizeRw != null) { + return false; + } + if (status != null ? !status.equals(container.status) : container.status != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (names != null ? names.hashCode() : 0); + result = 31 * result + (image != null ? image.hashCode() : 0); + result = 31 * result + (command != null ? command.hashCode() : 0); + result = 31 * result + (created != null ? created.hashCode() : 0); + result = 31 * result + (status != null ? status.hashCode() : 0); + result = 31 * result + (ports != null ? ports.hashCode() : 0); + result = 31 * result + (sizeRw != null ? sizeRw.hashCode() : 0); + result = 31 * result + (sizeRootFs != null ? sizeRootFs.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("id", id) + .add("image", image) + .add("command", command) + .add("created", created) + .add("status", status) + .add("ports", ports) + .add("sizeRw", sizeRw) + .add("sizeRootFs", sizeRootFs) + .toString(); + } + + public static class PortMapping { + + @JsonProperty("PrivatePort") private int privatePort; + @JsonProperty("PublicPort") private int publicPort; + @JsonProperty("Type") private String type; + @JsonProperty("IP") private String ip; + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final PortMapping that = (PortMapping) o; + + if (privatePort != that.privatePort) { + return false; + } + if (publicPort != that.publicPort) { + return false; + } + if (ip != null ? !ip.equals(that.ip) : that.ip != null) { + return false; + } + if (type != null ? !type.equals(that.type) : that.type != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = privatePort; + result = 31 * result + publicPort; + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (ip != null ? ip.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("privatePort", privatePort) + .add("publicPort", publicPort) + .add("type", type) + .add("ip", ip) + .toString(); + } + } +} diff --git a/src/main/java/com/spotify/docker/messages/ContainerConfig.java b/src/main/java/com/spotify/docker/messages/ContainerConfig.java new file mode 100644 index 000000000..53d3e2a0c --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/ContainerConfig.java @@ -0,0 +1,640 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Set; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class ContainerConfig { + + @JsonProperty("Hostname") private String hostname; + @JsonProperty("Domainname") private String domainname; + @JsonProperty("User") private String user; + @JsonProperty("Memory") private Long memory; + @JsonProperty("MemorySwap") private Long memorySwap; + @JsonProperty("CpuShares") private Long cpuShares; + @JsonProperty("Cpuset") private String cpuset; + @JsonProperty("AttachStdin") private Boolean attachStdin; + @JsonProperty("AttachStdout") private Boolean attachStdout; + @JsonProperty("AttachStderr") private Boolean attachStderr; + @JsonProperty("PortSpecs") private ImmutableList portSpecs; + @JsonProperty("ExposedPorts") private ImmutableSet exposedPorts; + @JsonProperty("Tty") private Boolean tty; + @JsonProperty("OpenStdin") private Boolean openStdin; + @JsonProperty("StdinOnce") private Boolean stdinOnce; + @JsonProperty("Env") private ImmutableList env; + @JsonProperty("Cmd") private ImmutableList cmd; + @JsonProperty("Image") private String image; + @JsonProperty("Volumes") private ImmutableSet volumes; + @JsonProperty("WorkingDir") private String workingDir; + @JsonProperty("Entrypoint") private ImmutableList entrypoint; + @JsonProperty("NetworkDisabled") private Boolean networkDisabled; + @JsonProperty("OnBuild") private ImmutableList onBuild; + + private ContainerConfig() { + } + + private ContainerConfig(final Builder builder) { + this.hostname = builder.hostname; + this.domainname = builder.domainname; + this.user = builder.user; + this.memory = builder.memory; + this.memorySwap = builder.memorySwap; + this.cpuShares = builder.cpuShares; + this.cpuset = builder.cpuset; + this.attachStdin = builder.attachStdin; + this.attachStdout = builder.attachStdout; + this.attachStderr = builder.attachStderr; + this.portSpecs = builder.portSpecs; + this.exposedPorts = builder.exposedPorts; + this.tty = builder.tty; + this.openStdin = builder.openStdin; + this.stdinOnce = builder.stdinOnce; + this.env = builder.env; + this.cmd = builder.cmd; + this.image = builder.image; + this.volumes = builder.volumes; + this.workingDir = builder.workingDir; + this.entrypoint = builder.entrypoint; + this.networkDisabled = builder.networkDisabled; + this.onBuild = builder.onBuild; + } + + public String hostname() { + return hostname; + } + + public String domainname() { + return domainname; + } + + public String user() { + return user; + } + + public Long memory() { + return memory; + } + + public Long memorySwap() { + return memorySwap; + } + + public Long cpuShares() { + return cpuShares; + } + + public String cpuset() { + return cpuset; + } + + public Boolean attachStdin() { + return attachStdin; + } + + public Boolean attachStdout() { + return attachStdout; + } + + public Boolean attachStderr() { + return attachStderr; + } + + public List portSpecs() { + return portSpecs; + } + + public Set exposedPorts() { + return exposedPorts; + } + + public Boolean tty() { + return tty; + } + + public Boolean openStdin() { + return openStdin; + } + + public Boolean stdinOnce() { + return stdinOnce; + } + + public List env() { + return env; + } + + public List cmd() { + return cmd; + } + + public String image() { + return image; + } + + public Set volumes() { + return volumes; + } + + public String workingDir() { + return workingDir; + } + + public List entrypoint() { + return entrypoint; + } + + public Boolean networkDisabled() { + return networkDisabled; + } + + public List onBuild() { + return onBuild; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ContainerConfig config = (ContainerConfig) o; + + if (attachStderr != null ? !attachStderr.equals(config.attachStderr) + : config.attachStderr != null) { + return false; + } + if (attachStdin != null ? !attachStdin.equals(config.attachStdin) + : config.attachStdin != null) { + return false; + } + if (attachStdout != null ? !attachStdout.equals(config.attachStdout) + : config.attachStdout != null) { + return false; + } + if (cmd != null ? !cmd.equals(config.cmd) : config.cmd != null) { + return false; + } + if (cpuShares != null ? !cpuShares.equals(config.cpuShares) : config.cpuShares != null) { + return false; + } + if (cpuset != null ? !cpuset.equals(config.cpuset) : config.cpuset != null) { + return false; + } + if (domainname != null ? !domainname.equals(config.domainname) : config.domainname != null) { + return false; + } + if (entrypoint != null ? !entrypoint.equals(config.entrypoint) : config.entrypoint != null) { + return false; + } + if (env != null ? !env.equals(config.env) : config.env != null) { + return false; + } + if (exposedPorts != null ? !exposedPorts.equals(config.exposedPorts) + : config.exposedPorts != null) { + return false; + } + if (hostname != null ? !hostname.equals(config.hostname) : config.hostname != null) { + return false; + } + if (image != null ? !image.equals(config.image) : config.image != null) { + return false; + } + if (memory != null ? !memory.equals(config.memory) : config.memory != null) { + return false; + } + if (memorySwap != null ? !memorySwap.equals(config.memorySwap) : config.memorySwap != null) { + return false; + } + if (networkDisabled != null ? !networkDisabled.equals(config.networkDisabled) + : config.networkDisabled != null) { + return false; + } + if (onBuild != null ? !onBuild.equals(config.onBuild) : config.onBuild != null) { + return false; + } + if (openStdin != null ? !openStdin.equals(config.openStdin) : config.openStdin != null) { + return false; + } + if (portSpecs != null ? !portSpecs.equals(config.portSpecs) : config.portSpecs != null) { + return false; + } + if (stdinOnce != null ? !stdinOnce.equals(config.stdinOnce) : config.stdinOnce != null) { + return false; + } + if (tty != null ? !tty.equals(config.tty) : config.tty != null) { + return false; + } + if (user != null ? !user.equals(config.user) : config.user != null) { + return false; + } + if (volumes != null ? !volumes.equals(config.volumes) : config.volumes != null) { + return false; + } + if (workingDir != null ? !workingDir.equals(config.workingDir) : config.workingDir != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = hostname != null ? hostname.hashCode() : 0; + result = 31 * result + (domainname != null ? domainname.hashCode() : 0); + result = 31 * result + (user != null ? user.hashCode() : 0); + result = 31 * result + (memory != null ? memory.hashCode() : 0); + result = 31 * result + (memorySwap != null ? memorySwap.hashCode() : 0); + result = 31 * result + (cpuShares != null ? cpuShares.hashCode() : 0); + result = 31 * result + (cpuset != null ? cpuset.hashCode() : 0); + result = 31 * result + (attachStdin != null ? attachStdin.hashCode() : 0); + result = 31 * result + (attachStdout != null ? attachStdout.hashCode() : 0); + result = 31 * result + (attachStderr != null ? attachStderr.hashCode() : 0); + result = 31 * result + (portSpecs != null ? portSpecs.hashCode() : 0); + result = 31 * result + (exposedPorts != null ? exposedPorts.hashCode() : 0); + result = 31 * result + (tty != null ? tty.hashCode() : 0); + result = 31 * result + (openStdin != null ? openStdin.hashCode() : 0); + result = 31 * result + (stdinOnce != null ? stdinOnce.hashCode() : 0); + result = 31 * result + (env != null ? env.hashCode() : 0); + result = 31 * result + (cmd != null ? cmd.hashCode() : 0); + result = 31 * result + (image != null ? image.hashCode() : 0); + result = 31 * result + (volumes != null ? volumes.hashCode() : 0); + result = 31 * result + (workingDir != null ? workingDir.hashCode() : 0); + result = 31 * result + (entrypoint != null ? entrypoint.hashCode() : 0); + result = 31 * result + (networkDisabled != null ? networkDisabled.hashCode() : 0); + result = 31 * result + (onBuild != null ? onBuild.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("hostname", hostname) + .add("domainname", domainname) + .add("user", user) + .add("memory", memory) + .add("memorySwap", memorySwap) + .add("cpuShares", cpuShares) + .add("cpuset", cpuset) + .add("attachStdin", attachStdin) + .add("attachStdout", attachStdout) + .add("attachStderr", attachStderr) + .add("portSpecs", portSpecs) + .add("exposedPorts", exposedPorts) + .add("tty", tty) + .add("openStdin", openStdin) + .add("stdinOnce", stdinOnce) + .add("env", env) + .add("cmd", cmd) + .add("image", image) + .add("volumes", volumes) + .add("workingDir", workingDir) + .add("entrypoint", entrypoint) + .add("networkDisabled", networkDisabled) + .add("onBuild", onBuild) + .toString(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String hostname; + private String domainname; + private String user; + private Long memory; + private Long memorySwap; + private Long cpuShares; + private String cpuset; + private Boolean attachStdin; + private Boolean attachStdout; + private Boolean attachStderr; + private ImmutableList portSpecs; + private ImmutableSet exposedPorts; + private Boolean tty; + private Boolean openStdin; + private Boolean stdinOnce; + private ImmutableList env; + private ImmutableList cmd; + private String image; + private ImmutableSet volumes; + private String workingDir; + private ImmutableList entrypoint; + private Boolean networkDisabled; + private ImmutableList onBuild; + + private Builder() { + } + + private Builder(final ContainerConfig config) { + this.hostname = config.hostname; + this.domainname = config.domainname; + this.user = config.user; + this.memory = config.memory; + this.memorySwap = config.memorySwap; + this.cpuShares = config.cpuShares; + this.cpuset = config.cpuset; + this.attachStdin = config.attachStdin; + this.attachStdout = config.attachStdout; + this.attachStderr = config.attachStderr; + this.portSpecs = config.portSpecs; + this.exposedPorts = config.exposedPorts; + this.tty = config.tty; + this.openStdin = config.openStdin; + this.stdinOnce = config.stdinOnce; + this.env = config.env; + this.cmd = config.cmd; + this.image = config.image; + this.volumes = config.volumes; + this.workingDir = config.workingDir; + this.entrypoint = config.entrypoint; + this.networkDisabled = config.networkDisabled; + this.onBuild = config.onBuild; + } + + public Builder hostname(final String hostname) { + this.hostname = hostname; + return this; + } + + public String hostname() { + return hostname; + } + + public Builder domainname(final String domainname) { + this.domainname = domainname; + return this; + } + + public String domainname() { + return domainname; + } + + public Builder user(final String user) { + this.user = user; + return this; + } + + public String user() { + return user; + } + + public Builder memory(final Long memory) { + this.memory = memory; + return this; + } + + public Long memory() { + return memory; + } + + public Builder memorySwap(final Long memorySwap) { + this.memorySwap = memorySwap; + return this; + } + + public Long memorySwap() { + return memorySwap; + } + + public Builder cpuShares(final Long cpuShares) { + this.cpuShares = cpuShares; + return this; + } + + public Long cpuShares() { + return cpuShares; + } + + public Builder cpuset(final String cpuset) { + this.cpuset = cpuset; + return this; + } + + public String cpuset() { + return cpuset; + } + + public Builder attachStdin(final Boolean attachStdin) { + this.attachStdin = attachStdin; + return this; + } + + public Boolean attachStdin() { + return attachStdin; + } + + public Builder attachStdout(final Boolean attachStdout) { + this.attachStdout = attachStdout; + return this; + } + + public Boolean attachStdout() { + return attachStdout; + } + + public Builder attachStderr(final Boolean attachStderr) { + this.attachStderr = attachStderr; + return this; + } + + public Boolean attachStderr() { + return attachStderr; + } + + public Builder portSpecs(final List portSpecs) { + this.portSpecs = ImmutableList.copyOf(portSpecs); + return this; + } + + public Builder portSpecs(final String... portSpecs) { + this.portSpecs = ImmutableList.copyOf(portSpecs); + return this; + } + + public List portSpecs() { + return portSpecs; + } + + public Builder exposedPorts(final Set exposedPorts) { + this.exposedPorts = ImmutableSet.copyOf(exposedPorts); + return this; + } + + public Builder exposedPorts(final String... exposedPorts) { + this.exposedPorts = ImmutableSet.copyOf(exposedPorts); + return this; + } + + public Set exposedPorts() { + return exposedPorts; + } + + public Builder tty(final Boolean tty) { + this.tty = tty; + return this; + } + + public Boolean tty() { + return tty; + } + + public Builder openStdin(final Boolean openStdin) { + this.openStdin = openStdin; + return this; + } + + public Boolean openStdin() { + return openStdin; + } + + public Builder stdinOnce(final Boolean stdinOnce) { + this.stdinOnce = stdinOnce; + return this; + } + + public Boolean stdinOnce() { + return stdinOnce; + } + + public Builder env(final List env) { + this.env = ImmutableList.copyOf(env); + return this; + } + + public Builder env(final String... env) { + this.env = ImmutableList.copyOf(env); + return this; + } + + public List env() { + return env; + } + + public Builder cmd(final List cmd) { + this.cmd = ImmutableList.copyOf(cmd); + return this; + } + + public Builder cmd(final String... cmd) { + this.cmd = ImmutableList.copyOf(cmd); + return this; + } + + public List cmd() { + return cmd; + } + + public Builder image(final String image) { + this.image = image; + return this; + } + + public String image() { + return image; + } + + public Builder volumes(final Set volumes) { + this.volumes = ImmutableSet.copyOf(volumes); + return this; + } + + public Builder volumes(final String... volumes) { + this.volumes = ImmutableSet.copyOf(volumes); + return this; + } + + public Set volumes() { + return volumes; + } + + public Builder workingDir(final String workingDir) { + this.workingDir = workingDir; + return this; + } + + public String workingDir() { + return workingDir; + } + + public Builder entrypoint(final List entrypoint) { + this.entrypoint = ImmutableList.copyOf(entrypoint); + return this; + } + + public Builder entrypoint(final String... entrypoint) { + this.entrypoint = ImmutableList.copyOf(entrypoint); + return this; + } + + public List entrypoint() { + return entrypoint; + } + + public Builder networkDisabled(final Boolean networkDisabled) { + this.networkDisabled = networkDisabled; + return this; + } + + public Boolean networkDisabled() { + return networkDisabled; + } + + public Builder onBuild(final List onBuild) { + this.onBuild = ImmutableList.copyOf(onBuild); + return this; + } + + public Builder onBuild(final String... onBuild) { + this.onBuild = ImmutableList.copyOf(onBuild); + return this; + } + + public List onBuild() { + return onBuild; + } + + public ContainerConfig build() { + return new ContainerConfig(this); + } + } +} diff --git a/src/main/java/com/spotify/docker/messages/ContainerCreation.java b/src/main/java/com/spotify/docker/messages/ContainerCreation.java new file mode 100644 index 000000000..cb6049e32 --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/ContainerCreation.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class ContainerCreation { + + @JsonProperty("Id") private String id; + @JsonProperty("Warnings") private ImmutableList warnings; + + public ContainerCreation() { + } + + public ContainerCreation(final String id) { + this.id = id; + } + + public String id() { + return id; + } + + public List getWarnings() { + return warnings; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ContainerCreation that = (ContainerCreation) o; + + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + if (warnings != null ? !warnings.equals(that.warnings) : that.warnings != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (warnings != null ? warnings.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("id", id) + .add("warnings", warnings) + .toString(); + } +} diff --git a/src/main/java/com/spotify/docker/messages/ContainerExit.java b/src/main/java/com/spotify/docker/messages/ContainerExit.java new file mode 100644 index 000000000..f21c663bb --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/ContainerExit.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class ContainerExit { + + @JsonProperty("StatusCode") private Integer statusCode; + + public ContainerExit() { + } + + public ContainerExit(final Integer statusCode) { + this.statusCode = statusCode; + } + + public Integer statusCode() { + return statusCode; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ContainerExit that = (ContainerExit) o; + + if (statusCode != null ? !statusCode.equals(that.statusCode) : that.statusCode != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return statusCode != null ? statusCode.hashCode() : 0; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("statusCode", statusCode) + .toString(); + } +} diff --git a/src/main/java/com/spotify/docker/messages/ContainerInfo.java b/src/main/java/com/spotify/docker/messages/ContainerInfo.java new file mode 100644 index 000000000..a2571df10 --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/ContainerInfo.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, setterVisibility = NONE, getterVisibility = NONE) +public class ContainerInfo { + + @JsonProperty("ID") private String id; + @JsonProperty("Created") private Date created; + @JsonProperty("Path") private String path; + @JsonProperty("Args") private ImmutableList args; + @JsonProperty("Config") private ContainerConfig config; + @JsonProperty("State") private ContainerState state; + @JsonProperty("Image") private String image; + @JsonProperty("NetworkSettings") private NetworkSettings networkSettings; + @JsonProperty("ResolvConfPath") private String resolvConfPath; + @JsonProperty("HostnamePath") private String hostnamePath; + @JsonProperty("HostsPath") private String hostsPath; + @JsonProperty("Name") private String name; + @JsonProperty("Driver") private String driver; + @JsonProperty("ExecDriver") private String execDriver; + @JsonProperty("ProcessLabel") private String processLabel; + @JsonProperty("MountLabel") private String mountLabel; + @JsonProperty("Volumes") private ImmutableMap volumes; + @JsonProperty("VolumesRW") private ImmutableMap volumesRW; + + public String id() { + return id; + } + + public Date created() { + return created == null ? null : new Date(created.getTime()); + } + + public String path() { + return path; + } + + public List args() { + return args; + } + + public ContainerConfig config() { + return config; + } + + public ContainerState state() { + return state; + } + + public String image() { + return image; + } + + public NetworkSettings networkSettings() { + return networkSettings; + } + + public String resolvConfPath() { + return resolvConfPath; + } + + public String hostnamePath() { + return hostnamePath; + } + + public String hostsPath() { + return hostsPath; + } + + public String name() { + return name; + } + + public String driver() { + return driver; + } + + public String execDriver() { + return execDriver; + } + + public String processLabel() { + return processLabel; + } + + public String mountLabel() { + return mountLabel; + } + + public Map volumes() { + return volumes; + } + + public Map volumesRW() { + return volumesRW; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ContainerInfo that = (ContainerInfo) o; + + if (args != null ? !args.equals(that.args) : that.args != null) { + return false; + } + if (config != null ? !config.equals(that.config) : that.config != null) { + return false; + } + if (created != null ? !created.equals(that.created) : that.created != null) { + return false; + } + if (driver != null ? !driver.equals(that.driver) : that.driver != null) { + return false; + } + if (execDriver != null ? !execDriver.equals(that.execDriver) : that.execDriver != null) { + return false; + } + if (hostnamePath != null ? !hostnamePath.equals(that.hostnamePath) + : that.hostnamePath != null) { + return false; + } + if (hostsPath != null ? !hostsPath.equals(that.hostsPath) : that.hostsPath != null) { + return false; + } + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + if (image != null ? !image.equals(that.image) : that.image != null) { + return false; + } + if (mountLabel != null ? !mountLabel.equals(that.mountLabel) : that.mountLabel != null) { + return false; + } + if (name != null ? !name.equals(that.name) : that.name != null) { + return false; + } + if (networkSettings != null ? !networkSettings.equals(that.networkSettings) + : that.networkSettings != null) { + return false; + } + if (path != null ? !path.equals(that.path) : that.path != null) { + return false; + } + if (processLabel != null ? !processLabel.equals(that.processLabel) + : that.processLabel != null) { + return false; + } + if (resolvConfPath != null ? !resolvConfPath.equals(that.resolvConfPath) + : that.resolvConfPath != null) { + return false; + } + if (state != null ? !state.equals(that.state) : that.state != null) { + return false; + } + if (volumes != null ? !volumes.equals(that.volumes) : that.volumes != null) { + return false; + } + if (volumesRW != null ? !volumesRW.equals(that.volumesRW) : that.volumesRW != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (created != null ? created.hashCode() : 0); + result = 31 * result + (path != null ? path.hashCode() : 0); + result = 31 * result + (args != null ? args.hashCode() : 0); + result = 31 * result + (config != null ? config.hashCode() : 0); + result = 31 * result + (state != null ? state.hashCode() : 0); + result = 31 * result + (image != null ? image.hashCode() : 0); + result = 31 * result + (networkSettings != null ? networkSettings.hashCode() : 0); + result = 31 * result + (resolvConfPath != null ? resolvConfPath.hashCode() : 0); + result = 31 * result + (hostnamePath != null ? hostnamePath.hashCode() : 0); + result = 31 * result + (hostsPath != null ? hostsPath.hashCode() : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (driver != null ? driver.hashCode() : 0); + result = 31 * result + (execDriver != null ? execDriver.hashCode() : 0); + result = 31 * result + (processLabel != null ? processLabel.hashCode() : 0); + result = 31 * result + (mountLabel != null ? mountLabel.hashCode() : 0); + result = 31 * result + (volumes != null ? volumes.hashCode() : 0); + result = 31 * result + (volumesRW != null ? volumesRW.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("id", id) + .add("created", created) + .add("path", path) + .add("args", args) + .add("config", config) + .add("state", state) + .add("image", image) + .add("networkSettings", networkSettings) + .add("resolvConfPath", resolvConfPath) + .add("hostnamePath", hostnamePath) + .add("hostsPath", hostsPath) + .add("name", name) + .add("driver", driver) + .add("execDriver", execDriver) + .add("processLabel", processLabel) + .add("mountLabel", mountLabel) + .add("volumes", volumes) + .add("volumesRW", volumesRW) + .toString(); + } +} diff --git a/src/main/java/com/spotify/docker/messages/ContainerState.java b/src/main/java/com/spotify/docker/messages/ContainerState.java new file mode 100644 index 000000000..bfa7d7ffd --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/ContainerState.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class ContainerState { + + @JsonProperty("Running") private Boolean running; + @JsonProperty("Pid") private Integer pid; + @JsonProperty("ExitCode") private Integer exitCode; + @JsonProperty("StartedAt") private Date startedAt; + @JsonProperty("FinishedAt") private Date finishedAt; + + public Boolean running() { + return running; + } + + public void running(final Boolean running) { + this.running = running; + } + + public Integer pid() { + return pid; + } + + public void pid(final Integer pid) { + this.pid = pid; + } + + public Integer exitCode() { + return exitCode; + } + + public void exitCode(final Integer exitCode) { + this.exitCode = exitCode; + } + + public Date startedAt() { + return startedAt == null ? null : new Date(startedAt.getTime()); + } + + public void startedAt(final Date startedAt) { + this.startedAt = (startedAt == null ? null : new Date(startedAt.getTime())); + } + + public Date finishedAt() { + return finishedAt == null ? null : new Date(finishedAt.getTime()); + } + + public void finishedAt(final Date finishedAt) { + this.finishedAt = (finishedAt == null ? null : new Date(finishedAt.getTime())); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ContainerState that = (ContainerState) o; + + if (exitCode != null ? !exitCode.equals(that.exitCode) : that.exitCode != null) { + return false; + } + if (finishedAt != null ? !finishedAt.equals(that.finishedAt) : that.finishedAt != null) { + return false; + } + if (pid != null ? !pid.equals(that.pid) : that.pid != null) { + return false; + } + if (running != null ? !running.equals(that.running) : that.running != null) { + return false; + } + if (startedAt != null ? !startedAt.equals(that.startedAt) : that.startedAt != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = running != null ? running.hashCode() : 0; + result = 31 * result + (pid != null ? pid.hashCode() : 0); + result = 31 * result + (exitCode != null ? exitCode.hashCode() : 0); + result = 31 * result + (startedAt != null ? startedAt.hashCode() : 0); + result = 31 * result + (finishedAt != null ? finishedAt.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("running", running) + .add("pid", pid) + .add("exitCode", exitCode) + .add("startedAt", startedAt) + .add("finishedAt", finishedAt) + .toString(); + } +} diff --git a/src/main/java/com/spotify/docker/messages/HostConfig.java b/src/main/java/com/spotify/docker/messages/HostConfig.java new file mode 100644 index 000000000..073674cc7 --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/HostConfig.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Map; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class HostConfig { + + @JsonProperty("Binds") private ImmutableList binds; + @JsonProperty("ContainerIDFile") private String containerIDFile; + @JsonProperty("LxcConf") private ImmutableList lxcConf; + @JsonProperty("Privileged") private Boolean privileged; + @JsonProperty("PortBindings") private ImmutableMap> portBindings; + @JsonProperty("Links") private ImmutableList links; + @JsonProperty("PublishAllPorts") private Boolean publishAllPorts; + @JsonProperty("Dns") private ImmutableList dns; + @JsonProperty("DnsSearch") private ImmutableList dnsSearch; + @JsonProperty("VolumesFrom") private ImmutableList volumesFrom; + @JsonProperty("NetworkMode") private String networkMode; + + private HostConfig() { + } + + private HostConfig(final Builder builder) { + this.binds = builder.binds; + this.containerIDFile = builder.containerIDFile; + this.lxcConf = builder.lxcConf; + this.privileged = builder.privileged; + this.portBindings = builder.portBindings; + this.links = builder.links; + this.publishAllPorts = builder.publishAllPorts; + this.dns = builder.dns; + this.dnsSearch = builder.dnsSearch; + this.volumesFrom = builder.volumesFrom; + this.networkMode = builder.networkMode; + } + + public List binds() { + return binds; + } + + public String containerIDFile() { + return containerIDFile; + } + + public List lxcConf() { + return lxcConf; + } + + public Boolean privileged() { + return privileged; + } + + public Map> portBindings() { + return portBindings; + } + + public List links() { + return links; + } + + public Boolean publishAllPorts() { + return publishAllPorts; + } + + public List dns() { + return dns; + } + + public List dnsSearch() { + return dnsSearch; + } + + public List volumesFrom() { + return volumesFrom; + } + + public String networkMode() { + return networkMode; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final HostConfig that = (HostConfig) o; + + if (binds != null ? !binds.equals(that.binds) : that.binds != null) { + return false; + } + if (containerIDFile != null ? !containerIDFile.equals(that.containerIDFile) + : that.containerIDFile != null) { + return false; + } + if (dns != null ? !dns.equals(that.dns) : that.dns != null) { + return false; + } + if (dnsSearch != null ? !dnsSearch.equals(that.dnsSearch) : that.dnsSearch != null) { + return false; + } + if (links != null ? !links.equals(that.links) : that.links != null) { + return false; + } + if (lxcConf != null ? !lxcConf.equals(that.lxcConf) : that.lxcConf != null) { + return false; + } + if (networkMode != null ? !networkMode.equals(that.networkMode) : that.networkMode != null) { + return false; + } + if (portBindings != null ? !portBindings.equals(that.portBindings) + : that.portBindings != null) { + return false; + } + if (privileged != null ? !privileged.equals(that.privileged) : that.privileged != null) { + return false; + } + if (publishAllPorts != null ? !publishAllPorts.equals(that.publishAllPorts) + : that.publishAllPorts != null) { + return false; + } + if (volumesFrom != null ? !volumesFrom.equals(that.volumesFrom) : that.volumesFrom != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = binds != null ? binds.hashCode() : 0; + result = 31 * result + (containerIDFile != null ? containerIDFile.hashCode() : 0); + result = 31 * result + (lxcConf != null ? lxcConf.hashCode() : 0); + result = 31 * result + (privileged != null ? privileged.hashCode() : 0); + result = 31 * result + (portBindings != null ? portBindings.hashCode() : 0); + result = 31 * result + (links != null ? links.hashCode() : 0); + result = 31 * result + (publishAllPorts != null ? publishAllPorts.hashCode() : 0); + result = 31 * result + (dns != null ? dns.hashCode() : 0); + result = 31 * result + (dnsSearch != null ? dnsSearch.hashCode() : 0); + result = 31 * result + (volumesFrom != null ? volumesFrom.hashCode() : 0); + result = 31 * result + (networkMode != null ? networkMode.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("binds", binds) + .add("containerIDFile", containerIDFile) + .add("lxcConf", lxcConf) + .add("privileged", privileged) + .add("portBindings", portBindings) + .add("links", links) + .add("publishAllPorts", publishAllPorts) + .add("dns", dns) + .add("dnsSearch", dnsSearch) + .add("volumesFrom", volumesFrom) + .add("networkMode", networkMode) + .toString(); + } + + public static class LxcConfParameter { + + @JsonProperty("Key") private String key; + @JsonProperty("Value") private String value; + + public LxcConfParameter(final String key, final String value) { + this.key = key; + this.value = value; + } + + public String key() { + return key; + } + + public String value() { + return value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final LxcConfParameter that = (LxcConfParameter) o; + + if (key != null ? !key.equals(that.key) : that.key != null) { + return false; + } + if (value != null ? !value.equals(that.value) : that.value != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = key != null ? key.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("key", key) + .add("value", value) + .toString(); + } + } + + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private ImmutableList binds; + private String containerIDFile; + private ImmutableList lxcConf; + private Boolean privileged; + private ImmutableMap> portBindings; + private ImmutableList links; + private Boolean publishAllPorts; + private ImmutableList dns; + private ImmutableList dnsSearch; + private ImmutableList volumesFrom; + private String networkMode; + + private Builder() { + } + + private Builder(final HostConfig hostConfig) { + this.binds = hostConfig.binds; + this.containerIDFile = hostConfig.containerIDFile; + this.lxcConf = hostConfig.lxcConf; + this.privileged = hostConfig.privileged; + this.portBindings = hostConfig.portBindings; + this.links = hostConfig.links; + this.publishAllPorts = hostConfig.publishAllPorts; + this.dns = hostConfig.dns; + this.dnsSearch = hostConfig.dnsSearch; + this.volumesFrom = hostConfig.volumesFrom; + this.networkMode = hostConfig.networkMode; + } + + public Builder binds(final List binds) { + this.binds = ImmutableList.copyOf(binds); + return this; + } + + public Builder binds(final String... binds) { + this.binds = ImmutableList.copyOf(binds); + return this; + } + + public List binds() { + return binds; + } + + public Builder containerIDFile(final String containerIDFile) { + this.containerIDFile = containerIDFile; + return this; + } + + public String containerIDFile() { + return containerIDFile; + } + + public Builder lxcConf(final List lxcConf) { + this.lxcConf = ImmutableList.copyOf(lxcConf); + return this; + } + + public Builder lxcConf(final LxcConfParameter... lxcConf) { + this.lxcConf = ImmutableList.copyOf(lxcConf); + return this; + } + + public List lxcConf() { + return lxcConf; + } + + public Builder privileged(final Boolean privileged) { + this.privileged = privileged; + return this; + } + + public Boolean privileged() { + return privileged; + } + + public Builder portBindings(final Map> portBindings) { + final ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (Map.Entry> entry : portBindings.entrySet()) { + builder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue())); + } + this.portBindings = builder.build(); + return this; + } + + public Map> portBindings() { + return portBindings; + } + + public Builder links(final List links) { + this.links = ImmutableList.copyOf(links); + return this; + } + + public Builder links(final String... links) { + this.links = ImmutableList.copyOf(links); + return this; + } + + public List links() { + return links; + } + + public Builder publishAllPorts(final Boolean publishAllPorts) { + this.publishAllPorts = publishAllPorts; + return this; + } + + public Boolean publishAllPorts() { + return publishAllPorts; + } + + public Builder dns(final List dns) { + this.dns = ImmutableList.copyOf(dns); + return this; + } + + public Builder dns(final String... dns) { + this.dns = ImmutableList.copyOf(dns); + return this; + } + + public List dns() { + return dns; + } + + public Builder dnsSearch(final List dnsSearch) { + this.dnsSearch = ImmutableList.copyOf(dnsSearch); + return this; + } + + public Builder dnsSearch(final String... dnsSearch) { + this.dnsSearch = ImmutableList.copyOf(dnsSearch); + return this; + } + + public List dnsSearch() { + return dnsSearch; + } + + public Builder volumesFrom(final List volumesFrom) { + this.volumesFrom = ImmutableList.copyOf(volumesFrom); + return this; + } + + public Builder volumesFrom(final String... volumesFrom) { + this.volumesFrom = ImmutableList.copyOf(volumesFrom); + return this; + } + + public List volumesFrom() { + return volumesFrom; + } + + public Builder networkMode(final String networkMode) { + this.networkMode = networkMode; + return this; + } + + public String networkMode() { + return networkMode; + } + + public HostConfig build() { + return new HostConfig(this); + } + } +} + + diff --git a/src/main/java/com/spotify/docker/messages/ImageInfo.java b/src/main/java/com/spotify/docker/messages/ImageInfo.java new file mode 100644 index 000000000..b8773a2b6 --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/ImageInfo.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class ImageInfo { + + @JsonProperty("id") private String id; + @JsonProperty("parent") private String parent; + @JsonProperty("comment") private String comment; + @JsonProperty("created") private Date created; + @JsonProperty("container") private String container; + @JsonProperty("container_config") private ContainerConfig containerConfig; + @JsonProperty("docker_version") private String dockerVersion; + @JsonProperty("author") private String author; + @JsonProperty("config") private ContainerConfig config; + @JsonProperty("architecture") private String architecture; + @JsonProperty("os") private String os; + @JsonProperty("Size") private Long size; + + public String id() { + return id; + } + + public String parent() { + return parent; + } + + public String comment() { + return comment; + } + + public Date created() { + return created == null ? null : new Date(created.getTime()); + } + + public String container() { + return container; + } + + public ContainerConfig containerConfig() { + return containerConfig; + } + + public String dockerVersion() { + return dockerVersion; + } + + public String author() { + return author; + } + + public ContainerConfig config() { + return config; + } + + public String architecture() { + return architecture; + } + + public String os() { + return os; + } + + public Long size() { + return size; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ImageInfo imageInfo = (ImageInfo) o; + + if (architecture != null ? !architecture.equals(imageInfo.architecture) + : imageInfo.architecture != null) { + return false; + } + if (author != null ? !author.equals(imageInfo.author) : imageInfo.author != null) { + return false; + } + if (comment != null ? !comment.equals(imageInfo.comment) : imageInfo.comment != null) { + return false; + } + if (config != null ? !config.equals(imageInfo.config) : imageInfo.config != null) { + return false; + } + if (container != null ? !container.equals(imageInfo.container) : imageInfo.container != null) { + return false; + } + if (containerConfig != null ? !containerConfig.equals(imageInfo.containerConfig) + : imageInfo.containerConfig != null) { + return false; + } + if (created != null ? !created.equals(imageInfo.created) : imageInfo.created != null) { + return false; + } + if (dockerVersion != null ? !dockerVersion.equals(imageInfo.dockerVersion) + : imageInfo.dockerVersion != null) { + return false; + } + if (id != null ? !id.equals(imageInfo.id) : imageInfo.id != null) { + return false; + } + if (os != null ? !os.equals(imageInfo.os) : imageInfo.os != null) { + return false; + } + if (parent != null ? !parent.equals(imageInfo.parent) : imageInfo.parent != null) { + return false; + } + if (size != null ? !size.equals(imageInfo.size) : imageInfo.size != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (parent != null ? parent.hashCode() : 0); + result = 31 * result + (comment != null ? comment.hashCode() : 0); + result = 31 * result + (created != null ? created.hashCode() : 0); + result = 31 * result + (container != null ? container.hashCode() : 0); + result = 31 * result + (containerConfig != null ? containerConfig.hashCode() : 0); + result = 31 * result + (dockerVersion != null ? dockerVersion.hashCode() : 0); + result = 31 * result + (author != null ? author.hashCode() : 0); + result = 31 * result + (config != null ? config.hashCode() : 0); + result = 31 * result + (architecture != null ? architecture.hashCode() : 0); + result = 31 * result + (os != null ? os.hashCode() : 0); + result = 31 * result + (size != null ? size.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("id", id) + .add("parent", parent) + .add("comment", comment) + .add("created", created) + .add("container", container) + .add("containerConfig", containerConfig) + .add("dockerVersion", dockerVersion) + .add("author", author) + .add("config", config) + .add("architecture", architecture) + .add("os", os) + .add("size", size) + .toString(); + } +} diff --git a/src/main/java/com/spotify/docker/messages/NetworkSettings.java b/src/main/java/com/spotify/docker/messages/NetworkSettings.java new file mode 100644 index 000000000..056eced41 --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/NetworkSettings.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Map; + +public class NetworkSettings { + + @JsonProperty("IPAddress") private String ipAddress; + @JsonProperty("IPPrefixLen") private Integer ipPrefixLen; + @JsonProperty("Gateway") private String gateway; + @JsonProperty("Bridge") private String bridge; + @JsonProperty("PortMapping") private ImmutableMap> portMapping; + @JsonProperty("Ports") private ImmutableMap> ports; + + private NetworkSettings() { + } + + private NetworkSettings(final Builder builder) { + this.ipAddress = builder.ipAddress; + this.ipPrefixLen = builder.ipPrefixLen; + this.gateway = builder.gateway; + this.bridge = builder.bridge; + this.portMapping = builder.portMapping; + this.ports = builder.ports; + } + + public String ipAddress() { + return ipAddress; + } + + public Integer ipPrefixLen() { + return ipPrefixLen; + } + + public String gateway() { + return gateway; + } + + public String bridge() { + return bridge; + } + + public Map> portMapping() { + return portMapping; + } + + public Map> ports() { + return ports; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final NetworkSettings that = (NetworkSettings) o; + + if (bridge != null ? !bridge.equals(that.bridge) : that.bridge != null) { + return false; + } + if (gateway != null ? !gateway.equals(that.gateway) : that.gateway != null) { + return false; + } + if (ipAddress != null ? !ipAddress.equals(that.ipAddress) : that.ipAddress != null) { + return false; + } + if (ipPrefixLen != null ? !ipPrefixLen.equals(that.ipPrefixLen) : that.ipPrefixLen != null) { + return false; + } + if (portMapping != null ? !portMapping.equals(that.portMapping) : that.portMapping != null) { + return false; + } + if (ports != null ? !ports.equals(that.ports) : that.ports != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = ipAddress != null ? ipAddress.hashCode() : 0; + result = 31 * result + (ipPrefixLen != null ? ipPrefixLen.hashCode() : 0); + result = 31 * result + (gateway != null ? gateway.hashCode() : 0); + result = 31 * result + (bridge != null ? bridge.hashCode() : 0); + result = 31 * result + (portMapping != null ? portMapping.hashCode() : 0); + result = 31 * result + (ports != null ? ports.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("ipAddress", ipAddress) + .add("ipPrefixLen", ipPrefixLen) + .add("gateway", gateway) + .add("bridge", bridge) + .add("portMapping", portMapping) + .add("ports", ports) + .toString(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String ipAddress; + private Integer ipPrefixLen; + private String gateway; + private String bridge; + private ImmutableMap> portMapping; + private ImmutableMap> ports; + + private Builder() { + } + + private Builder(final NetworkSettings networkSettings) { + this.ipAddress = networkSettings.ipAddress; + this.ipPrefixLen = networkSettings.ipPrefixLen; + this.gateway = networkSettings.gateway; + this.bridge = networkSettings.bridge; + this.portMapping = networkSettings.portMapping; + this.ports = networkSettings.ports; + } + + public Builder ipAddress(final String ipAddress) { + this.ipAddress = ipAddress; + return this; + } + + public Builder ipPrefixLen(final Integer ipPrefixLen) { + this.ipPrefixLen = ipPrefixLen; + return this; + } + + public Builder gateway(final String gateway) { + this.gateway = gateway; + return this; + } + + public Builder bridge(final String bridge) { + this.bridge = bridge; + return this; + } + + public Builder portMapping(final Map> portMapping) { + final ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (Map.Entry> entry : portMapping.entrySet()) { + builder.put(entry.getKey(), ImmutableMap.copyOf(entry.getValue())); + } + this.portMapping = builder.build(); + return this; + } + + public Builder ports(final Map> ports) { + final ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (Map.Entry> entry : ports.entrySet()) { + builder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue())); + } + this.ports = builder.build(); + return this; + } + + public NetworkSettings build() { + return new NetworkSettings(this); + } + } +} diff --git a/src/main/java/com/spotify/docker/messages/PortBinding.java b/src/main/java/com/spotify/docker/messages/PortBinding.java new file mode 100644 index 000000000..b84123ee8 --- /dev/null +++ b/src/main/java/com/spotify/docker/messages/PortBinding.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker.messages; + +import com.google.common.base.Objects; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; + +@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE) +public class PortBinding { + + @JsonProperty("HostIp") private String hostIp; + @JsonProperty("HostPort") private String hostPort; + + public String hostIp() { + return hostIp; + } + + public void hostIp(final String hostIp) { + this.hostIp = hostIp; + } + + public String hostPort() { + return hostPort; + } + + public void hostPort(final String hostPort) { + this.hostPort = hostPort; + } + + public static PortBinding of(final String ip, final String port) { + final PortBinding binding = new PortBinding(); + binding.hostIp(ip); + binding.hostPort(port); + return binding; + } + + public static PortBinding of(final String ip, final int port) { + return of(ip, String.valueOf(port)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final PortBinding that = (PortBinding) o; + + if (hostIp != null ? !hostIp.equals(that.hostIp) : that.hostIp != null) { + return false; + } + if (hostPort != null ? !hostPort.equals(that.hostPort) : that.hostPort != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = hostIp != null ? hostIp.hashCode() : 0; + result = 31 * result + (hostPort != null ? hostPort.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("hostIp", hostIp) + .add("hostPort", hostPort) + .toString(); + } +} diff --git a/src/test/java/com/spotify/docker/DockerClientTest.java b/src/test/java/com/spotify/docker/DockerClientTest.java new file mode 100644 index 000000000..0cbfb2ecf --- /dev/null +++ b/src/test/java/com/spotify/docker/DockerClientTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import com.google.common.base.Strings; +import com.google.common.net.HostAndPort; + +import com.spotify.docker.messages.Container; +import com.spotify.docker.messages.ContainerConfig; +import com.spotify.docker.messages.ContainerCreation; +import com.spotify.docker.messages.ContainerInfo; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import static com.google.common.base.Optional.fromNullable; +import static java.lang.Long.toHexString; +import static java.lang.String.format; +import static java.lang.System.getenv; +import static org.hamcrest.Matchers.any; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.junit.Assert.assertThat; + + +public class DockerClientTest { + + public static final int DOCKER_PORT = Integer.valueOf(env("DOCKER_PORT", "4160")); + public static final String DOCKER_HOST = env("DOCKER_HOST", ":" + DOCKER_PORT); + public static final String DOCKER_ADDRESS; + public static final String DOCKER_ENDPOINT; + + @Rule public ExpectedException exception = ExpectedException.none(); + + static { + // Parse DOCKER_HOST + final String stripped = DOCKER_HOST.replaceAll(".*://", ""); + final HostAndPort hostAndPort = HostAndPort.fromString(stripped); + final String host = hostAndPort.getHostText(); + DOCKER_ADDRESS = Strings.isNullOrEmpty(host) ? "localhost" : host; + DOCKER_ENDPOINT = format("http://%s:%d", DOCKER_ADDRESS, + hostAndPort.getPortOrDefault(DOCKER_PORT)); + } + + private static String env(final String key, final String defaultValue) { + return fromNullable(getenv(key)).or(defaultValue); + } + + private final DockerClient sut = new DefaultDockerClient(URI.create(DOCKER_ENDPOINT)); + + private final String nameTag = toHexString(ThreadLocalRandom.current().nextLong()); + + @After + public void removeContainers() throws Exception { + final List containers = sut.listContainers(); + for (Container container : containers) { + final ContainerInfo info = sut.inspectContainer(container.id()); + if (info != null && info.name().contains(nameTag)) { + sut.killContainer(info.id()); + sut.removeContainer(info.id()); + } + } + } + + @Test + public void testPullWithTag() throws Exception { + sut.pull("ubuntu:12.04"); + } + + @Test + public void integrationTest() throws Exception { + + // Pull image + sut.pull("busybox"); + + // Create container + final ContainerConfig config = ContainerConfig.builder() + .image("busybox") + .cmd("sh", "-c", "while :; do sleep 1; done") + .build(); + final String name = randomName(); + final ContainerCreation creation = sut.createContainer(config, name); + final String id = creation.id(); + assertThat(creation.getWarnings(), anyOf(is(empty()), is(nullValue()))); + assertThat(id, is(any(String.class))); + + // Inspect using container ID + { + final ContainerInfo info = sut.inspectContainer(id); + assertThat(info.config().image(), equalTo(config.image())); + assertThat(info.config().cmd(), equalTo(config.cmd())); + } + + // Inspect using container name + { + final ContainerInfo info = sut.inspectContainer(name); + assertThat(info.config().image(), equalTo(config.image())); + assertThat(info.config().cmd(), equalTo(config.cmd())); + } + + // Start container + sut.startContainer(id); + + // Kill container + sut.killContainer(id); + + // Remove the container + sut.removeContainer(id); + + // Verify that the container is gone + exception.expect(ContainerNotFoundException.class); + sut.inspectContainer(id); + } + + private String randomName() { + return nameTag + '-' + toHexString(ThreadLocalRandom.current().nextLong()); + } +} \ No newline at end of file diff --git a/src/test/java/com/spotify/docker/ImageRefTest.java b/src/test/java/com/spotify/docker/ImageRefTest.java new file mode 100644 index 000000000..97061be98 --- /dev/null +++ b/src/test/java/com/spotify/docker/ImageRefTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014 Spotify AB. + * + * 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 com.spotify.docker; + +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +public class ImageRefTest { + + @Test + public void testImageWithoutTag() { + final ImageRef sut = new ImageRef("foobar"); + assertThat(sut.getImage(), equalTo("foobar")); + assertThat(sut.getTag(), is(nullValue())); + } + + @Test + public void testImageWithTag() { + final ImageRef sut = new ImageRef("foobar:12345"); + assertThat(sut.getImage(), equalTo("foobar")); + assertThat(sut.getTag(), is("12345")); + } + + @Test + public void testImageWithTagAndRegistry() { + final ImageRef sut = new ImageRef("registry:4711/foo/bar:12345"); + assertThat(sut.getImage(), equalTo("registry:4711/foo/bar")); + assertThat(sut.getTag(), is("12345")); + } + +} \ No newline at end of file