Skip to content

Commit c82b92d

Browse files
Łukasz WarchałKostyaSha
authored andcommitted
Allow multiple tags in build image command
Several tags for image can be defined by calling multiple time withTag() method of BuildImageCmd. In Netty implementation, the WebTarget class is modified to support several query parameteres with the same name. Fix docker-java#720
1 parent 75e9a2c commit c82b92d

File tree

7 files changed

+189
-32
lines changed

7 files changed

+189
-32
lines changed

src/main/java/com/github/dockerjava/api/command/BuildImageCmd.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.InputStream;
55
import java.net.URI;
66
import java.util.Map;
7+
import java.util.Set;
78

89
import javax.annotation.CheckForNull;
910
import javax.annotation.Nonnull;
@@ -34,10 +35,21 @@ public interface BuildImageCmd extends AsyncDockerCmd<BuildImageCmd, BuildRespon
3435

3536
/**
3637
* "t" in API
38+
*
39+
* @deprecated since docker API version 1.21 there can be multiple tags
40+
* specified so use {@link #getTags()}
3741
*/
3842
@CheckForNull
43+
@Deprecated
3944
String getTag();
4045

46+
/**
47+
* Multple "t" tags.
48+
* @since {@link RemoteApiVersion#VERSION_1_21}
49+
*/
50+
@CheckForNull
51+
Set<String> getTags();
52+
4153
/**
4254
* "remote" in API
4355
*/
@@ -109,8 +121,15 @@ public interface BuildImageCmd extends AsyncDockerCmd<BuildImageCmd, BuildRespon
109121

110122
// setters
111123

124+
/**
125+
* @deprecated since docker API version 1.21 there can be multiple tags
126+
* specified so use {@link #withTags(Set<String>)}
127+
*/
128+
@Deprecated
112129
BuildImageCmd withTag(String tag);
113130

131+
BuildImageCmd withTags(Set<String> tags);
132+
114133
BuildImageCmd withRemote(URI remote);
115134

116135
BuildImageCmd withBaseDirectory(File baseDirectory);

src/main/java/com/github/dockerjava/core/command/BuildImageCmdImpl.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,28 @@
88
import java.net.URI;
99
import java.util.HashMap;
1010
import java.util.Map;
11+
import java.util.Set;
1112

1213
import com.github.dockerjava.api.command.BuildImageCmd;
1314
import com.github.dockerjava.api.model.AuthConfigurations;
1415
import com.github.dockerjava.api.model.BuildResponseItem;
1516
import com.github.dockerjava.core.dockerfile.Dockerfile;
1617
import com.github.dockerjava.core.util.FilePathUtil;
1718

