[JCLOUDS-1007] Implemented Docker Exec support in MiscApi

This commit is contained in:
Josef Cacek 2015-09-28 23:08:00 +02:00 committed by Ignasi Barrera
parent d7b3f5d98f
commit f6ad2cc380
12 changed files with 604 additions and 11 deletions

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.docker.domain;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
/**
* Represents a response from Exec Create call (<code>POST /containers/(id)/exec</code>).
*/
@AutoValue
public abstract class Exec {
public abstract String id();
@SerializedNames({ "Id"})
public static Exec create(String id) {
return new AutoValue_Exec(id);
}
}

View File

@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.docker.domain;
import static org.jclouds.docker.internal.NullSafeCopies.copyOf;
import java.util.List;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
/**
* Json Parameters (some of them) of Exec Create call.
*/
@AutoValue
public abstract class ExecCreateParams {
public abstract boolean attachStdout();
public abstract boolean attachStderr();
public abstract List<String> cmd();
@SerializedNames({ "AttachStdout", "AttachStderr", "Cmd" })
private static ExecCreateParams create(boolean attachStdout, boolean attachStderr, List<String> cmd) {
return builder().attachStdout(attachStdout).attachStderr(attachStderr).cmd(cmd).build();
}
/**
* Creates builder for {@link ExecCreateParams}, it sets
* {@link #attachStderr()} and {@link #attachStdout()} to true as a default.
*
* @return new {@link ExecCreateParams.Builder} instance
*/
public static Builder builder() {
return new AutoValue_ExecCreateParams.Builder().attachStderr(true).attachStdout(true);
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder attachStdout(boolean b);
public abstract Builder attachStderr(boolean b);
public abstract Builder cmd(List<String> cmd);
abstract List<String> cmd();
abstract ExecCreateParams autoBuild();
public ExecCreateParams build() {
cmd(copyOf(cmd()));
return autoBuild();
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.docker.domain;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
/**
* Represents a response (part of it) from Exec Inspect call (
* <code>GET /exec/(id)/json</code>).
*/
@AutoValue
public abstract class ExecInspect {
public abstract String id();
public abstract boolean running();
public abstract int exitCode();
@SerializedNames({ "ID", "Running", "ExitCode"})
public static ExecInspect create(String id, boolean running, int exitCode) {
return new AutoValue_ExecInspect(id, running, exitCode);
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.docker.domain;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
/**
* Json Parameter(s) (some of them) of Exec Start call.
*/
@AutoValue
public abstract class ExecStartParams {
public abstract boolean detach();
@SerializedNames({ "Detach" })
public static ExecStartParams create(boolean detach) {
return builder().detach(detach).build();
}
public static Builder builder() {
return new AutoValue_ExecStartParams.Builder().detach(false);
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder detach(boolean b);
public abstract ExecStartParams build();
}
}

View File

@ -23,13 +23,21 @@ import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import org.jclouds.docker.domain.Exec;
import org.jclouds.docker.domain.ExecCreateParams;
import org.jclouds.docker.domain.ExecInspect;
import org.jclouds.docker.domain.ExecStartParams;
import org.jclouds.docker.domain.Info;
import org.jclouds.docker.domain.Version;
import org.jclouds.docker.options.BuildOptions;
import org.jclouds.docker.util.DockerInputStream;
import org.jclouds.io.Payload;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Headers;
import org.jclouds.rest.binders.BindToJsonPayload;
@Consumes(MediaType.APPLICATION_JSON)
@Path("/v{jclouds.api-version}")
@ -82,4 +90,51 @@ public interface MiscApi {
@Headers(keys = { "Content-Type", "Connection" }, values = { "application/tar", "close" })
InputStream build(Payload inputStream, BuildOptions options);
/**
* Sets up an exec instance in a running container with given Id.
*
* @param containerId
* container Id
* @param execCreateParams
* exec parameters
* @return an instance which holds exec identifier
*/
@Named("container:exec")
@POST
@Path("/containers/{id}/exec")
Exec execCreate(@PathParam("id") String containerId,
@BinderParam(BindToJsonPayload.class) ExecCreateParams execCreateParams);
/**
* Starts a previously set up exec instance id. If
* {@link ExecStartParams#detach()} is true, this API returns after starting
* the exec command. Otherwise, this API sets up an interactive session with
* the exec command.
*
* @param execId
* exec instance id
* @param execStartParams
* start parameters
* @return raw docker stream which can be wrapped to
* {@link DockerInputStream}
* @see #execCreate(String, ExecCreateParams)
* @see DockerInputStream
*/
@Named("exec:start")
@POST
@Path("/exec/{id}/start")
InputStream execStart(@PathParam("id") String execId,
@BinderParam(BindToJsonPayload.class) ExecStartParams execStartParams);
/**
* Returns low-level information about the exec command id.
*
* @param execId
* exec instance id
* @return details about exec instance
*/
@Named("exec:inspect")
@GET
@Path("/exec/{id}/json")
ExecInspect execInspect(@PathParam("id") String execId);
}

View File

@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.docker.util;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Extension to {@link DataInputStream} which adds method
* {@link #readStdStreamData()} to allow read multiplexed standard streams.
*/
public final class DockerInputStream extends DataInputStream {
/**
* Ctor from superclass.
*
* @param in
* @see DataInputStream#DataInputStream(InputStream)
*/
public DockerInputStream(InputStream in) {
super(in);
}
/**
* @return {@link StdStreamData} instance read from the input stream or
* <code>null</code> if we reached end of the stream.
* @throws IOException
*/
public StdStreamData readStdStreamData() throws IOException {
byte[] header = new byte[8];
// try to read first byte from the message header - just to check if we
// are at the end
// of stream
if (-1 == read(header, 0, 1)) {
return null;
}
// read the rest of the header
readFully(header, 1, 7);
// decode size as an unsigned int
long size = (long) (header[4] & 0xFF) << 24 | (header[5] & 0xFF) << 16 | (header[6] & 0xFF) << 8
| (header[7] & 0xFF);
byte[] payload;
// The size from the header is an unsigned int so it can happen the byte
// array has not a sufficient size and we'll have to truncate the frame
payload = new byte[(int) Math.min(Integer.MAX_VALUE, size)];
readFully(payload);
boolean truncated = false;
if (size > Integer.MAX_VALUE) {
truncated = true;
// skip the rest
readFully(new byte[(int) (size - Integer.MAX_VALUE)]);
}
return new StdStreamData(header[0], payload, truncated);
}
}

View File

@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.docker.util;
import java.util.Arrays;
/**
* Representation of single message from docker-raw-stream. It holds stream
* type, data (payload) and flag which says if the payload was truncated. The
* truncation can occur when the frame size is greater than
* {@link Integer#MAX_VALUE}.
*/
public final class StdStreamData {
private final StdStreamType type;
private final byte[] payload;
private final boolean truncated;
/**
* Ctor.
*
* @param streamTypeId
* standard stream type (0=stdIn, 1=stdOut, 2=stdErr)
* @param payload
* message data - must not be <code>null</code>
* @param truncated
* @throws ArrayIndexOutOfBoundsException
* if streamTypeId is not an index in {@link StdStreamType} enum.
* @throws NullPointerException
* if provided payload is <code>null</code>
*/
StdStreamData(byte streamTypeId, byte[] payload, boolean truncated)
throws ArrayIndexOutOfBoundsException, NullPointerException {
this.type = StdStreamType.values()[streamTypeId];
this.payload = Arrays.copyOf(payload, payload.length);
this.truncated = truncated;
}
/**
* Type of stream.
*
* @return
*/
public StdStreamType getType() {
return type;
}
/**
* Data from this message.
*
* @return payload.
*/
public byte[] getPayload() {
return payload;
}
/**
* Flag which says if the payload was truncated (because of size).
*
* @return true if truncated
*/
public boolean isTruncated() {
return truncated;
}
/**
* Standard streams enum. The order of entries is important!
*/
public static enum StdStreamType {
IN, OUT, ERR;
}
}

View File

@ -16,23 +16,79 @@
*/
package org.jclouds.docker.features;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import org.assertj.core.api.Fail;
import org.jclouds.docker.compute.BaseDockerApiLiveTest;
import org.jclouds.docker.domain.Config;
import org.jclouds.docker.domain.Container;
import org.jclouds.docker.domain.Exec;
import org.jclouds.docker.domain.ExecCreateParams;
import org.jclouds.docker.domain.ExecInspect;
import org.jclouds.docker.domain.ExecStartParams;
import org.jclouds.docker.domain.Image;
import org.jclouds.docker.options.BuildOptions;
import org.jclouds.docker.options.CreateImageOptions;
import org.jclouds.docker.options.RemoveContainerOptions;
import org.jclouds.docker.util.DockerInputStream;
import org.jclouds.docker.util.StdStreamData;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@Test(groups = "live", testName = "MiscApiLiveTest", singleThreaded = true)
public class MiscApiLiveTest extends BaseDockerApiLiveTest {
protected static final String BUSYBOX_IMAGE_TAG = "busybox:ubuntu-12.04";
private static String imageId;
private Container container = null;
private Image image = null;
private Exec exec = null;
@BeforeClass
protected void init() {
if (api.getImageApi().inspectImage(BUSYBOX_IMAGE_TAG) == null) {
CreateImageOptions options = CreateImageOptions.Builder.fromImage(BUSYBOX_IMAGE_TAG);
InputStream createImageStream = api.getImageApi().createImage(options);
consumeStream(createImageStream);
}
image = api.getImageApi().inspectImage(BUSYBOX_IMAGE_TAG);
assertNotNull(image);
Config containerConfig = Config.builder().image(image.id())
.cmd(ImmutableList.of("/bin/sh", "-c", "touch hello; while true; do echo hello world; sleep 1; done"))
.build();
container = api.getContainerApi().createContainer("miscApiTest", containerConfig);
assertNotNull(container);
api.getContainerApi().startContainer(container.id());
assertTrue(api.getContainerApi().inspectContainer(container.id()).state().running());
}
@AfterClass
protected void tearDown() {
if (container != null) {
if (api.getContainerApi().inspectContainer(container.id()) != null) {
api.getContainerApi().removeContainer(container.id(), RemoveContainerOptions.Builder.force(true));
}
}
if (image != null) {
api.getImageApi().deleteImage(BUSYBOX_IMAGE_TAG);
}
}
@Test
public void testVersion() {
assertNotNull(api().getVersion().apiVersion());
@ -61,9 +117,62 @@ public class MiscApiLiveTest extends BaseDockerApiLiveTest {
assertNotNull(imageId);
}
@Test
public void testExecCreate() {
exec = api().execCreate(container.id(),
ExecCreateParams.builder()
.cmd(ImmutableList.<String> of("/bin/sh", "-c",
"echo -n Standard >&1 && echo -n Error >&2 && exit 2"))
.attachStderr(true).attachStdout(true).build());
assertNotNull(exec);
assertNotNull(exec.id());
}
@Test(dependsOnMethods = "testExecCreate")
public void testExecStart() throws IOException {
final ExecStartParams startParams = ExecStartParams.builder().detach(false).build();
DockerInputStream inputStream = null;
try {
inputStream = new DockerInputStream(api().execStart(exec.id(), startParams));
assertNotNull(inputStream);
ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
StdStreamData data = null;
while (null != (data = inputStream.readStdStreamData())) {
assertFalse(data.isTruncated());
switch (data.getType()) {
case OUT:
baosOut.write(data.getPayload());
break;
case ERR:
baosErr.write(data.getPayload());
break;
default:
Fail.fail("Unexpected Stream type");
break;
}
}
assertEquals(baosOut.toString(), "Standard");
assertEquals(baosErr.toString(), "Error");
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
@Test(dependsOnMethods = "testExecStart")
public void testExecInspect() throws IOException {
ExecInspect execInspect = api().execInspect(exec.id());
assertNotNull(execInspect);
assertEquals(execInspect.id(), exec.id());
assertEquals(execInspect.running(), false);
assertEquals(execInspect.exitCode(), 2);
}
private MiscApi api() {
return api.getMiscApi();
}
}

View File

@ -16,25 +16,40 @@
*/
package org.jclouds.docker.features;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.jclouds.docker.compute.BaseDockerApiLiveTest.tarredDockerfile;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.docker.DockerApi;
import org.jclouds.docker.config.DockerParserModule;
import org.jclouds.docker.domain.Exec;
import org.jclouds.docker.domain.ExecCreateParams;
import org.jclouds.docker.domain.ExecInspect;
import org.jclouds.docker.domain.ExecStartParams;
import org.jclouds.docker.internal.BaseDockerMockTest;
import org.jclouds.docker.parse.InfoParseTest;
import org.jclouds.docker.parse.VersionParseTest;
import org.jclouds.docker.util.DockerInputStream;
import org.jclouds.docker.util.StdStreamData;
import org.jclouds.docker.util.StdStreamData.StdStreamType;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.jclouds.docker.compute.BaseDockerApiLiveTest.tarredDockerfile;
import static org.testng.Assert.assertEquals;
import java.io.File;
import java.io.FileInputStream;
import javax.ws.rs.core.HttpHeaders;
import com.google.common.collect.ImmutableList;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
/**
* Mock tests for the {@link org.jclouds.docker.features.MiscApi} class.
@ -92,6 +107,64 @@ public class MiscApiMockTest extends BaseDockerMockTest {
}
}
public void testExecCreate() throws Exception {
MockWebServer server = mockWebServer(new MockResponse().setBody(payloadFromResource("/exec.json")));
MiscApi api = api(DockerApi.class, server.getUrl("/").toString(), new DockerParserModule()).getMiscApi();
try {
final String containerId = "a40d212a0a379de00426a1da2a8fd3fd20d5f74fd7c2dd42f6c93a6b1b0e6974";
final ExecCreateParams execParams = ExecCreateParams.builder()
.cmd(ImmutableList.<String> of("/bin/sh", "-c", "echo -n Standard >&1 && echo -n Error >&2"))
.attachStderr(true).attachStdout(true).build();
final Exec expectedExec = Exec.create("dbf45d296388032ebb9872edb75847f6655a72b4e9ab0d99ae1c75589c4ca957");
assertEquals(api.execCreate(containerId, execParams), expectedExec);
assertSent(server, "POST", "/containers/" + containerId + "/exec");
} finally {
server.shutdown();
}
}
public void testExecStart() throws Exception {
MockWebServer server = mockWebServer(new MockResponse().setBody(payloadFromResource("/exec.start")));
MiscApi api = api(DockerApi.class, server.getUrl("/").toString(), new DockerParserModule()).getMiscApi();
DockerInputStream dis = null;
try {
final String execId = "dbf45d296388032ebb9872edb75847f6655a72b4e9ab0d99ae1c75589c4ca957";
final ExecStartParams startParams = ExecStartParams.builder().detach(false).build();
dis = new DockerInputStream(api.execStart(execId, startParams));
final StdStreamData msg1 = dis.readStdStreamData();
assertFalse(msg1.isTruncated());
assertEquals(msg1.getPayload(), "Standard".getBytes(StandardCharsets.UTF_8));
assertEquals(msg1.getType(), StdStreamType.OUT);
final StdStreamData msg2 = dis.readStdStreamData();
assertFalse(msg2.isTruncated());
assertEquals(msg2.getPayload(), "Error".getBytes(StandardCharsets.UTF_8));
assertEquals(msg2.getType(), StdStreamType.ERR);
assertNull(dis.readStdStreamData());
assertSent(server, "POST", "/exec/" + execId + "/start");
} finally {
if (dis != null) {
dis.close();
}
server.shutdown();
}
}
public void testExecInspect() throws IOException, InterruptedException {
MockWebServer server = mockWebServer(new MockResponse().setBody(payloadFromResource("/execInspect.json")));
MiscApi api = api(DockerApi.class, server.getUrl("/").toString(), new DockerParserModule()).getMiscApi();
final String expectedExecId = "fda1cf8064863fc0667c691c69793fdb7d0bd4a1fabb8250536abe5203e4208a";
ExecInspect execInspect = api.execInspect(expectedExecId);
assertNotNull(execInspect);
assertEquals(execInspect.id(), expectedExecId);
assertEquals(execInspect.running(), false);
assertEquals(execInspect.exitCode(), 2);
assertSent(server, "GET", "/exec/" + expectedExecId + "/json");
}
/**
* Asserts that correct values of HTTP headers are used in Docker build REST
* API calls.

View File

@ -0,0 +1 @@
{"Id":"dbf45d296388032ebb9872edb75847f6655a72b4e9ab0d99ae1c75589c4ca957"}

Binary file not shown.

View File

@ -0,0 +1 @@
{"ID":"fda1cf8064863fc0667c691c69793fdb7d0bd4a1fabb8250536abe5203e4208a","Running":false,"ExitCode":2,"ProcessConfig":{"privileged":false,"user":"","tty":false,"entrypoint":"/bin/sh","arguments":["-c","echo -n Standard \u003e\u00261 \u0026\u0026 echo -n Error \u003e\u00262 \u0026\u0026 exit 2"]},"OpenStdin":false,"OpenStderr":true,"OpenStdout":true,"Container":{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":1561,"ExitCode":0,"Error":"","StartedAt":"2015-09-29T12:36:21.011469908Z","FinishedAt":"0001-01-01T00:00:00Z"},"ID":"07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e","Created":"2015-09-29T12:36:19.829846377Z","Path":"/bin/sh","Args":["-c","touch hello; while true; do echo hello world; sleep 1; done"],"Config":{"Hostname":"07695c5170a1","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["/bin/sh","-c","touch hello; while true; do echo hello world; sleep 1; done"],"Image":"ff8f955d1fed83a6239675b9a767fc9502db9934922a2f69ac6bf4cad8a7d7be","Volumes":null,"VolumeDriver":"","WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"MacAddress":"","OnBuild":null,"Labels":{},"Init":""},"Image":"ff8f955d1fed83a6239675b9a767fc9502db9934922a2f69ac6bf4cad8a7d7be","NetworkSettings":{"Bridge":"","EndpointID":"78d38a6f4d63966b16ca824a533b70f0a3a00490586688a188f67ec0bb07e68f","Gateway":"172.17.42.1","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"HairpinMode":false,"IPAddress":"172.17.0.3","IPPrefixLen":16,"IPv6Gateway":"","LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:03","NetworkID":"880f733909fa4ad45c8fbc95b80c83e22a7b46f8cd7fb23dea62b0996ef1c8c6","PortMapping":null,"Ports":{},"SandboxKey":"/var/run/docker/netns/07695c5170a1","SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null},"ResolvConfPath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/resolv.conf","HostnamePath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/hostname","HostsPath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/hosts","LogPath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e-json.log","Name":"/miscApiTest","Driver":"devicemapper","ExecDriver":"native-0.2","MountLabel":"","ProcessLabel":"","RestartCount":0,"UpdateDns":false,"MountPoints":{},"Volumes":{},"VolumesRW":{},"Init":"","AppArmorProfile":""}}