exposedPorts = new ArrayList<>(ports.size());
63 | for (String port : ports) {
64 | Matcher matcher = TCP_PORT_MATCHER.matcher(port);
65 | if (matcher.matches()) {
66 | int tcpPort = Integer.parseInt(matcher.group("port"));
67 | exposedPorts.add(new ExposedPort(port, tcpPort, containerIp));
68 | }
69 | }
70 | return exposedPorts;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/broken-it/push-with-explicit-registry-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | push-with-explicit-registry-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the basic use case.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 |
20 |
21 | @project.groupId@
22 | @project.artifactId@
23 | @project.version@
24 |
25 |
26 | build
27 |
28 | build-images
29 |
30 |
31 |
32 |
33 | nginx
34 | ${project.basedir}/src/test/resources/Dockerfile
35 | corgis:latest
36 | true
37 | true
38 | ${docker.registry}
39 |
40 |
41 |
42 |
43 |
44 | push
45 |
46 | push-images
47 |
48 |
49 |
50 |
51 |
52 | maven-compiler-plugin
53 | 3.1
54 |
55 | ${maven.compiler.source}
56 | ${maven.compiler.target}
57 | ${project.build.sourceEncoding}
58 |
59 |
60 |
61 | maven-deploy-plugin
62 | 2.8.1
63 |
64 | true
65 |
66 |
67 |
68 | maven-enforcer-plugin
69 | 1.3.1
70 |
71 |
72 | enforce-maven-version
73 |
74 | enforce
75 |
76 |
77 |
78 |
79 | @maven.required.version@
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/provider/RemoteDockerProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.provider;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Collections;
22 | import java.util.List;
23 | import java.util.Map;
24 |
25 | import net.wouterdanes.docker.provider.model.ContainerStartConfiguration;
26 | import net.wouterdanes.docker.provider.model.ExposedPort;
27 | import net.wouterdanes.docker.remoteapi.model.ContainerInspectionResult;
28 | import net.wouterdanes.docker.remoteapi.model.ContainerStartRequest;
29 |
30 | /**
31 | * This class is responsible for providing a docker interface with a remote (not running on localhost) docker host. It
32 | * can be configured by setting an environment variable {@value #DOCKER_HOST_SYSTEM_ENV }, like in the client. Or you
33 | * can specify the host and port on the command line like such:
34 | * -D{@value #DOCKER_HOST_PROPERTY}=[host] -D{@value #DOCKER_PORT_PROPERTY}=[port]
35 | *
36 | * The provider defaults to {@value #TCP_PROTOCOL}://{@value #DEFAULT_DOCKER_HOST}:{@value #DEFAULT_DOCKER_PORT}
37 | */
38 | public class RemoteDockerProvider extends RemoteApiBasedDockerProvider {
39 |
40 | public RemoteDockerProvider() {
41 | super();
42 | }
43 |
44 | @Override
45 | public ContainerInspectionResult startContainer(final ContainerStartConfiguration configuration) {
46 | ContainerStartRequest startRequest = new ContainerStartRequest()
47 | .withAllPortsPublished()
48 | .withLinks(configuration.getLinks());
49 |
50 | return super.startContainer(configuration, startRequest);
51 | }
52 |
53 | @Override
54 | public List getExposedPorts(final String containerId) {
55 | ContainerInspectionResult containerInspectionResult = getContainersService().inspectContainer(containerId);
56 | if (containerInspectionResult.getNetworkSettings().getPorts().isEmpty()) {
57 | return Collections.emptyList();
58 | }
59 | Map> ports =
60 | containerInspectionResult.getNetworkSettings().getPorts();
61 | List exposedPorts = new ArrayList<>();
62 | for (Map.Entry> port : ports.entrySet()) {
63 | String exposedPort = port.getKey();
64 | int hostPort = port.getValue().get(0).getHostPort();
65 | exposedPorts.add(new ExposedPort(exposedPort, hostPort, getHost()));
66 | }
67 | return exposedPorts;
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/net/wouterdanes/docker/provider/AbstractFakeDockerProvider.java:
--------------------------------------------------------------------------------
1 | package net.wouterdanes.docker.provider;
2 |
3 | import net.wouterdanes.docker.provider.model.ContainerCommitConfiguration;
4 | import net.wouterdanes.docker.provider.model.ContainerStartConfiguration;
5 | import net.wouterdanes.docker.provider.model.ExposedPort;
6 | import net.wouterdanes.docker.provider.model.ImageBuildConfiguration;
7 | import net.wouterdanes.docker.remoteapi.model.ContainerInspectionResult;
8 | import net.wouterdanes.docker.remoteapi.model.Credentials;
9 | import org.apache.maven.plugin.logging.Log;
10 | import org.eclipse.aether.RepositorySystem;
11 | import org.eclipse.aether.RepositorySystemSession;
12 | import org.eclipse.aether.repository.RemoteRepository;
13 |
14 | import java.util.List;
15 |
16 | /**
17 | * Utility class to create mock docker providers, extend this and implement the getInstance() method, then create
18 | * a static field in the extending class that can be modified by the test and that hold the actual mock.
19 | */
20 | public abstract class AbstractFakeDockerProvider implements DockerProvider {
21 |
22 | private final AbstractFakeDockerProvider proxy;
23 |
24 | public AbstractFakeDockerProvider() {
25 | proxy = getInstance();
26 | }
27 |
28 | protected abstract AbstractFakeDockerProvider getInstance();
29 |
30 | @Override
31 | public ContainerInspectionResult startContainer(final ContainerStartConfiguration configuration) {
32 | return proxy.startContainer(configuration);
33 | }
34 |
35 | @Override
36 | public void stopContainer(final String containerId) {
37 | proxy.stopContainer(containerId);
38 | }
39 |
40 | @Override
41 | public void deleteContainer(final String containerId) {
42 | proxy.deleteContainer(containerId);
43 | }
44 |
45 | @Override
46 | public List getExposedPorts(final String containerId) {
47 | return proxy.getExposedPorts(containerId);
48 | }
49 |
50 | @Override
51 | public String buildImage(final ImageBuildConfiguration image) {
52 | return proxy.buildImage(image);
53 | }
54 |
55 | @Override
56 | public String commitContainer(ContainerCommitConfiguration configuration) {
57 | return proxy.commitContainer(configuration);
58 | }
59 |
60 | @Override
61 | public void removeImage(final String imageId) {
62 | proxy.removeImage(imageId);
63 | }
64 |
65 | @Override
66 | public void pushImage(final String nameAndTag) {
67 | proxy.pushImage(nameAndTag);
68 | }
69 |
70 | @Override
71 | public void tagImage(final String imageId, final String nameAndTag) {
72 | proxy.tagImage(imageId, nameAndTag);
73 | }
74 |
75 | @Override
76 | public void setCredentials(Credentials credentials) {
77 | proxy.setCredentials(credentials);
78 | }
79 |
80 | @Override
81 | public String getLogs(final String containerId) {
82 | return proxy.getLogs(containerId);
83 | }
84 |
85 | @Override
86 | public void setLogger(final Log logger) {
87 | proxy.setLogger(logger);
88 | }
89 |
90 | @Override
91 | public void setRepositorySystem(RepositorySystem repositorySystem) {
92 | // NOOP
93 | }
94 |
95 | @Override
96 | public void setRepositorySystemSession(RepositorySystemSession repositorySystemSession) {
97 | // NOOP
98 | }
99 |
100 | @Override
101 | public void setRemoteRepositories(List remoteRepositories) {
102 | // NOOP
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/maven/CommitContainerMojo.java:
--------------------------------------------------------------------------------
1 | package net.wouterdanes.docker.maven;
2 |
3 | import net.wouterdanes.docker.provider.model.ContainerCommitConfiguration;
4 | import net.wouterdanes.docker.provider.model.ImageBuildConfiguration;
5 | import net.wouterdanes.docker.remoteapi.exception.DockerException;
6 | import org.apache.maven.plugin.MojoExecutionException;
7 | import org.apache.maven.plugin.MojoFailureException;
8 | import org.apache.maven.plugins.annotations.InstantiationStrategy;
9 | import org.apache.maven.plugins.annotations.LifecyclePhase;
10 | import org.apache.maven.plugins.annotations.Mojo;
11 | import org.apache.maven.plugins.annotations.Parameter;
12 |
13 | import java.util.List;
14 | import java.util.Optional;
15 |
16 | @Mojo(name = "commit-containers", defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST, threadSafe = true,
17 | instantiationStrategy = InstantiationStrategy.PER_LOOKUP)
18 | public class CommitContainerMojo extends AbstractPreVerifyDockerMojo {
19 | @Parameter(required = true)
20 | private List containers;
21 |
22 | public void setConfiguration(final List containers) {
23 | this.containers = containers;
24 | }
25 |
26 | @Override
27 | protected void doExecute() throws MojoExecutionException, MojoFailureException {
28 | if (containers == null || containers.isEmpty()) {
29 | getLog().warn("No containers specified.");
30 | return;
31 | }
32 | for (ContainerCommitConfiguration container : containers) {
33 | commitContainer(container);
34 | }
35 | }
36 |
37 | protected void commitContainer(ContainerCommitConfiguration container) throws MojoFailureException {
38 | getLog().info(String.format("Creating image for configuration '%s'", container));
39 | String containerId = container.getId();
40 | Optional containerInfo = getInfoForContainerStartId(containerId);
41 | if (containerInfo.isPresent()) {
42 | try {
43 | //replace container name by its actual id.
44 | String startedContainerId = containerInfo.get().getContainerInfo().getId();
45 | container.setId(startedContainerId);
46 |
47 | String imageId = getDockerProvider().commitContainer(container);
48 | getLog().info(String.format("Image '%s' created from container '%s'", imageId, container.getId()));
49 |
50 | //Register the resulting image so it can be pushed
51 | ImageBuildConfiguration imageBuildConfiguration = new ImageBuildConfiguration();
52 | imageBuildConfiguration.setId(containerId);
53 | imageBuildConfiguration.setNameAndTag(container.getRepo() + ":" + container.getTag());
54 | imageBuildConfiguration.setPush(container.isPush());
55 | registerBuiltImage(imageId, imageBuildConfiguration);
56 |
57 | } catch (DockerException e) {
58 | String errorMessage = String.format("Image '%s:%s' could not be created from container '%s'", container.getRepo(), container.getTag(), container.getId());
59 | handleDockerException(errorMessage, e);
60 | }
61 | } else {
62 | String message = String.format("No container found for id '%s'", containerId);
63 | registerPluginError(new DockerPluginError("commit-containers", message));
64 | getLog().warn(message);
65 | }
66 | }
67 |
68 | @Override
69 | protected String getMojoGoalName() {
70 | return "commit-containers";
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/remoteapi/model/ContainerStartRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi.model;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import java.util.Map;
23 |
24 | import org.codehaus.jackson.annotate.JsonProperty;
25 |
26 | /**
27 | * See
28 | * http://docs.docker.io/reference/api/docker_remote_api_v1.10/#start-a-container
29 | */
30 | @SuppressWarnings("unused")
31 | public class ContainerStartRequest {
32 |
33 | @JsonProperty("Binds")
34 | private List binds;
35 | @JsonProperty("LxcConf")
36 | private Map lxcConf;
37 | @JsonProperty("PortBindings")
38 | private Map>> portBindings;
39 | @JsonProperty("PublishAllPorts")
40 | private boolean publishAllPorts = false;
41 | @JsonProperty("Privileged")
42 | private boolean privileged = false;
43 | @JsonProperty("Links")
44 | private List links = new ArrayList<>();
45 |
46 | public ContainerStartRequest withBinds(List binds) {
47 | this.binds = binds;
48 | return this;
49 | }
50 |
51 | public ContainerStartRequest withLxcConf(Map lxcConf) {
52 | this.lxcConf = lxcConf;
53 | return this;
54 | }
55 |
56 | public ContainerStartRequest withPortBindings(Map>> portBindings) {
57 | this.portBindings = portBindings;
58 | return this;
59 | }
60 |
61 | public ContainerStartRequest withAllPortsPublished() {
62 | this.publishAllPorts = true;
63 | return this;
64 | }
65 |
66 | public ContainerStartRequest withLink(String containerName, String alias) {
67 | addLink(containerName, alias);
68 | return this;
69 | }
70 |
71 | public ContainerStartRequest withLinks(List links) {
72 | for (ContainerLink link : links) {
73 | addLink(link.getContainerId(), link.getContainerAlias());
74 | }
75 | return this;
76 | }
77 |
78 | public ContainerStartRequest makePrivileged() {
79 | this.privileged = true;
80 | return this;
81 | }
82 |
83 | public List getBinds() {
84 | return binds;
85 | }
86 |
87 | public Map getLxcConf() {
88 | return lxcConf;
89 | }
90 |
91 | public Map>> getPortBindings() {
92 | return portBindings;
93 | }
94 |
95 | public boolean isPublishAllPorts() {
96 | return publishAllPorts;
97 | }
98 |
99 | public boolean isPrivileged() {
100 | return privileged;
101 | }
102 |
103 | public List getLinks() {
104 | return links;
105 | }
106 |
107 | private void addLink(final String containerName, final String alias) {
108 | links.add(String.format("%s:%s", containerName, alias));
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/provider/model/ImageBuildConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.provider.model;
19 |
20 | import com.google.gson.Gson;
21 | import org.apache.maven.plugins.annotations.Parameter;
22 |
23 | import java.io.File;
24 | import java.util.HashMap;
25 | import java.util.List;
26 | import java.util.Map;
27 |
28 | /**
29 | * This class is responsible for holding the configuration of a single docker image to be built by the
30 | * {@link net.wouterdanes.docker.maven.BuildImageMojo}
31 | */
32 | public class ImageBuildConfiguration {
33 |
34 | @Parameter(required = true)
35 | private File dockerFile;
36 |
37 | @Parameter(required = true)
38 | private String id;
39 |
40 | @Parameter
41 | private String nameAndTag;
42 |
43 | @Parameter(defaultValue = "false")
44 | private boolean keep;
45 |
46 | @Parameter(defaultValue = "false")
47 | private boolean push;
48 |
49 | @Parameter
50 | private String registry;
51 |
52 | @Parameter
53 | private List artifacts;
54 |
55 | @Parameter
56 | private List mavenArtifacts;
57 |
58 | @Parameter
59 | protected Map buildArguments = new HashMap();
60 |
61 | public String getId() {
62 | return id;
63 | }
64 |
65 | public void setId(final String id) {
66 | this.id = id;
67 | }
68 |
69 | public String getNameAndTag() {
70 | return nameAndTag;
71 | }
72 |
73 | public void setNameAndTag(final String nameAndTag) {
74 | this.nameAndTag = nameAndTag;
75 | }
76 |
77 | public boolean isKeep() {
78 | return keep;
79 | }
80 |
81 | public void setKeep(final boolean keep) {
82 | this.keep = keep;
83 | }
84 |
85 | public boolean isPush() {
86 | return push;
87 | }
88 |
89 | public void setPush(boolean push) {
90 | this.push = push;
91 | }
92 |
93 | public String getRegistry() {
94 | return registry;
95 | }
96 |
97 | public void setRegistry(String registry) {
98 | this.registry = registry;
99 | }
100 |
101 | public List getArtifacts() {
102 | return artifacts;
103 | }
104 |
105 | public void setArtifacts(List artifacts) {
106 | this.artifacts = artifacts;
107 | }
108 |
109 | public List getMavenArtifacts() {
110 | return mavenArtifacts;
111 | }
112 |
113 | public void setMavenArtifacts(List mavenArtifacts) {
114 | this.mavenArtifacts = mavenArtifacts;
115 | }
116 |
117 | public String getBuildArguments() {
118 | Gson gson = new Gson();
119 | return gson.toJson(buildArguments);
120 | }
121 |
122 | public void setBuildArguments(Map buildArguments) {
123 | this.buildArguments = buildArguments;
124 | }
125 |
126 | public File getDockerFile() {
127 | return dockerFile;
128 | }
129 |
130 | public void setDockerFile(File dockerFile) {
131 | this.dockerFile = dockerFile;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/broken-it/tag-and-push-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | tag-and-push-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the basic use case.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 |
20 |
21 | @project.groupId@
22 | @project.artifactId@
23 | @project.version@
24 |
25 |
26 | build
27 |
28 | build-images
29 |
30 |
31 |
32 |
33 | nginx
34 | ${project.basedir}/src/test/resources/Dockerfile
35 | dross:snapshot
36 | true
37 | true
38 | ${docker.registry}
39 |
40 |
41 |
42 |
43 |
44 | tag
45 |
46 | tag-images
47 |
48 |
49 |
50 |
51 | nginx
52 |
53 | dross:release
54 | dross:4.1
55 |
56 | true
57 |
58 |
59 |
60 |
61 |
62 | push
63 |
64 | push-images
65 |
66 |
67 |
68 |
69 |
70 | maven-compiler-plugin
71 | 3.1
72 |
73 | ${maven.compiler.source}
74 | ${maven.compiler.target}
75 | ${project.build.sourceEncoding}
76 |
77 |
78 |
79 | maven-deploy-plugin
80 | 2.8.1
81 |
82 | true
83 |
84 |
85 |
86 | maven-enforcer-plugin
87 | 1.3.1
88 |
89 |
90 | enforce-maven-version
91 |
92 | enforce
93 |
94 |
95 |
96 |
97 | @maven.required.version@
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/remoteapi/model/ContainerCreateRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi.model;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.Collections;
23 | import java.util.List;
24 | import java.util.Map;
25 |
26 | import org.codehaus.jackson.annotate.JsonProperty;
27 |
28 | /**
29 | * See
30 | * http://docs.docker.io/reference/api/docker_remote_api_v1.10/#create-a-container
31 | */
32 | @SuppressWarnings("unused")
33 | public class ContainerCreateRequest {
34 |
35 | @JsonProperty("Hostname")
36 | private String hostname;
37 | @JsonProperty("User")
38 | private String user;
39 | @JsonProperty("Memory")
40 | private Long memory;
41 | @JsonProperty("Cmd")
42 | private List cmd;
43 | @JsonProperty("Image")
44 | private String image;
45 | @JsonProperty("Env")
46 | private List env;
47 | @JsonProperty("MacAddress")
48 | private String macAddress;
49 |
50 | public String getHostname() {
51 | return hostname;
52 | }
53 |
54 | public String getUser() {
55 | return user;
56 | }
57 |
58 | public Long getMemory() {
59 | return memory;
60 | }
61 |
62 | public List getCmd() {
63 | return cmd;
64 | }
65 |
66 | public String getImage() {
67 | return image;
68 | }
69 |
70 | public List getEnv() {
71 | return env != null ? Collections.unmodifiableList(env) : Collections.emptyList();
72 | }
73 |
74 | public String getMacAddress() {
75 | return macAddress;
76 | }
77 |
78 | public ContainerCreateRequest withEnv(Map env) {
79 | if (env != null && env.size() > 0) {
80 | this.env = new ArrayList<>();
81 | for (Map.Entry entry : env.entrySet()) {
82 | this.env.add(String.format("%s=%s", entry.getKey(), entry.getValue()));
83 | }
84 | }
85 | return this;
86 | }
87 |
88 | public ContainerCreateRequest withHostname(String hostname) {
89 | this.hostname = hostname;
90 | return this;
91 | }
92 |
93 | public ContainerCreateRequest withUser(String user) {
94 | this.user = user;
95 | return this;
96 | }
97 |
98 | public ContainerCreateRequest withMemory(long memory) {
99 | this.memory = memory;
100 | return this;
101 | }
102 |
103 | public ContainerCreateRequest withCommand(String command) {
104 | this.cmd = Arrays.asList(command);
105 | return this;
106 | }
107 |
108 | public ContainerCreateRequest withCommands(List commands) {
109 | this.cmd = new ArrayList<>(commands);
110 | return this;
111 | }
112 |
113 | public ContainerCreateRequest withMacAddress(String macAddress) {
114 | this.macAddress = macAddress;
115 | return this;
116 | }
117 |
118 | public ContainerCreateRequest fromImage(String image) {
119 | this.image = image;
120 | return this;
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/test/java/net/wouterdanes/docker/provider/DockerExceptionThrowingDockerProvider.java:
--------------------------------------------------------------------------------
1 | package net.wouterdanes.docker.provider;
2 |
3 | import java.util.List;
4 |
5 | import org.apache.maven.plugin.logging.Log;
6 |
7 | import net.wouterdanes.docker.provider.model.ContainerCommitConfiguration;
8 | import net.wouterdanes.docker.provider.model.ContainerStartConfiguration;
9 | import net.wouterdanes.docker.provider.model.ExposedPort;
10 | import net.wouterdanes.docker.provider.model.ImageBuildConfiguration;
11 | import net.wouterdanes.docker.remoteapi.exception.DockerException;
12 | import net.wouterdanes.docker.remoteapi.model.ContainerInspectionResult;
13 | import net.wouterdanes.docker.remoteapi.model.Credentials;
14 | import org.eclipse.aether.RepositorySystem;
15 | import org.eclipse.aether.RepositorySystemSession;
16 | import org.eclipse.aether.repository.RemoteRepository;
17 |
18 | /**
19 | * A Mock {@link net.wouterdanes.docker.provider.DockerProvider} that only throws
20 | * {@link net.wouterdanes.docker.remoteapi.exception.DockerException}s
21 | *
22 | * If you want to use this provider in your tests, simply write:
23 | * DockerExceptionThrowingDockerProvider.class.newInstance();
24 | */
25 | public class DockerExceptionThrowingDockerProvider implements DockerProvider {
26 |
27 | public static final String PROVIDER_KEY = DockerExceptionThrowingDockerProvider.class.getName();
28 |
29 | static {
30 | DockerProviderSupplier.registerProvider(PROVIDER_KEY, DockerExceptionThrowingDockerProvider.class);
31 | }
32 |
33 | @Override
34 | public void setCredentials(final Credentials credentials) {
35 | // NOOP
36 | }
37 |
38 | @Override
39 | public ContainerInspectionResult startContainer(final ContainerStartConfiguration configuration) {
40 | throwBadException();
41 | return null;
42 | }
43 |
44 | @Override
45 | public void stopContainer(final String containerId) {
46 | throwBadException();
47 | }
48 |
49 | @Override
50 | public void deleteContainer(final String containerId) {
51 | throwBadException();
52 | }
53 |
54 | @Override
55 | public List getExposedPorts(final String containerId) {
56 | throwBadException();
57 | return null;
58 | }
59 |
60 | @Override
61 | public String buildImage(final ImageBuildConfiguration image) {
62 | throwBadException();
63 | return null;
64 | }
65 |
66 | @Override
67 | public String commitContainer(ContainerCommitConfiguration configuration) {
68 | throwBadException();
69 | return null;
70 | }
71 |
72 | @Override
73 | public void removeImage(final String imageId) {
74 | throwBadException();
75 | }
76 |
77 | @Override
78 | public void pushImage(final String nameAndTag) {
79 | throwBadException();
80 | }
81 |
82 | @Override
83 | public void tagImage(final String imageId, final String nameAndTag) {
84 | throwBadException();
85 | }
86 |
87 | @Override
88 | public String getLogs(final String containerId) {
89 | throwBadException();
90 | return null;
91 | }
92 |
93 | @Override
94 | public void setLogger(final Log logger) {
95 | // NOOP
96 | }
97 |
98 | @Override
99 | public void setRepositorySystem(RepositorySystem repositorySystem) {
100 | // NOOP
101 | }
102 |
103 | @Override
104 | public void setRepositorySystemSession(RepositorySystemSession repositorySystemSession) {
105 | // NOOP
106 | }
107 |
108 | @Override
109 | public void setRemoteRepositories(List remoteRepositories) {
110 | // NOOP
111 | }
112 |
113 | private static void throwBadException() {
114 | throw new DockerException("Bad stuff");
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/maven/BuildImageMojo.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.maven;
19 |
20 | import net.wouterdanes.docker.provider.model.ImageBuildConfiguration;
21 | import net.wouterdanes.docker.remoteapi.exception.DockerException;
22 | import org.apache.maven.plugin.MojoExecutionException;
23 | import org.apache.maven.plugin.MojoFailureException;
24 | import org.apache.maven.plugins.annotations.InstantiationStrategy;
25 | import org.apache.maven.plugins.annotations.LifecyclePhase;
26 | import org.apache.maven.plugins.annotations.Mojo;
27 | import org.apache.maven.plugins.annotations.Parameter;
28 |
29 | import java.util.HashSet;
30 | import java.util.List;
31 | import java.util.Set;
32 |
33 | /**
34 | * This class is responsible for building docker images specified in the POM file. It runs by default during the
35 | * package phase of a maven project.
36 | */
37 | @Mojo(name = "build-images", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true,
38 | instantiationStrategy = InstantiationStrategy.PER_LOOKUP)
39 | public class BuildImageMojo extends AbstractPreVerifyDockerMojo {
40 |
41 | @Parameter(required = true)
42 | private List images;
43 |
44 | public void setImages(final List images) {
45 | this.images = images;
46 | }
47 |
48 | @Override
49 | protected void doExecute() throws MojoExecutionException, MojoFailureException {
50 | if (images == null || images.isEmpty()) {
51 | getLog().warn("No images to build specified.");
52 | return;
53 | }
54 |
55 | validateAllImages();
56 |
57 | for (ImageBuildConfiguration image : images) {
58 | try {
59 | logImageConfig(image);
60 | String imageId = getDockerProvider().buildImage(image);
61 | getLog().info(String.format("Image '%s' has Id '%s'", image.getId(), imageId));
62 | registerBuiltImage(imageId, image);
63 | } catch (DockerException e) {
64 | String errorMessage = String.format("Cannot build image '%s'", image.getId());
65 | handleDockerException(errorMessage, e);
66 | }
67 | }
68 | }
69 |
70 | private void logImageConfig(final ImageBuildConfiguration image) {
71 | StringBuilder builder = new StringBuilder(String.format("Building image '%s'", image.getId()));
72 | if (image.getNameAndTag() != null) {
73 | builder.append(String.format(", with name and tag '%s'", image.getNameAndTag()));
74 | }
75 | builder.append("..");
76 | getLog().info(builder.toString());
77 | }
78 |
79 | private void validateAllImages() throws MojoExecutionException {
80 | Set ids = new HashSet<>(images.size());
81 | for (ImageBuildConfiguration image : images) {
82 | if (ids.contains(image.getId())) {
83 | throw new MojoExecutionException(String.format("Image ID '%s' used twice, Image IDs must be unique!",
84 | image.getId()));
85 | }
86 | ids.add(image.getId());
87 | }
88 | }
89 |
90 | @Override
91 | protected String getMojoGoalName() {
92 | return "build-images";
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/net/wouterdanes/docker/remoteapi/CredentialsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi;
19 |
20 | import net.wouterdanes.docker.remoteapi.model.Credentials;
21 | import org.codehaus.jackson.JsonNode;
22 | import org.codehaus.jackson.map.ObjectMapper;
23 | import org.junit.Before;
24 | import org.junit.Test;
25 |
26 | import java.io.IOException;
27 | import java.util.Base64;
28 |
29 | import static org.junit.Assert.assertEquals;
30 |
31 | public class CredentialsTest {
32 |
33 | private static final String USERNAME = "jim";
34 | private static final String PASSWORD = "123456";
35 | private static final String EMAIL = "jim_rulz87@hotmail.com";
36 | private static final String SERVERNAME = "http://myspace.com/jim.the.impaler";
37 |
38 | private BaseService miscService;
39 |
40 | @Before
41 | public void setUp() {
42 | miscService = new BaseService("don't care", "doesn't matter") {
43 | };
44 | }
45 |
46 | @Test
47 | public void testAuthHeaderWithServerName() {
48 | Credentials credsWithServer = new Credentials(USERNAME, PASSWORD, EMAIL, SERVERNAME);
49 | miscService.setCredentials(credsWithServer);
50 |
51 | String encodedResult = miscService.getRegistryAuthHeaderValue();
52 | DecodedAuthHeader decodedResult = decodeAuthHeader(encodedResult);
53 |
54 | assertEquals(USERNAME, decodedResult.getUserName());
55 | assertEquals(PASSWORD, decodedResult.getPassword());
56 | assertEquals(EMAIL, decodedResult.getEmail());
57 | assertEquals(SERVERNAME, decodedResult.getServerAddr());
58 | }
59 |
60 | @Test
61 | public void testAuthHeaderWithoutServerName() {
62 | Credentials credsWithoutServer = new Credentials(USERNAME, PASSWORD, EMAIL, null);
63 | miscService.setCredentials(credsWithoutServer);
64 |
65 | String encodedResult = miscService.getRegistryAuthHeaderValue();
66 | DecodedAuthHeader decodedResult = decodeAuthHeader(encodedResult);
67 |
68 | assertEquals(USERNAME, decodedResult.getUserName());
69 | assertEquals(PASSWORD, decodedResult.getPassword());
70 | assertEquals(EMAIL, decodedResult.getEmail());
71 | assertEquals(Credentials.DEFAULT_SERVER_NAME, decodedResult.getServerAddr());
72 | }
73 |
74 | @Test
75 | public void testAuthHeaderWithoutCredentials() {
76 | String encodedResult = miscService.getRegistryAuthHeaderValue();
77 | assertEquals("null", encodedResult);
78 | }
79 |
80 | protected DecodedAuthHeader decodeAuthHeader(String encodedValue) {
81 | try {
82 | byte[] jsonBytes = Base64.getDecoder().decode(encodedValue);
83 | JsonNode node = new ObjectMapper().readTree(jsonBytes);
84 | return new DecodedAuthHeader(node);
85 | } catch (IOException e) {
86 | throw new AssertionError(e);
87 | }
88 | }
89 |
90 | private static class DecodedAuthHeader {
91 |
92 | private final JsonNode node;
93 |
94 | public DecodedAuthHeader(JsonNode node) {
95 | this.node = node;
96 | }
97 |
98 | public String getUserName() {
99 | return getTextField("username");
100 | }
101 |
102 | public String getPassword() {
103 | return getTextField("password");
104 | }
105 |
106 | public String getEmail() {
107 | return getTextField("email");
108 | }
109 |
110 | public String getServerAddr() {
111 | return getTextField("serveraddress");
112 | }
113 |
114 | private String getTextField(String fieldName) {
115 | return node.get(fieldName).getTextValue();
116 | }
117 |
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/remoteapi/util/HttpsHelper.java:
--------------------------------------------------------------------------------
1 | package net.wouterdanes.docker.remoteapi.util;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.nio.charset.Charset;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 | import java.security.KeyFactory;
10 | import java.security.KeyPair;
11 | import java.security.KeyStore;
12 | import java.security.KeyStoreException;
13 | import java.security.NoSuchAlgorithmException;
14 | import java.security.PrivateKey;
15 | import java.security.PublicKey;
16 | import java.security.cert.Certificate;
17 | import java.security.cert.CertificateException;
18 | import java.security.spec.InvalidKeySpecException;
19 | import java.security.spec.PKCS8EncodedKeySpec;
20 | import java.security.spec.X509EncodedKeySpec;
21 | import java.util.UUID;
22 |
23 | import org.bouncycastle.cert.X509CertificateHolder;
24 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
25 | import org.bouncycastle.openssl.PEMKeyPair;
26 | import org.bouncycastle.openssl.PEMParser;
27 |
28 | /**
29 | * Helper methods to parse and load Docker certificate files for encrypted https connection to the docker daemon
30 | */
31 | public final class HttpsHelper {
32 |
33 | public static final String KEYSTORE_PWD = UUID.randomUUID().toString();
34 |
35 | private HttpsHelper() {}
36 |
37 | public static KeyStore createKeyStore(final String certPath)
38 | throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, CertificateException,
39 | KeyStoreException {
40 | KeyPair keyPair = loadPrivateKey(certPath);
41 | Certificate privCert = loadCertificate(certPath);
42 |
43 | KeyStore keyStore = KeyStore.getInstance("JKS");
44 | keyStore.load(null);
45 |
46 | keyStore.setKeyEntry("docker", keyPair.getPrivate(), KEYSTORE_PWD.toCharArray(), new Certificate[]{privCert});
47 | return keyStore;
48 | }
49 |
50 | public static KeyStore createTrustStore(final String certPath)
51 | throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
52 | Path caPath = Paths.get(certPath, "ca.pem");
53 | BufferedReader reader = Files.newBufferedReader(caPath, Charset.defaultCharset());
54 |
55 | PEMParser parser = new PEMParser(reader);
56 | X509CertificateHolder object = (X509CertificateHolder) parser.readObject();
57 | Certificate caCert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(object);
58 |
59 | KeyStore trustStore = KeyStore.getInstance("JKS");
60 | trustStore.load(null);
61 | trustStore.setCertificateEntry("ca", caCert);
62 | return trustStore;
63 | }
64 |
65 | private static Certificate loadCertificate(final String certPath) throws IOException, CertificateException {
66 | Path cert = Paths.get(certPath, "cert.pem");
67 | BufferedReader reader = Files.newBufferedReader(cert, Charset.defaultCharset());
68 | PEMParser parser = new PEMParser(reader);
69 |
70 | X509CertificateHolder object = (X509CertificateHolder) parser.readObject();
71 | return new JcaX509CertificateConverter().setProvider("BC").getCertificate(object);
72 | }
73 |
74 | private static KeyPair loadPrivateKey(final String certPath)
75 | throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
76 |
77 | Path path = Paths.get(certPath, "key.pem");
78 | BufferedReader reader = Files.newBufferedReader(path, Charset.defaultCharset());
79 |
80 | PEMParser parser = new PEMParser(reader);
81 | Object object = parser.readObject();
82 |
83 | PEMKeyPair keyPair = (PEMKeyPair) object;
84 |
85 | byte[] privateEncoded = keyPair.getPrivateKeyInfo().getEncoded();
86 | byte[] publicEncoded = keyPair.getPublicKeyInfo().getEncoded();
87 |
88 | KeyFactory factory = KeyFactory.getInstance("RSA");
89 |
90 | X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicEncoded);
91 | PublicKey publicKey = factory.generatePublic(publicKeySpec);
92 |
93 | PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateEncoded);
94 | PrivateKey privateKey = factory.generatePrivate(privateKeySpec);
95 |
96 | return new KeyPair(publicKey, privateKey);
97 |
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/provider/model/ContainerStartConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.provider.model;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Collections;
22 | import java.util.List;
23 | import java.util.Map;
24 |
25 | import net.wouterdanes.docker.remoteapi.model.ContainerLink;
26 |
27 | /**
28 | * This class is responsible for holding the start configuration of a docker container
See
30 | * http://docs.docker.io/reference/api/docker_remote_api_v1.10/#start-a-container
31 | */
32 | public class ContainerStartConfiguration {
33 |
34 | public static final int DEFAULT_STARTUP_TIMEOUT = 5 * 60;
35 |
36 | private String image;
37 | private String id;
38 | private List links;
39 | private Map env;
40 |
41 | /**
42 | * Regular expression to look for that indicates the container has started up
43 | */
44 | private String waitForStartup;
45 |
46 | /**
47 | * The maximum time to wait for this container to start (seconds), default is 30 sec.
48 | */
49 | private int startupTimeout;
50 |
51 | /**
52 | * Hostname to give to this container
53 | */
54 | private String hostname;
55 |
56 | /**
57 | * Supply an optional mac address for the container
58 | */
59 | private String macAddress;
60 |
61 | /**
62 | * Set the image name or id to use and returns the object so you can chain from/with statements.
63 | *
64 | * @param image the image name or id
65 | * @return this object
66 | */
67 | public ContainerStartConfiguration fromImage(String image) {
68 | this.image = image;
69 | return this;
70 | }
71 |
72 | public ContainerStartConfiguration withId(String id) {
73 | this.id = id;
74 | return this;
75 | }
76 |
77 | public ContainerStartConfiguration withLinks(ContainerLink... links) {
78 | if (this.links == null) {
79 | this.links = new ArrayList<>(links.length);
80 | }
81 | Collections.addAll(this.links, links);
82 | return this;
83 | }
84 |
85 | public ContainerStartConfiguration withLink(ContainerLink link) {
86 | return withLinks(link);
87 | }
88 |
89 | public ContainerStartConfiguration waitForStartup(String pattern) {
90 | this.waitForStartup = pattern;
91 | return this;
92 | }
93 |
94 | public ContainerStartConfiguration withStartupTimeout(int timeout) {
95 | this.startupTimeout = timeout;
96 | return this;
97 | }
98 |
99 | public ContainerStartConfiguration withEnv(Map env) {
100 | this.env = env;
101 | return this;
102 | }
103 |
104 | public ContainerStartConfiguration withHostname(String hostname) {
105 | this.hostname = hostname;
106 | return this;
107 | }
108 |
109 | public ContainerStartConfiguration withMacAddress(String macAddress) {
110 | this.macAddress = macAddress;
111 | return this;
112 | }
113 |
114 | public String getImage() {
115 | return image;
116 | }
117 |
118 | public String getId() {
119 | return id != null ? id : image;
120 | }
121 |
122 | public List getLinks() {
123 | return links != null ? Collections.unmodifiableList(links) : Collections.emptyList();
124 | }
125 |
126 | public Map getEnv() {
127 | return env != null ? Collections.unmodifiableMap(env) : Collections.emptyMap();
128 | }
129 |
130 | public String getHostname() {
131 | return hostname;
132 | }
133 |
134 | public String getMacAddress() {
135 | return macAddress;
136 | }
137 |
138 | public String getWaitForStartup() {
139 | return waitForStartup;
140 | }
141 |
142 | public int getStartupTimeout() {
143 | return startupTimeout != 0 ? startupTimeout : DEFAULT_STARTUP_TIMEOUT;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/remoteapi/model/ImageDescriptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi.model;
19 |
20 | import org.apache.commons.lang3.Validate;
21 |
22 | import java.util.Optional;
23 | import java.util.regex.Matcher;
24 | import java.util.regex.Pattern;
25 |
26 | /**
27 | * Creates an image descriptor based on a passed image id or qualifier in the form ([registry]/[repo]/[image]:[tag])
28 | */
29 | public class ImageDescriptor {
30 |
31 | private static final Pattern IMAGE_QUALIFIER = Pattern.compile("^"
32 | + "((?[\\w\\.\\-]+(:\\d+)?)/)??" // registry
33 | + "((?[\\w]+)/)?" // repository
34 | + "(?[\\w\\.\\-]+)" // image
35 | + "(:(?[\\w\\.\\-]+))?" // tag
36 | + "$");
37 |
38 | private final String id;
39 | private final Optional registry;
40 | private final Optional repository;
41 | private final String image;
42 | private final Optional tag;
43 |
44 | public ImageDescriptor(String id) {
45 | Validate.notBlank(id, "Id was null or empty");
46 |
47 | this.id = id;
48 |
49 | Matcher matcher = IMAGE_QUALIFIER.matcher(id);
50 | if (matcher.matches()) {
51 | // because Optional.orNull(x) cannot handle null values of x
52 | this.registry = Optional.ofNullable(matcher.group("registry"));
53 | this.repository = Optional.ofNullable(matcher.group("repository"));
54 | this.image = matcher.group("image");
55 | this.tag = Optional.ofNullable(matcher.group("tag"));
56 | } else {
57 | this.registry = Optional.empty();
58 | this.repository = Optional.empty();
59 | this.image = id;
60 | this.tag = Optional.empty();
61 | }
62 | }
63 |
64 | public String getId() {
65 | return id;
66 | }
67 |
68 | public Optional getRegistry() {
69 | return registry;
70 | }
71 |
72 | public Optional getRepository() {
73 | return repository;
74 | }
75 |
76 | public String getImage() {
77 | return image;
78 | }
79 |
80 | public Optional getTag() {
81 | return tag;
82 | }
83 |
84 | public String getRegistryRepositoryAndImage() {
85 | StringBuilder buf = new StringBuilder();
86 | appendRegistry(buf);
87 | appendRepository(buf);
88 | appendImage(buf);
89 | return buf.toString();
90 | }
91 |
92 | public String getRepositoryAndImage() {
93 | StringBuilder buf = new StringBuilder();
94 | appendRepository(buf);
95 | appendImage(buf);
96 | return buf.toString();
97 | }
98 |
99 | public String getRepositoryImageAndTag() {
100 | StringBuilder buf = new StringBuilder();
101 | appendRepository(buf);
102 | appendImage(buf);
103 | appendTag(buf);
104 | return buf.toString();
105 | }
106 |
107 | private void appendRegistry(StringBuilder buf) {
108 | if (registry.isPresent()) {
109 | buf.append(registry.get());
110 | buf.append('/');
111 | }
112 | }
113 |
114 | private void appendRepository(StringBuilder buf) {
115 | if (repository.isPresent()) {
116 | buf.append(repository.get());
117 | buf.append('/');
118 | }
119 | }
120 |
121 | private void appendImage(StringBuilder buf) {
122 | buf.append(image);
123 | }
124 |
125 | private void appendTag(StringBuilder buf) {
126 | if (tag.isPresent()) {
127 | buf.append(':');
128 | buf.append(tag.get());
129 | }
130 | }
131 |
132 | @Override
133 | public String toString() {
134 | return "ImageDescriptor[id=" + id
135 | + ", registry=" + registry.orElse("")
136 | + ", repository=" + repository.orElse("")
137 | + ", image=" + image
138 | + ", tag=" + tag.orElse("")
139 | + "]";
140 | }
141 |
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/src/it/buildargs-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | buildargs-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the buildargs implementation.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 |
20 | junit
21 | junit
22 | 4.8.2
23 | test
24 |
25 |
26 | org.apache.httpcomponents
27 | httpclient
28 | 4.3.3
29 | test
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.apache.maven.plugins
37 | maven-failsafe-plugin
38 | 2.17
39 |
40 |
41 | run-tests
42 |
43 | integration-test
44 | verify
45 |
46 |
47 |
48 | http://${docker.containers.buildargs.ports.80/tcp.host}:${docker.containers.buildargs.ports.80/tcp.port}
49 |
50 |
51 |
52 |
53 |
54 |
55 | @project.groupId@
56 | @project.artifactId@
57 | @project.version@
58 |
59 |
60 | build
61 |
62 | build-images
63 |
64 |
65 |
66 |
67 | buildargs
68 | ${project.basedir}/src/test/resources/Dockerfile
69 | buildargs:tahahag
70 |
71 | nginx
72 |
73 | true
74 |
75 |
76 |
77 |
78 |
79 | start
80 |
81 |
82 |
83 | buildargs
84 | buildargs
85 |
86 |
87 |
88 |
89 | start-containers
90 |
91 |
92 |
93 | stop
94 |
95 |
96 |
97 | stop-containers
98 |
99 |
100 |
101 | verify
102 |
103 | verify
104 |
105 |
106 |
107 |
108 |
109 | maven-compiler-plugin
110 | 3.1
111 |
112 | ${maven.compiler.source}
113 | ${maven.compiler.target}
114 | ${project.build.sourceEncoding}
115 |
116 |
117 |
118 | maven-deploy-plugin
119 | 2.8.1
120 |
121 | true
122 |
123 |
124 |
125 | maven-enforcer-plugin
126 | 1.3.1
127 |
128 |
129 | enforce-maven-version
130 |
131 | enforce
132 |
133 |
134 |
135 |
136 | @maven.required.version@
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/src/it/skip-using-property-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | simple-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the basic use case.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 | true
17 |
18 |
19 |
20 |
21 | junit
22 | junit
23 | 4.8.2
24 | test
25 |
26 |
27 | org.apache.httpcomponents
28 | httpclient
29 | 4.3.3
30 | test
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-failsafe-plugin
39 | 2.17
40 |
41 |
42 | run-tests
43 |
44 | integration-test
45 | verify
46 |
47 |
48 |
49 | http://${docker.containers.cache.ports.80/tcp.host}:${docker.containers.cache.ports.80/tcp.port}
50 |
51 |
52 |
53 |
54 |
55 |
56 | @project.groupId@
57 | @project.artifactId@
58 | @project.version@
59 |
60 |
61 | build
62 |
63 | build-images
64 |
65 |
66 |
67 |
68 | nginx
69 | ${project.basedir}/src/test/resources/Dockerfile
70 | nginx-name:tahahag
71 | true
72 |
73 |
74 |
75 |
76 |
77 | start
78 |
79 |
80 |
81 | Debian
82 | debian:wheezy
83 |
84 |
85 | BusyBox
86 | busybox
87 |
88 |
89 | cache
90 | nginx
91 |
92 |
93 |
94 |
95 | start-containers
96 |
97 |
98 |
99 | stop
100 |
101 | stop-containers
102 |
103 |
104 |
105 | verify
106 |
107 | verify
108 |
109 |
110 |
111 |
112 |
113 | maven-compiler-plugin
114 | 3.1
115 |
116 | ${maven.compiler.source}
117 | ${maven.compiler.target}
118 | ${project.build.sourceEncoding}
119 |
120 |
121 |
122 | maven-deploy-plugin
123 | 2.8.1
124 |
125 | true
126 |
127 |
128 |
129 | maven-enforcer-plugin
130 | 1.3.1
131 |
132 |
133 | enforce-maven-version
134 |
135 | enforce
136 |
137 |
138 |
139 |
140 | @maven.required.version@
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/provider/DockerProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.provider;
19 |
20 | import net.wouterdanes.docker.provider.model.ContainerCommitConfiguration;
21 | import net.wouterdanes.docker.provider.model.ContainerStartConfiguration;
22 | import net.wouterdanes.docker.provider.model.ExposedPort;
23 | import net.wouterdanes.docker.provider.model.ImageBuildConfiguration;
24 | import net.wouterdanes.docker.remoteapi.model.ContainerInspectionResult;
25 | import net.wouterdanes.docker.remoteapi.model.Credentials;
26 | import org.apache.maven.plugin.logging.Log;
27 | import org.eclipse.aether.RepositorySystem;
28 | import org.eclipse.aether.RepositorySystemSession;
29 | import org.eclipse.aether.repository.RemoteRepository;
30 |
31 | import java.util.List;
32 |
33 | /**
34 | * This interface represents an implementation that provides Docker functionality. Examples are:
35 | *
36 | * - Local Docker via Unix Socket
37 | * - Local Docker via TCP Socket
38 | * - Remote (Boot2Docker) via TCP Socket
39 | * - tutum.co
40 | *
41 | */
42 | public interface DockerProvider {
43 |
44 | /**
45 | * Sets (or un-sets) the credentials to be used when communicating with the Docker host.
46 | * @param credentials the user credentials, may be null
47 | */
48 | void setCredentials(Credentials credentials);
49 |
50 | /**
51 | * Starts a docker container and returns the ID of the started container
52 | * @param configuration the configuration parameters
53 | * @return the ID of the started container
54 | */
55 | ContainerInspectionResult startContainer(ContainerStartConfiguration configuration);
56 |
57 | /**
58 | * Stops a docker container
59 | * @param containerId the Id of the container to stop
60 | */
61 | void stopContainer(String containerId);
62 |
63 | /**
64 | * Delete a docker container
65 | * @param containerId the Id of the container to delete
66 | */
67 | void deleteContainer(String containerId);
68 |
69 | /**
70 | * Returns a list of ports exposed by the container, including information on how to reach them
71 | * @param containerId the Id of the container
72 | * @return {@link List} of {@link net.wouterdanes.docker.provider.model.ExposedPort}s
73 | */
74 | List getExposedPorts(String containerId);
75 |
76 | /**
77 | * Builds a new Docker Image based on the passed configuration and returns the id of the newly created image.
78 | * @param image the image configuration to use
79 | * @return the id of the new Docker Image
80 | */
81 | String buildImage(ImageBuildConfiguration image);
82 |
83 | /**
84 | * Create a new image from a container's changes
85 | * @param configuration the configuration parameters
86 | * @return the ID of the created image
87 | */
88 | String commitContainer(ContainerCommitConfiguration configuration);
89 |
90 | /**
91 | * Removes an image from docker
92 | * @param imageId the Id of the images to remove
93 | */
94 | void removeImage(String imageId);
95 |
96 | /**
97 | * Pushes an image from docker to a registry.
98 | * @param nameAndTag optional name and tag to be associated with pushed image
99 | */
100 | void pushImage(String nameAndTag);
101 |
102 | /**
103 | * Associates an image with a new repo/tag.
104 | * @param imageId the Id of the image to tag
105 | * @param nameAndTag the repo/tag to assign
106 | */
107 | void tagImage(String imageId, String nameAndTag);
108 |
109 | /**
110 | * Returns the logs of the specified container
111 | * @param containerId the Id of the container
112 | * @return the container's logs
113 | */
114 | String getLogs(String containerId);
115 |
116 | /**
117 | * Sets the logger to use.
118 | * @param logger the Maven logger to use
119 | */
120 | void setLogger(Log logger);
121 |
122 | /**
123 | * Sets the repositorySystem to use.
124 | * @param repositorySystem the Maven repositorySystem to use
125 | */
126 | void setRepositorySystem(RepositorySystem repositorySystem);
127 |
128 | /**
129 | * Sets the repositorySystemSession to use.
130 | * @param repositorySystemSession the Maven repositorySystemSession to use
131 | */
132 | void setRepositorySystemSession(RepositorySystemSession repositorySystemSession);
133 |
134 | /**
135 | * Sets the remoteRepositories to use.
136 | * @param remoteRepositories the Maven remoteRepositories to use
137 | */
138 | void setRemoteRepositories(List remoteRepositories);
139 | }
140 |
--------------------------------------------------------------------------------
/src/it/simple-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | simple-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the basic use case.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 |
20 | junit
21 | junit
22 | 4.8.2
23 | test
24 |
25 |
26 | org.apache.httpcomponents
27 | httpclient
28 | 4.3.3
29 | test
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.apache.maven.plugins
37 | maven-failsafe-plugin
38 | 2.17
39 |
40 |
41 | run-tests
42 |
43 | integration-test
44 | verify
45 |
46 |
47 |
48 |
49 | http://${docker.containers.cache.ports.80/tcp.host}:${docker.containers.cache.ports.80/tcp.port}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | @project.groupId@
58 | @project.artifactId@
59 | @project.version@
60 |
61 |
62 | build
63 |
64 | build-images
65 |
66 |
67 |
68 |
69 | nginx
70 | ${project.basedir}/src/test/resources/Dockerfile
71 |
72 |
73 | ${project.basedir}/src/test/resources/1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij
74 |
75 |
76 | nginx-name:tahahag
77 | true
78 |
79 |
80 |
81 |
82 |
83 | start
84 |
85 |
86 |
87 | cache
88 | nginx
89 |
90 |
91 |
92 |
93 | start-containers
94 |
95 |
96 |
97 | stop
98 |
99 |
100 |
101 | stop-containers
102 |
103 |
104 |
105 | verify
106 |
107 | verify
108 |
109 |
110 |
111 |
112 |
113 | maven-compiler-plugin
114 | 3.1
115 |
116 | ${maven.compiler.source}
117 | ${maven.compiler.target}
118 | ${project.build.sourceEncoding}
119 |
120 |
121 |
122 | maven-deploy-plugin
123 | 2.8.1
124 |
125 | true
126 |
127 |
128 |
129 | maven-enforcer-plugin
130 | 1.3.1
131 |
132 |
133 | enforce-maven-version
134 |
135 | enforce
136 |
137 |
138 |
139 |
140 | @maven.required.version@
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/it/skip-docker-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | simple-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the basic use case.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 |
20 | junit
21 | junit
22 | 4.8.2
23 | test
24 |
25 |
26 | org.apache.httpcomponents
27 | httpclient
28 | 4.3.3
29 | test
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.apache.maven.plugins
37 | maven-failsafe-plugin
38 | 2.17
39 |
40 |
41 | run-tests
42 |
43 | integration-test
44 | verify
45 |
46 |
47 |
48 | http://${docker.containers.cache.ports.80/tcp.host}:${docker.containers.cache.ports.80/tcp.port}
49 |
50 |
51 |
52 |
53 |
54 |
55 | @project.groupId@
56 | @project.artifactId@
57 | @project.version@
58 |
59 |
60 | build
61 |
62 | build-images
63 |
64 |
65 | true
66 |
67 |
68 | nginx
69 | ${project.basedir}/src/test/resources/Dockerfile
70 | nginx-name:tahahag
71 | true
72 |
73 |
74 |
75 |
76 |
77 | start
78 |
79 | true
80 |
81 |
82 | Debian
83 | debian:wheezy
84 |
85 |
86 | BusyBox
87 | busybox
88 |
89 |
90 | cache
91 | nginx
92 |
93 |
94 |
95 |
96 | start-containers
97 |
98 |
99 |
100 | stop
101 |
102 | true
103 |
104 |
105 | stop-containers
106 |
107 |
108 |
109 | verify
110 |
111 | verify
112 |
113 |
114 |
115 |
116 |
117 | maven-compiler-plugin
118 | 3.1
119 |
120 | ${maven.compiler.source}
121 | ${maven.compiler.target}
122 | ${project.build.sourceEncoding}
123 |
124 |
125 |
126 | maven-deploy-plugin
127 | 2.8.1
128 |
129 | true
130 |
131 |
132 |
133 | maven-enforcer-plugin
134 | 1.3.1
135 |
136 |
137 | enforce-maven-version
138 |
139 | enforce
140 |
141 |
142 |
143 |
144 | @maven.required.version@
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # version 5.0.0
2 | - **NOTE**: You now need at least Docker 1.9 to use this plugin, due to the support for buildargs. Some CVE's also got fixed, so you probably want to upgrade that Docker Engine regardless. :-)
3 | - You can now maintain your docker registry credentials in maven's settings.xml. Thanks to [Cedric Thiebault](https://github.com/cthiebault) for the pull request!
4 | - Added the ability to set a Mac address for a started container. Thanks to [Mark Collin](https://github.com/Ardesco) for making the pull request!
5 | - Added the ability to pass build args to the Dockerfile when building an image. This should help with parametrizing your Dockerfiles while still having them be useful outside of Maven. Thanks to [Mark Collin](https://github.com/Ardesco) for making the pull request!
6 | - **BUGFIX**: Plugin now properly removes created volumes when deleting a container. I apologize profoundly to any disks who were hurt in the last 4 versions.
7 | - **BUGFIX**: Sometimes file names are long, sometimes they are so long that certain maven plugins fall over.. The plugin should now be able to handle adding files with paths longer than 100 bytes. Especially useful when you're all about organizing your hard drive into many folders!
8 | - Plugin should be more resilient when docker logging drivers lie about the amount of characters to grab from the stream. (Don't ask...)
9 |
10 | # version 4.2.1
11 | - Fixed backwards compatibility with docker daemons older than 1.10, the daemon should now be able to figure out the built image IDs again. Sorry!
12 |
13 | # version 4.2.0
14 | - You can now add maven dependencies to your docker images. Thanks to [Mark Collin](https://github.com/Ardesco) for making the pull request!
15 | - Fixed building an image under Docker 1.10, the output of a quiet build had significantly changed, so the plugin got all confused and basically gave up. Poor plugin. :( The plugin should now be able to decrypt the fairly cryptic statement "sha256:{insert random uuid}"
16 |
17 | # version 4.1.1
18 | - Files added to the tar-ball sent to the docker daemon are no longer fully loaded into memory. The plugin should no longer need hundreds of MegaBytes of memory to work with big files. :-) Sorry! And thanks to [Cedric Thiebault](https://github.com/cthiebault) for fixing!
19 |
20 | # version 4.1.0
21 | - Now passes authentication header to build image task to allow pulling images from a private repository.
22 | - Now allows you to persist the logs for all containers to a certain folder.
23 |
24 | # version 4.0.0
25 | - Now needs Java8 to run, Java7 has been deprecated for a while
26 | - The plugin now uses the "stop" instead of "kill" command to stop containers in the `post-integration-test` phase.
27 | - Added the ability to commit containers after running them in the `post-integration-test` phase.
28 |
29 | # version 3.1.0
30 | - Now outputs the daemon responses while building an image.
31 |
32 | # version 3.0.1
33 | - Development: Fixed the VerifyHostnameSetIT which generated a NPE because of a non-existent logger.
34 | - Tagging is now "forced", untagging a previous image with the same tag.
35 | - Fixed the exception message when tagging an image fails.
36 |
37 | # version 3.0
38 | - Docker images are now constructed somewhat differently. You have you specify the Dockerfile as a special entry. Files
39 | in the Dockerfile are now called artifacts and you can specify where in the tar they will be placed. It's now possible to
40 | point to whole folders and have those added to the tar ball in the build phase. Just put the path to the folder in
41 | `` tags.
42 | - Added the ability to set the hostname of a container
43 |
44 | # version 2.3
45 | - Docker 1.3: Added support for HTTPS encryption, fixing connection with Boot2docker 1.3.
46 |
47 | # version 2.2
48 | - Changed the default port for the http interface to the IANA port for docker: 2375. (thanks Erno Tukia!)
49 | - Fixed pulling images from a private repository. (thanks Erno Tukia!)
50 |
51 | # version 2.1.1
52 | - Bug fix: Image names can now contain hyphens and dots and get properly pushed. (thanks Erno Tukia!)
53 |
54 | # version 2.1
55 | - Fixed a bug in `start-container` goal that crashed the plugin if a container had `waitForStartup` set, but failed to
56 | start
57 | - `push-images` goal no longer allows you to try to push an image without a name.
58 | - DEV: the build now starts a docker registry in docker so you can integration test against a registry too.
59 | - A container now waits with starting up before linked containers have finished starting up.
60 | - Environment variables can now be specified for a starting container (thanks Scott Stevelinck!)
61 |
62 | # version 2.0
63 | - The plugin now targets v1.12 of the Docker remote API, which shipped with version 1.0 of Docker. So from this version
64 | on, you will need at least version 1.0 of the docker daemon.
65 | - The plugin now enables you to scan the stderr & stdout of a container for a certain phrase that indicates successful
66 | initialization of the container. This phrase can be any regular expression and it is globally matched.
67 | - The plugin now allows you to link containers that you start.
68 |
69 | # version 1.6
70 | - The plugin now forces at least Maven 3.1.1, because else the plugin will not function well
71 | - The plugin now checks for duplicate image IDs, failing the build if the same image ID is used twice
72 | - The plugin now checks for duplicate container IDs, failing the build if the same container ID is used twice
73 | - Fixed Push & Tag for private repositories that got broken in 1.5
74 | - Upgraded maven plugin/api dependencies to the version that comes with Maven 3.1.1
75 |
--------------------------------------------------------------------------------
/src/broken-it/push-with-creds-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | net.wouterdanes.docker.it
7 | push-with-creds-it
8 | 1.0-SNAPSHOT
9 |
10 | A simple IT verifying the basic use case.
11 |
12 |
13 | UTF-8
14 | 1.7
15 | 1.7
16 |
17 |
18 |
19 |
20 | junit
21 | junit
22 | 4.8.2
23 | test
24 |
25 |
26 | org.apache.httpcomponents
27 | httpclient
28 | 4.3.3
29 | test
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.apache.maven.plugins
37 | maven-failsafe-plugin
38 | 2.17
39 |
40 |
41 | run-tests
42 |
43 | integration-test
44 | verify
45 |
46 |
47 |
48 | http://${docker.containers.cache.ports.80/tcp.host}:${docker.containers.cache.ports.80/tcp.port}
49 |
50 |
51 |
52 |
53 |
54 |
55 | @project.groupId@
56 | @project.artifactId@
57 | @project.version@
58 |
59 |
60 | build
61 |
62 | build-images
63 |
64 |
65 |
66 |
67 | nginx
68 | ${project.basedir}/src/test/resources/Dockerfile
69 | drek:latest
70 | true
71 | true
72 | ${docker.registry}
73 |
74 |
75 | nginxier
76 | ${project.basedir}/src/test/resources/Dockerfile
77 | ${docker.registry}/nginxier:latest
78 | true
79 | true
80 |
81 |
82 |
83 |
84 |
85 | start
86 |
87 |
88 |
89 | cache
90 | nginx
91 |
92 |
93 |
94 |
95 | start-containers
96 |
97 |
98 |
99 | stop
100 |
101 | stop-containers
102 |
103 |
104 |
105 | verify
106 |
107 | verify
108 |
109 |
110 |
111 | push
112 |
113 | push-images
114 |
115 |
116 |
117 |
118 |
119 | maven-compiler-plugin
120 | 3.1
121 |
122 | ${maven.compiler.source}
123 | ${maven.compiler.target}
124 | ${project.build.sourceEncoding}
125 |
126 |
127 |
128 | maven-deploy-plugin
129 | 2.8.1
130 |
131 | true
132 |
133 |
134 |
135 | maven-enforcer-plugin
136 | 1.3.1
137 |
138 |
139 | enforce-maven-version
140 |
141 | enforce
142 |
143 |
144 |
145 |
146 | @maven.required.version@
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/remoteapi/ImagesService.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi;
19 |
20 | import com.google.gson.JsonElement;
21 | import com.google.gson.JsonObject;
22 | import com.google.gson.JsonStreamParser;
23 | import net.wouterdanes.docker.remoteapi.model.ImageDescriptor;
24 |
25 | import javax.ws.rs.WebApplicationException;
26 | import javax.ws.rs.client.WebTarget;
27 | import javax.ws.rs.core.MediaType;
28 | import javax.ws.rs.core.Response;
29 | import java.io.BufferedReader;
30 | import java.io.InputStream;
31 | import java.io.InputStreamReader;
32 | import java.util.Optional;
33 |
34 | /**
35 | * This class is responsible for talking to the Docker Remote API "images" endpoint.
See
37 | * http://docs.docker.io/reference/api/docker_remote_api_v1.12/#22-images
38 | */
39 | public class ImagesService extends BaseService {
40 |
41 | public ImagesService(String dockerApiRoot) {
42 | super(dockerApiRoot, "/images");
43 | }
44 |
45 | public void deleteImage(final String imageId) {
46 | try {
47 | getServiceEndPoint()
48 | .path(imageId)
49 | .request(MediaType.APPLICATION_JSON_TYPE)
50 | .delete(String.class);
51 | } catch (WebApplicationException e) {
52 | throw makeImageTargetingException("Cannot remove image", e);
53 | }
54 | }
55 |
56 | public String pullImage(final String image) {
57 | ImageDescriptor descriptor = new ImageDescriptor(image);
58 |
59 | WebTarget target = getServiceEndPoint()
60 | .path("create");
61 |
62 | target = target.queryParam("fromImage", descriptor.getRegistryRepositoryAndImage());
63 |
64 | if (descriptor.getTag().isPresent()) {
65 | target = target.queryParam("tag", descriptor.getTag().get());
66 | }
67 |
68 | Response response = target.request()
69 | .header(REGISTRY_AUTH_HEADER, getRegistryAuthHeaderValue())
70 | .accept(MediaType.APPLICATION_JSON_TYPE)
71 | .post(null);
72 |
73 | InputStream inputStream = (InputStream) response.getEntity();
74 |
75 | parseStreamToDisplayImageDownloadStatus(inputStream);
76 |
77 | return response.readEntity(String.class);
78 | }
79 |
80 | private static void parseStreamToDisplayImageDownloadStatus(final InputStream inputStream) {
81 | InputStreamReader isr = new InputStreamReader(inputStream);
82 | BufferedReader reader = new BufferedReader(isr);
83 |
84 | JsonStreamParser parser = new JsonStreamParser(reader);
85 |
86 | while (parser.hasNext()) {
87 | JsonElement element = parser.next();
88 | JsonObject object = element.getAsJsonObject();
89 | if (object.has("status")) {
90 | System.out.print(".");
91 | }
92 | if (object.has("error")) {
93 | System.err.println("ERROR: " + object.get("error").getAsString());
94 | }
95 | }
96 | System.out.println("");
97 | }
98 |
99 | public String pushImage(String nameAndTag) {
100 | try {
101 | WebTarget target = createPushRequestFromTag(nameAndTag);
102 |
103 | return target.request()
104 | .header(REGISTRY_AUTH_HEADER, getRegistryAuthHeaderValue())
105 | .accept(MediaType.APPLICATION_JSON_TYPE)
106 | .post(null, String.class);
107 |
108 | } catch (WebApplicationException e) {
109 | throw makeImageTargetingException(nameAndTag, e);
110 |
111 | }
112 | }
113 |
114 | public void tagImage(final String imageId, final String nameAndTag) {
115 | ImageDescriptor descriptor = new ImageDescriptor(nameAndTag);
116 |
117 | WebTarget target = getServiceEndPoint()
118 | .path(imageId)
119 | .path("tag")
120 | .queryParam("repo", descriptor.getRegistryRepositoryAndImage())
121 | .queryParam("force", 1);
122 |
123 | Optional targetTag = descriptor.getTag();
124 | if (targetTag.isPresent()) {
125 | target = target.queryParam("tag", targetTag.get());
126 | }
127 |
128 | Response response = target.request()
129 | .accept(MediaType.APPLICATION_JSON_TYPE)
130 | .post(null);
131 |
132 | Response.StatusType statusInfo = response.getStatusInfo();
133 |
134 | response.close();
135 |
136 | checkImageTargetingResponse(imageId, statusInfo);
137 | }
138 |
139 | private WebTarget createPushRequestFromTag(final String nameAndTag) {
140 | ImageDescriptor descriptor = new ImageDescriptor(nameAndTag);
141 | WebTarget target = getServiceEndPoint()
142 | .path(descriptor.getRegistryRepositoryAndImage())
143 | .path("push");
144 |
145 | if (descriptor.getTag().isPresent()) {
146 | target = target.queryParam("tag", descriptor.getTag().get());
147 | }
148 |
149 | return target;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/test/java/net/wouterdanes/docker/remoteapi/ImageDescriptorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi;
19 |
20 | import org.junit.Test;
21 |
22 | import net.wouterdanes.docker.remoteapi.model.ImageDescriptor;
23 | import static junit.framework.Assert.assertEquals;
24 |
25 | public class ImageDescriptorTest {
26 |
27 | @Test
28 | public void testThatElementsAreDetected() throws Exception {
29 | assertDescriptor("ubuntu:precise", "ubuntu:precise", null, null, "ubuntu", "precise",
30 | "ubuntu", "ubuntu", "ubuntu:precise");
31 | assertDescriptor("ubuntu", "ubuntu", null, null, "ubuntu", null,
32 | "ubuntu", "ubuntu", "ubuntu");
33 | assertDescriptor("tutum.co/ubuntu", "tutum.co/ubuntu", "tutum.co", null, "ubuntu", null,
34 | "tutum.co/ubuntu", "ubuntu", "ubuntu");
35 | assertDescriptor("tutum.co/ubuntu:precise", "tutum.co/ubuntu:precise", "tutum.co", null, "ubuntu", "precise",
36 | "tutum.co/ubuntu", "ubuntu", "ubuntu:precise");
37 | assertDescriptor("wouter/ubuntu", "wouter/ubuntu", null, "wouter", "ubuntu", null,
38 | "wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu");
39 | assertDescriptor("tutum.co/wouter/ubuntu", "tutum.co/wouter/ubuntu", "tutum.co", "wouter", "ubuntu", null,
40 | "tutum.co/wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu");
41 | assertDescriptor("tutum.co/wouter/ubuntu:precise",
42 | "tutum.co/wouter/ubuntu:precise", "tutum.co", "wouter", "ubuntu", "precise",
43 | "tutum.co/wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu:precise");
44 | assertDescriptor("wouter/ubuntu:precise", "wouter/ubuntu:precise", null, "wouter", "ubuntu", "precise",
45 | "wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu:precise");
46 | assertDescriptor("abde1231adc", "abde1231adc", null, null, "abde1231adc", null,
47 | "abde1231adc", "abde1231adc", "abde1231adc");
48 |
49 | // support registries with port numbers
50 | assertDescriptor("tutum.co:5000/ubuntu",
51 | "tutum.co:5000/ubuntu", "tutum.co:5000", null, "ubuntu", null,
52 | "tutum.co:5000/ubuntu", "ubuntu", "ubuntu");
53 | assertDescriptor("tutum.co:5000/ubuntu:precise",
54 | "tutum.co:5000/ubuntu:precise", "tutum.co:5000", null, "ubuntu", "precise",
55 | "tutum.co:5000/ubuntu", "ubuntu", "ubuntu:precise");
56 | assertDescriptor("tutum.co:5000/wouter/ubuntu",
57 | "tutum.co:5000/wouter/ubuntu", "tutum.co:5000", "wouter", "ubuntu", null,
58 | "tutum.co:5000/wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu");
59 | assertDescriptor("tutum.co:5000/wouter/ubuntu:precise",
60 | "tutum.co:5000/wouter/ubuntu:precise", "tutum.co:5000", "wouter", "ubuntu", "precise",
61 | "tutum.co:5000/wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu:precise");
62 | // more complicated URL
63 | assertDescriptor("index_01-reg.tutum.co:5000/wouter/ubuntu:precise",
64 | "index_01-reg.tutum.co:5000/wouter/ubuntu:precise", "index_01-reg.tutum.co:5000", "wouter", "ubuntu", "precise",
65 | "index_01-reg.tutum.co:5000/wouter/ubuntu", "wouter/ubuntu", "wouter/ubuntu:precise");
66 |
67 | // check for image names with "." and "-"
68 | assertDescriptor("localhost:5000/library/ubuntu-precise", "localhost:5000/library/ubuntu-precise",
69 | "localhost:5000", "library", "ubuntu-precise", null, "localhost:5000/library/ubuntu-precise",
70 | "library/ubuntu-precise", "library/ubuntu-precise");
71 | assertDescriptor("localhost:5000/library/ubuntu.precise", "localhost:5000/library/ubuntu.precise",
72 | "localhost:5000", "library", "ubuntu.precise", null, "localhost:5000/library/ubuntu.precise",
73 | "library/ubuntu.precise", "library/ubuntu.precise");
74 |
75 | // check for tags "." and "-" in them
76 | assertDescriptor("ubuntu:14.04", "ubuntu:14.04", null, null, "ubuntu", "14.04",
77 | "ubuntu", "ubuntu", "ubuntu:14.04");
78 | assertDescriptor("ubuntu:mark-2", "ubuntu:mark-2", null, null, "ubuntu", "mark-2",
79 | "ubuntu", "ubuntu", "ubuntu:mark-2");
80 | }
81 |
82 | private static void assertDescriptor(String qualifier,
83 | String id, String registry, String repository,
84 | String image, String tag, String registryRepoAndImage, String repoAndImage, String repoImageAndTag) {
85 | ImageDescriptor descriptor = new ImageDescriptor(qualifier);
86 | assertEquals("Id should be correct", id, descriptor.getId());
87 | assertEquals("Registry should be correct", registry, descriptor.getRegistry().orElse(null));
88 | assertEquals("Repository should be correct", repository, descriptor.getRepository().orElse(null));
89 | assertEquals("Image should be correct", image, descriptor.getImage());
90 | assertEquals("Tag should be correct", tag, descriptor.getTag().orElse(null));
91 | assertEquals("Repository+Image should be correct", repoAndImage, descriptor.getRepositoryAndImage());
92 | assertEquals("Repository+Image+Tag should be correct", repoImageAndTag, descriptor.getRepositoryImageAndTag());
93 | assertEquals("Registry, Repository+Image should be correct", registryRepoAndImage, descriptor.getRegistryRepositoryAndImage());
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/net/wouterdanes/docker/remoteapi/ContainersService.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.remoteapi;
19 |
20 | import net.wouterdanes.docker.remoteapi.exception.ContainerNotFoundException;
21 | import net.wouterdanes.docker.remoteapi.exception.DockerException;
22 | import net.wouterdanes.docker.remoteapi.model.ContainerCreateRequest;
23 | import net.wouterdanes.docker.remoteapi.model.ContainerCreateResponse;
24 | import net.wouterdanes.docker.remoteapi.model.ContainerInspectionResult;
25 | import net.wouterdanes.docker.remoteapi.model.ContainerStartRequest;
26 |
27 | import javax.ws.rs.HttpMethod;
28 | import javax.ws.rs.WebApplicationException;
29 | import javax.ws.rs.client.Entity;
30 | import javax.ws.rs.core.MediaType;
31 | import javax.ws.rs.core.Response;
32 | import java.nio.ByteBuffer;
33 | import java.nio.ByteOrder;
34 | import java.nio.charset.Charset;
35 |
36 | /**
37 | * This class is responsible for talking to the Docker Remote API "containers" endpoint.
See
39 | * http://docs.docker.io/reference/api/docker_remote_api_v1.12/#21-containers
40 | */
41 | public class ContainersService extends BaseService {
42 |
43 | public ContainersService(String dockerApiRoot) {
44 | super(dockerApiRoot, "/containers");
45 | }
46 |
47 | public String createContainer(ContainerCreateRequest containerCreateRequest) {
48 | String createResponseStr;
49 | try {
50 | createResponseStr = getServiceEndPoint()
51 | .path("/create")
52 | .request(MediaType.APPLICATION_JSON_TYPE)
53 | .post(Entity.entity(toJson(containerCreateRequest), MediaType.APPLICATION_JSON_TYPE), String.class);
54 | } catch (WebApplicationException e) {
55 | throw makeImageTargetingException(containerCreateRequest.getImage(), e);
56 | }
57 | ContainerCreateResponse createResponse = toObject(createResponseStr, ContainerCreateResponse.class);
58 | return createResponse.getId();
59 | }
60 |
61 | public void deleteContainer(String id) {
62 | Response response = getServiceEndPoint()
63 | .path(id)
64 | .queryParam("v", 1)
65 | .request()
66 | .delete();
67 |
68 | Response.StatusType statusInfo = response.getStatusInfo();
69 | response.close();
70 |
71 | checkContainerTargetingResponse(id, statusInfo);
72 | }
73 |
74 | public String getLogs(final String containerId) {
75 | byte[] bytes = getServiceEndPoint()
76 | .path(containerId)
77 | .path("logs")
78 | .queryParam("stdout", 1)
79 | .queryParam("stderr", 1)
80 | .request("application/vnd.docker.raw-stream")
81 | .get(byte[].class);
82 |
83 | // To see how docker returns the logs and why it's parsed like this:
84 | // http://docs.docker.com/v1.2/reference/api/docker_remote_api_v1.14/#attach-to-a-container
85 | StringBuilder logs = new StringBuilder();
86 | ByteBuffer bb = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
87 |
88 | while (bb.hasRemaining()) {
89 | bb.position(bb.position() + 4);
90 | int frameLength = Math.min(bb.getInt(), bb.remaining());
91 | byte[] frame = new byte[frameLength];
92 | bb.get(frame);
93 | logs.append(new String(frame, Charset.forName("UTF-8")));
94 | }
95 |
96 | return logs.toString();
97 | }
98 |
99 | public ContainerInspectionResult inspectContainer(final String containerId) {
100 | String json = getServiceEndPoint()
101 | .path(containerId)
102 | .path("json")
103 | .request(MediaType.APPLICATION_JSON_TYPE)
104 | .get(String.class);
105 |
106 | return toObject(json, ContainerInspectionResult.class);
107 | }
108 |
109 | public void killContainer(String id) {
110 | Response response = getServiceEndPoint()
111 | .path(id)
112 | .path("/kill")
113 | .request()
114 | .method(HttpMethod.POST);
115 |
116 | Response.StatusType statusInfo = response.getStatusInfo();
117 | response.close();
118 |
119 | checkContainerTargetingResponse(id, statusInfo);
120 | }
121 |
122 | public void startContainer(String id, ContainerStartRequest configuration) {
123 | Response response = getServiceEndPoint()
124 | .path(id)
125 | .path("/start")
126 | .request()
127 | .post(Entity.entity(toJson(configuration), MediaType.APPLICATION_JSON_TYPE));
128 |
129 | Response.StatusType statusInfo = response.getStatusInfo();
130 | response.close();
131 |
132 | checkContainerTargetingResponse(id, statusInfo);
133 | }
134 |
135 | public void stopContainer(String id) {
136 | Response response = getServiceEndPoint()
137 | .path(id)
138 | .path("/stop")
139 | .queryParam("t", 10)
140 | .request()
141 | .method(HttpMethod.POST);
142 |
143 | Response.StatusType statusInfo = response.getStatusInfo();
144 | response.close();
145 |
146 | checkContainerTargetingResponse(id, statusInfo);
147 | }
148 |
149 | private static void checkContainerTargetingResponse(final String id, final Response.StatusType statusInfo) {
150 | switch (statusInfo.getStatusCode()) {
151 | case 404:
152 | throw new ContainerNotFoundException(id);
153 | case 500:
154 | throw new DockerException(statusInfo.getReasonPhrase());
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/test/java/net/wouterdanes/docker/maven/BuildImageMojoTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 Wouter Danes
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | package net.wouterdanes.docker.maven;
19 |
20 | import net.wouterdanes.docker.provider.AbstractFakeDockerProvider;
21 | import net.wouterdanes.docker.provider.DockerExceptionThrowingDockerProvider;
22 | import net.wouterdanes.docker.provider.DockerProviderSupplier;
23 | import net.wouterdanes.docker.provider.model.ImageBuildConfiguration;
24 | import net.wouterdanes.docker.provider.model.PushableImage;
25 | import org.apache.maven.plugin.MojoExecutionException;
26 | import org.apache.maven.plugin.MojoFailureException;
27 | import org.junit.After;
28 | import org.junit.Before;
29 | import org.junit.Test;
30 | import org.mockito.Mockito;
31 |
32 | import java.util.*;
33 |
34 | import static org.junit.Assert.*;
35 | import static org.mockito.Matchers.any;
36 |
37 | public class BuildImageMojoTest {
38 |
39 | private static final String FAKE_PROVIDER_KEY = UUID.randomUUID().toString();
40 | private static final String IMAGEID = UUID.randomUUID().toString();
41 | private static final String STARTID = UUID.randomUUID().toString();
42 | private static final String NAMEANDTAG = UUID.randomUUID().toString();
43 | private static final String REGISTRY = UUID.randomUUID().toString();
44 | private static final String REGISTRYANDNAMEANDTAG = REGISTRY + "/" + NAMEANDTAG;
45 |
46 | private BuildImageMojo mojo = new BuildImageMojo();
47 |
48 | private ImageBuildConfiguration mockImage;
49 |
50 | @Before
51 | public void setUp() throws Exception {
52 | mojo.setPluginContext(new HashMap());
53 |
54 | FakeDockerProvider.instance = Mockito.mock(FakeDockerProvider.class);
55 | Mockito.when(FakeDockerProvider.instance.buildImage(any(ImageBuildConfiguration.class))).thenReturn(IMAGEID);
56 |
57 | DockerProviderSupplier.registerProvider(FAKE_PROVIDER_KEY, FakeDockerProvider.class);
58 | DockerExceptionThrowingDockerProvider.class.newInstance();
59 |
60 | // valid by default
61 | mockImage = Mockito.mock(ImageBuildConfiguration.class);
62 | Mockito.when(mockImage.getId()).thenReturn(STARTID);
63 | Mockito.when(mockImage.getNameAndTag()).thenReturn(NAMEANDTAG);
64 |
65 | List images = Collections.singletonList(mockImage);
66 |
67 | mojo.setImages(images);
68 | }
69 |
70 | @After
71 | public void tearDown() throws Exception {
72 | DockerProviderSupplier.removeProvider(FAKE_PROVIDER_KEY);
73 | }
74 |
75 | @Test
76 | public void testThatTheMojoBuildsAtLeastOneImageWhenAllImagesAreValid() throws Exception {
77 | executeMojo(FAKE_PROVIDER_KEY);
78 |
79 | Mockito.verify(FakeDockerProvider.instance, Mockito.atLeastOnce()).buildImage(any(ImageBuildConfiguration.class));
80 |
81 | assertTrue(mojo.getPluginErrors().isEmpty());
82 | assertImageNotEnqueuedForPush();
83 | }
84 |
85 | @Test
86 | public void testThatTheMojoLogsAnErrorWhenBuildingAnImageFails() throws Exception {
87 | executeMojo(DockerExceptionThrowingDockerProvider.PROVIDER_KEY);
88 |
89 | assertFalse(mojo.getPluginErrors().isEmpty());
90 | assertImageNotEnqueuedForPush();
91 | }
92 |
93 | @Test
94 | public void testThatTheMojoEnqueuesImageWithoutRegistryForPush() throws Exception {
95 | Mockito.when(mockImage.isPush()).thenReturn(true);
96 |
97 | executeMojo(FAKE_PROVIDER_KEY);
98 |
99 | assertTrue(mojo.getPluginErrors().isEmpty());
100 | assertImageEnqueuedForPush(NAMEANDTAG);
101 | }
102 |
103 | @Test
104 | public void testThatTheMojoEnqueuesImageWithRegistryForPush() throws Exception {
105 | Mockito.when(mockImage.isPush()).thenReturn(true);
106 | Mockito.when(mockImage.getRegistry()).thenReturn(REGISTRY);
107 |
108 | executeMojo(FAKE_PROVIDER_KEY);
109 |
110 | assertTrue(mojo.getPluginErrors().isEmpty());
111 | assertImageEnqueuedForPush(REGISTRYANDNAMEANDTAG);
112 | }
113 |
114 | @Test
115 | public void testThatTheMojoEnqueuesImageWithoutNameForPush() throws Exception {
116 | Mockito.when(mockImage.isPush()).thenReturn(true);
117 | Mockito.when(mockImage.getNameAndTag()).thenReturn(null);
118 |
119 | executeMojo(FAKE_PROVIDER_KEY);
120 |
121 | assertTrue(mojo.getPluginErrors().isEmpty());
122 | assertImageEnqueuedForPush(null);
123 | }
124 |
125 | @Test(expected = MojoExecutionException.class)
126 | public void testThatTheMojoThrowsAnExceptionWhenDuplicateImageIdsExist() throws Exception {
127 | List images = new ArrayList<>(2);
128 | images.add(mockImage);
129 | images.add(mockImage);
130 |
131 | mojo.setImages(images);
132 |
133 | mojo.execute();
134 | assertImageEnqueuedForPush(null);
135 | }
136 |
137 | private void executeMojo(String provider) throws MojoExecutionException, MojoFailureException {
138 | mojo.setProviderName(provider);
139 | mojo.execute();
140 | }
141 |
142 | private void assertImageNotEnqueuedForPush() {
143 | assertTrue(mojo.getImagesToPush().isEmpty());
144 | }
145 |
146 | private void assertImageEnqueuedForPush(String expectedNameAndTag) {
147 | assertEquals(1, mojo.getImagesToPush().size());
148 |
149 | PushableImage actual = mojo.getImagesToPush().get(0);
150 | assertEquals(BuildImageMojoTest.IMAGEID, actual.getImageId());
151 | assertEquals(expectedNameAndTag, actual.getNameAndTag().orElse(null));
152 | }
153 |
154 | public static class FakeDockerProvider extends AbstractFakeDockerProvider {
155 |
156 | private static FakeDockerProvider instance;
157 |
158 | @Override
159 | protected AbstractFakeDockerProvider getInstance() {
160 | return instance;
161 | }
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------