19+
import javax.annotation.CheckForNull;
20+
1821
/**
19-
*
2022
* Build an image from Dockerfile.
21-
*
2223
*/
2324
public class BuildImageCmdImpl extends AbstrAsyncDockerCmd<BuildImageCmd, BuildResponseItem> implements BuildImageCmd {
2425

2526
private InputStream tarInputStream;
2627

28+
@Deprecated
2729
private String tag;
2830

31+
private Set<String> tags;
32+
2933
private Boolean noCache;
3034

3135
private Boolean remove = true;
@@ -82,11 +86,17 @@ public BuildImageCmdImpl(BuildImageCmd.Exec exec, InputStream tarInputStream) {
8286

8387
// getters API
8488

89+
@Deprecated
8590
@Override
8691
public String getTag() {
8792
return tag;
8893
}
8994

95+
@CheckForNull
96+
public Set<String> getTags() {
97+
return tags;
98+
}
99+
90100
@Override
91101
public URI getRemote() {
92102
return remote;
@@ -178,13 +188,23 @@ public Long getShmsize() {
178188

179189
// setters
180190

191+
/**
192+
* @deprecated use #withTags()
193+
*/
194+
@Deprecated
181195
@Override
182196
public BuildImageCmdImpl withTag(String tag) {
183197
checkNotNull(tag, "Tag is null");
184198
this.tag = tag;
185199
return this;
186200
}
187201

202+
@Override
203+
public BuildImageCmd withTags(Set<String> tags) {
204+
this.tags = tags;
205+
return this;
206+
}
207+
188208
@Override
189209
public BuildImageCmd withRemote(URI remote) {
190210
this.remote = remote;

src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.github.dockerjava.jaxrs;
22

33
import static javax.ws.rs.client.Entity.entity;
4+
import static org.apache.commons.lang.StringUtils.isNotBlank;
45

56
import javax.ws.rs.client.Invocation;
67
import javax.ws.rs.client.WebTarget;
@@ -65,9 +66,15 @@ protected AbstractCallbackNotifier<BuildResponseItem> callbackNotifier(BuildImag
6566
if (dockerFilePath != null && command.getRemote() == null && !"Dockerfile".equals(dockerFilePath)) {
6667
webTarget = webTarget.queryParam("dockerfile", dockerFilePath);
6768
}
68-
if (command.getTag() != null) {
69+
70+
if (command.getTags() != null && !command.getTags().isEmpty()) {
71+
for (String t : command.getTags()) {
72+
webTarget = webTarget.queryParam("t", t);
73+
}
74+
} else if (isNotBlank(command.getTag())) {
6975
webTarget = webTarget.queryParam("t", command.getTag());
7076
}
77+
7178
if (command.getRemote() != null) {
7279
webTarget = webTarget.queryParam("remote", command.getRemote().toString());
7380
}

src/main/java/com/github/dockerjava/netty/WebTarget.java

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,59 @@
11
package com.github.dockerjava.netty;
22

3+
import java.io.IOException;
4+
import java.io.UnsupportedEncodingException;
5+
import java.net.URLEncoder;
6+
import java.nio.charset.Charset;
7+
import java.nio.charset.UnsupportedCharsetException;
38
import java.util.ArrayList;
49
import java.util.Arrays;
510
import java.util.List;
611
import java.util.Map;
12+
import java.util.Set;
713

14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
import com.google.common.collect.ImmutableSet;
16+
import io.netty.handler.codec.http.HttpConstants;
817
import org.apache.commons.lang.StringUtils;
918

1019
import com.google.common.collect.ImmutableList;
1120
import com.google.common.collect.ImmutableMap;
1221

1322
/**
14-
* This class is basically a replacement of javax.ws.rs.client.WebTarget to allow simpler migration of JAX-RS code to a netty based
23+
* This class is basically a replacement of {@link javax.ws.rs.client.WebTarget} to allow simpler migration of JAX-RS code to a netty based
1524
* implementation.
1625
*
1726
* @author Marcus Linke
1827
*/
1928
public class WebTarget {
29+
private static final ObjectMapper MAPPER = new ObjectMapper();
2030

2131
private final ChannelProvider channelProvider;
2232

2333
private final ImmutableList<String> path;
2434

2535
private final ImmutableMap<String, String> queryParams;
2636

37+
/**
38+
* Multiple values for the same name param.
39+
*/
40+
private final ImmutableMap<String, Set<String>> queryParamsSet;
41+
2742
private static final String PATH_SEPARATOR = "/";
2843

2944
public WebTarget(ChannelProvider channelProvider) {
30-
this(channelProvider, ImmutableList.<String>of(), ImmutableMap.<String, String>of());
45+
this(channelProvider, ImmutableList.<String>of(), ImmutableMap.<String, String>of(),
46+
ImmutableMap.<String, Set<String>>of());
3147
}
3248

3349
private WebTarget(ChannelProvider channelProvider,
3450
ImmutableList<String> path,
35-
ImmutableMap<String, String> queryParams) {
51+
ImmutableMap<String, String> queryParams,
52+
ImmutableMap<String, Set<String>> queryParamsSet) {
3653
this.channelProvider = channelProvider;
3754
this.path = path;
3855
this.queryParams = queryParams;
56+
this.queryParamsSet = queryParamsSet;
3957
}
4058

4159
public WebTarget path(String... components) {
@@ -45,15 +63,21 @@ public WebTarget path(String... components) {
4563
newPath.addAll(Arrays.asList(StringUtils.split(component, PATH_SEPARATOR)));
4664
}
4765

48-
return new WebTarget(channelProvider, newPath.build(), queryParams);
66+
return new WebTarget(channelProvider, newPath.build(), queryParams, queryParamsSet);
4967
}
5068

5169
public InvocationBuilder request() {
5270
String resource = PATH_SEPARATOR + StringUtils.join(path, PATH_SEPARATOR);
5371

5472
List<String> params = new ArrayList<>();
5573
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
56-
params.add(entry.getKey() + "=" + entry.getValue());
74+
params.add(entry.getKey() + "=" + encodeComponent(entry.getValue(), HttpConstants.DEFAULT_CHARSET));
75+
}
76+
77+
for (Map.Entry<String, Set<String>> entry : queryParamsSet.entrySet()) {
78+
for (String entryValueValue : entry.getValue()) {
79+
params.add(entry.getKey() + "=" + encodeComponent(entryValueValue, HttpConstants.DEFAULT_CHARSET));
80+
}
5781
}
5882

5983
if (!params.isEmpty()) {
@@ -63,21 +87,58 @@ public InvocationBuilder request() {
6387
return new InvocationBuilder(channelProvider, resource);
6488
}
6589

90+
/**
91+
* @see io.netty.handler.codec.http.QueryStringEncoder
92+
*/
93+
private static String encodeComponent(String s, Charset charset) {
94+
// TODO: Optimize me.
95+
try {
96+
return URLEncoder.encode(s, charset.name()).replace("+", "%20");
97+
} catch (UnsupportedEncodingException ignored) {
98+
throw new UnsupportedCharsetException(charset.name());
99+
}
100+
}
101+
66102
public WebTarget resolveTemplate(String name, Object value) {
67103
ImmutableList.Builder<String> newPath = ImmutableList.builder();
68104
for (String component : path) {
69105
component = component.replaceAll("\\{" + name + "\\}", value.toString());
70106
newPath.add(component);
71107
}
72-
return new WebTarget(channelProvider, newPath.build(), queryParams);
108+
return new WebTarget(channelProvider, newPath.build(), queryParams, queryParamsSet);
73109
}
74110

75111
public WebTarget queryParam(String name, Object value) {
76112
ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder().putAll(queryParams);
77113
if (value != null) {
78114
builder.put(name, value.toString());
79115
}
80-
return new WebTarget(channelProvider, path, builder.build());
116+
return new WebTarget(channelProvider, path, builder.build(), queryParamsSet);
117+
}
118+
119+
public WebTarget queryParamsSet(String name, Set<?> values) {
120+
ImmutableMap.Builder<String, Set<String>> builder = ImmutableMap.<String, Set<String>>builder().putAll(queryParamsSet);
121+
if (values != null) {
122+
ImmutableSet.Builder<String> valueBuilder = ImmutableSet.builder();
123+
for (Object value : values) {
124+
valueBuilder.add(value.toString());
125+
}
126+
builder.put(name, valueBuilder.build());
127+
}
128+
return new WebTarget(channelProvider, path, queryParams, builder.build());
129+
}
130+
131+
public WebTarget queryParamsJsonMap(String name, Map<String, String> values) {
132+
if (values != null && !values.isEmpty()) {
133+
try {
134+
// when param value is JSON string
135+
return queryParam(name, MAPPER.writeValueAsString(values));
136+
} catch (IOException e) {
137+
throw new RuntimeException(e);
138+
}
139+
} else {
140+
return this;
141+
}
81142
}
82143

83144
@Override
@@ -97,14 +158,22 @@ public boolean equals(Object o) {
97158
if (path != null ? !path.equals(webTarget.path) : webTarget.path != null) {
98159
return false;
99160
}
100-
return queryParams != null ? queryParams.equals(webTarget.queryParams) : webTarget.queryParams == null;
161+
if (queryParams != null ? !queryParams.equals(webTarget.queryParams) : webTarget.queryParams != null) {
162+
return false;
163+
}
164+
if (queryParamsSet != null ? !queryParamsSet.equals(webTarget.queryParamsSet) : webTarget.queryParamsSet != null) {
165+
return false;
166+
}
167+
168+
return true;
101169
}
102170

103171
@Override
104172
public int hashCode() {
105173
int result = channelProvider != null ? channelProvider.hashCode() : 0;
106174
result = 31 * result + (path != null ? path.hashCode() : 0);
107175
result = 31 * result + (queryParams != null ? queryParams.hashCode() : 0);
176+
result = 31 * result + (queryParamsSet != null ? queryParamsSet.hashCode() : 0);
108177
return result;
109178
}
110179
}

src/main/java/com/github/dockerjava/netty/exec/BuildImageCmdExec.java

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.github.dockerjava.netty.exec;
22

3-
import com.fasterxml.jackson.databind.ObjectMapper;
43
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
65

@@ -14,15 +13,12 @@
1413
import com.github.dockerjava.netty.MediaType;
1514
import com.github.dockerjava.netty.WebTarget;
1615

17-
import java.io.IOException;
18-
import java.util.Map;
16+
import static org.apache.commons.lang.StringUtils.isNotBlank;
1917

2018
public class BuildImageCmdExec extends AbstrAsyncDockerCmdExec<BuildImageCmd, BuildResponseItem> implements
2119
BuildImageCmd.Exec {
2220
private static final Logger LOGGER = LoggerFactory.getLogger(BuildImageCmdExec.class);
2321

24-
private static final ObjectMapper MAPPER = new ObjectMapper();
25-
2622
public BuildImageCmdExec(WebTarget baseResource, DockerClientConfig dockerClientConfig) {
2723
super(baseResource, dockerClientConfig);
2824
}
@@ -55,9 +51,13 @@ protected Void execute0(BuildImageCmd command, ResultCallback<BuildResponseItem>
5551
if (dockerFilePath != null && command.getRemote() == null && !"Dockerfile".equals(dockerFilePath)) {
5652
webTarget = webTarget.queryParam("dockerfile", dockerFilePath);
5753
}
58-
if (command.getTag() != null) {
59-
webTarget = webTarget.queryParam("t", command.getTag());
54+
55+
if (command.getTags() != null && !command.getTags().isEmpty()) {
56+
webTarget = webTarget.queryParamsSet("t", command.getTags());
57+
} else if (isNotBlank(command.getTag())) {
58+
webTarget = webTarget.queryParam("t", command.getTags());
6059
}
60+
6161
if (command.getRemote() != null) {
6262
webTarget = webTarget.queryParam("remote", command.getRemote().toString());
6363
}
@@ -86,13 +86,17 @@ protected Void execute0(BuildImageCmd command, ResultCallback<BuildResponseItem>
8686
webTarget = webTarget.queryParam("cpusetcpus", command.getCpusetcpus());
8787
}
8888

89-
webTarget = writeMap(webTarget, "buildargs", command.getBuildArgs());
89+
if (command.getBuildArgs() != null) {
90+
webTarget = webTarget.queryParamsJsonMap("buildargs", command.getBuildArgs());
91+
}
9092

9193
if (command.getShmsize() != null) {
9294
webTarget = webTarget.queryParam("shmsize", command.getShmsize());
9395
}
9496

95-
webTarget = writeMap(webTarget, "labels", command.getLabels());
97+
if (command.getLabels() != null) {
98+
webTarget = webTarget.queryParamsJsonMap("labels", command.getLabels());
99+
}
96100

97101
LOGGER.trace("POST: {}", webTarget);
98102

@@ -106,16 +110,4 @@ protected Void execute0(BuildImageCmd command, ResultCallback<BuildResponseItem>
106110

107111
return null;
108112
}
109-
110-
private WebTarget writeMap(WebTarget webTarget, String name, Map<String, String> value) {
111-
if (value != null && !value.isEmpty()) {
112-
try {
113-
return webTarget.queryParam(name, MAPPER.writeValueAsString(value));
114-
} catch (IOException e) {
115-
throw new RuntimeException(e);
116-
}
117-
} else {
118-
return webTarget;
119-
}
120-
}
121113
}

0 commit comments

Comments
 (0)