Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.dockerjava.api.command;

import java.io.InputStream;
import java.util.List;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -28,7 +29,21 @@ public interface SaveImageCmd extends SyncDockerCmd<InputStream> {
SaveImageCmd withTag(String tag);

/**
* Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent connection leaks.
* Confines the saved image to the specified platform or platforms (if invoked repeatedly).
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_48} for one platform and
* {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_52} for multiple platforms.
* @return this
*/
SaveImageCmd withPlatform(String platform);

/**
* Gets the platforms that were added by {@link #withPlatform(String)}.
* @return platforms the saved image should be confined to.
*/
List<String> getPlatforms();

/**
* It's the responsibility of the caller to consume and/or close the {@link InputStream} to prevent connection leaks.
*
* @throws NotFoundException
* No such image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ interface TaggedImage {
*/
List<TaggedImage> getImages();

/**
* Confines the saved image to the specified platform or platforms (if invoked repeatedly).
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_48} for one platform and.
* @return this
*/
SaveImagesCmd withPlatform(String platform);

/**
* Gets the platform that was set by {@link #withPlatform(String)}.
* @return platform the saved images should be confined to.
*/
String getPlatform();

/**
* Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent connection leaks.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public class RemoteApiVersion implements Serializable {
public static final RemoteApiVersion VERSION_1_42 = RemoteApiVersion.create(1, 42);
public static final RemoteApiVersion VERSION_1_43 = RemoteApiVersion.create(1, 43);
public static final RemoteApiVersion VERSION_1_44 = RemoteApiVersion.create(1, 44);
public static final RemoteApiVersion VERSION_1_48 = RemoteApiVersion.create(1, 48);
public static final RemoteApiVersion VERSION_1_52 = RemoteApiVersion.create(1, 52);


/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.github.dockerjava.core.command;

import java.io.InputStream;
import java.util.List;
import java.util.Objects;

import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.exception.NotFoundException;
import com.google.common.collect.ImmutableList;

public class SaveImageCmdImpl extends AbstrDockerCmd<SaveImageCmd, InputStream> implements SaveImageCmd {
private String name;

private String tag;

private final ImmutableList.Builder<String> platforms = ImmutableList.builder();

public SaveImageCmdImpl(SaveImageCmd.Exec exec, String name) {
super(exec);
withName(name);
Expand Down Expand Up @@ -54,4 +58,16 @@ public SaveImageCmd withTag(String tag) {
public InputStream exec() throws NotFoundException {
return super.exec();
}

@Override
public SaveImageCmd withPlatform(String platform) {
platforms.add(platform);
return this;
}

@Override
public List<String> getPlatforms() {
return platforms.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public String toString() {
}
}

private String platform;

private final ImmutableList.Builder<TaggedImage> taggedImages = ImmutableList.builder();

public SaveImagesCmdImpl(final SaveImagesCmd.Exec exec) {
Expand All @@ -43,7 +45,16 @@ public SaveImagesCmd withImage(@Nonnull final String name, @Nonnull final String
return this;
}

@Override
public SaveImagesCmd withPlatform(String platform) {
this.platform = platform;
return this;
}

@Override
public String getPlatform() {
return platform;
}

@Override
public List<TaggedImage> getImages() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.MediaType;
import com.github.dockerjava.core.WebTarget;
import com.github.dockerjava.core.util.PlatformUtil;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

public class SaveImageCmdExec extends AbstrSyncDockerCmdExec<SaveImageCmd, InputStream> implements SaveImageCmd.Exec {
private static final Logger LOGGER = LoggerFactory.getLogger(SaveImageCmdExec.class);
Expand All @@ -28,6 +31,13 @@ protected InputStream execute(SaveImageCmd command) {
WebTarget webResource = getBaseResource().
path("/images/" + name + "/get");

Set<String> platforms = new HashSet<>(command.getPlatforms());
if (!platforms.isEmpty()) {
for (String platform : platforms) {
webResource = webResource.queryParamsJsonMap("platform", PlatformUtil.platformMap(platform));
}
}

LOGGER.trace("GET: {}", webResource);
return webResource.request().accept(MediaType.APPLICATION_JSON).get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.MediaType;
import com.github.dockerjava.core.WebTarget;
import com.github.dockerjava.core.util.PlatformUtil;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -29,10 +30,14 @@ protected InputStream execute(SaveImagesCmd command) {
for (SaveImagesCmd.TaggedImage image : images) {
queryParamSet.add(image.asString());
}
final WebTarget webResource = getBaseResource()
WebTarget webResource = getBaseResource()
.path("/images/get")
.queryParamsSet("names", queryParamSet.build());

if (command.getPlatform() != null) {
webResource = webResource.queryParamsJsonMap("platform", PlatformUtil.platformMap(command.getPlatform()));
}

LOGGER.trace("GET: {}", webResource);
return webResource.request().accept(MediaType.APPLICATION_JSON).get();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.github.dockerjava.core.util;

import java.util.HashMap;
import java.util.Map;

public class PlatformUtil {

private PlatformUtil() {
}

public static Map<String, String> platformMap(String platform) {

HashMap<String, String> platformMap = new HashMap<>();
if (platform == null) {
return platformMap;
}
String[] r = platform.split("/");
if (r.length > 0) {
platformMap.put("os", r[0]);
}
if (r.length > 1) {
platformMap.put("architecture", r[1]);
}
if (r.length > 2) {
platformMap.put("variant", r[2]);
}

return platformMap;

}

}
7 changes: 7 additions & 0 deletions docker-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${apache-compress.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class CustomCommandIT extends CmdIT {

@Test
public void testCustomCommand() throws Exception {
DockerHttpClient httpClient = CmdIT.createDockerHttpClient(DockerRule.config(null));
DockerHttpClient httpClient = CmdIT.createDockerHttpClient(DockerRule.config(null).build());

Assume.assumeNotNull(httpClient);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package com.github.dockerjava.cmd;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DockerRule;
import com.github.dockerjava.core.RemoteApiVersion;
import lombok.SneakyThrows;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static com.github.dockerjava.utils.TestUtils.getVersion;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

public class SaveImageCmdIT extends CmdIT {
Expand All @@ -29,6 +43,83 @@ public void saveImage() throws Exception {
) {
assertThat(image2.read(), not(-1));
}

ObjectMapper objectMapper = new ObjectMapper();

if (getVersion(dockerRule.getClient()).isGreaterOrEqual(RemoteApiVersion.VERSION_1_48)) {

try (DockerClient c = createDockerClient(DockerRule.config(null).withApiVersion(RemoteApiVersion.VERSION_1_48).build())) {

c.pullImageCmd("busybox").withTag("latest").withPlatform("linux/arm64").start().awaitCompletion();

Map<String, byte[]> tar;

try (InputStream inputStream = c.saveImageCmd("busybox").withTag("latest").withPlatform("linux/arm64").exec()) {
tar = loadTar(inputStream);
}

List<Map<String, Object>> list =
objectMapper.readValue(tar.get("manifest.json"), new TypeReference<List<Map<String, Object>>>() {});

assertThat(list.size(), is(1));

Map<String, Object> config = objectMapper.readValue(tar.get(list.get(0).get("Config").toString()), new TypeReference<Map<String, Object>>() {});

assertThat(config.get("architecture"), is("arm64"));
assertThat(config.get("os"), is("linux"));

}

}

// $TODO: test multi-platform save, but it requires containerd image store

}

@SneakyThrows
private Map<String, byte[]> loadTar(InputStream data) {
Map<String, byte[]> out = new LinkedHashMap<>();

try (TarArchiveInputStream tin = new TarArchiveInputStream(data)) {
TarArchiveEntry e;
while ((e = tin.getNextEntry()) != null) {
if (e.isDirectory()) {
continue;
}
String name = e.getName();
long sizeL = e.getSize();
if (sizeL < 0) {
throw new IOException("Negative size for tar entry: " + name + " size=" + sizeL);
}
if (sizeL > Integer.MAX_VALUE) {
throw new IOException("Tar entry too large to load into memory: " + name + " size=" + sizeL);
}
int size = (int) sizeL;

// Read exactly this entry's bytes.
byte[] bytes = readFully(tin, size);
out.put(name, bytes);

// TarArchiveInputStream aligns to 512-byte blocks internally; no manual skip needed.
}
}

return out;

}

private static byte[] readFully(InputStream in, int size) throws IOException {
byte[] buf = new byte[size];
int off = 0;
while (off < size) {
int r = in.read(buf, off, size - off);
if (r < 0) {
throw new IOException("Unexpected EOF while reading tar entry (" + off + "/" + size + " bytes)");
}
off += r;
}
return buf;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class DockerRule extends ExternalResource {

private final Set<String> createdVolumeNames = new HashSet<>();

private final DefaultDockerClientConfig config = config();
private final DefaultDockerClientConfig config = config().build();

public DockerClient newClient() {
DockerClientImpl dockerClient = CmdIT.createDockerClient(config);
Expand Down Expand Up @@ -168,19 +168,19 @@ protected void after() {
}
}

private static DefaultDockerClientConfig config() {
private static DefaultDockerClientConfig.Builder config() {
return config(null);
}

public static DefaultDockerClientConfig config(String password) {
public static DefaultDockerClientConfig.Builder config(String password) {
DefaultDockerClientConfig.Builder builder = DefaultDockerClientConfig.createDefaultConfigBuilder()
.withApiVersion(RemoteApiVersion.VERSION_1_44)
.withRegistryUrl("https://index.docker.io/v1/");
if (password != null) {
builder = builder.withRegistryPassword(password);
builder.withRegistryPassword(password);
}

return builder.build();
return builder;
}

public String buildImage(File baseDir) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class PrivateRegistryRule extends ExternalResource {
private String containerId;

public PrivateRegistryRule() {
this.dockerClient = CmdIT.createDockerClient(DockerRule.config(null));
this.dockerClient = CmdIT.createDockerClient(DockerRule.config(null).build());
}

public AuthConfig getAuthConfig() {
Expand Down
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
<hamcrest.library.version>2.2</hamcrest.library.version>
<hamcrest.jpa-matchers>1.8</hamcrest.jpa-matchers>
<lambdaj.version>2.3.3</lambdaj.version>
<mockito.version>3.3.0</mockito.version>
<mockito.version>3.3.0</mockito.version>
<apache-compress.version>1.28.0</apache-compress.version>


<maven-jar-plugin.version>3.0.2</maven-jar-plugin.version>
Expand Down