Skip to content
Merged
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
Expand Up @@ -15,12 +15,14 @@ import de.gesellix.docker.engine.DockerClientConfig
import de.gesellix.docker.engine.RequestMethod.DELETE
import de.gesellix.docker.engine.RequestMethod.GET
import de.gesellix.docker.engine.RequestMethod.POST
import de.gesellix.docker.remote.api.BuildInfo
import de.gesellix.docker.remote.api.BuildPruneResponse
import de.gesellix.docker.remote.api.ContainerConfig
import de.gesellix.docker.remote.api.HistoryResponseItem
import de.gesellix.docker.remote.api.IdResponse
import de.gesellix.docker.remote.api.Image
import de.gesellix.docker.remote.api.ImageDeleteResponseItem
import de.gesellix.docker.remote.api.ImageID
import de.gesellix.docker.remote.api.ImagePruneResponse
import de.gesellix.docker.remote.api.ImageSearchResponseItem
import de.gesellix.docker.remote.api.ImageSummary
Expand Down Expand Up @@ -169,6 +171,7 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
// with Dockerfiles relying on `buildx`. Maybe we can simply shell out to a locally installed docker cli,
// but that's something the user might solve themselves.
@Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class)
@JvmOverloads
fun imageBuild(
dockerfile: String?,
t: String?,
Expand Down Expand Up @@ -196,7 +199,8 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
platform: String?,
target: String?,
outputs: String?,
inputStream: InputStream
inputStream: InputStream,
callback: StreamCallback<BuildInfo?>? = null, timeoutMillis: Long? = null /*= 24.hours.toLongMilliseconds()*/
) {
val localVariableConfig = imageBuildRequestConfig(
dockerfile = dockerfile,
Expand Down Expand Up @@ -228,25 +232,25 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
inputStream = inputStream
)

val localVarResponse = requestStream<Any?>(
val localVarResponse = requestStream<BuildInfo?>(
localVariableConfig
)

val timeout = Duration.of(1, ChronoUnit.MINUTES)
// TODO collect events and/or extract the image id
// from either `aux ID=sha:.*`,
// or `stream Successfully built .*`,
// or `stream Successfully tagged .*` messages.
val callback = LoggingCallback<Any>()
val timeout = if (timeoutMillis == null) {
Duration.of(10, ChronoUnit.MINUTES)
} else {
Duration.of(timeoutMillis, ChronoUnit.MILLIS)
}
val actualCallback = callback ?: LoggingCallback<BuildInfo?>()

return when (localVarResponse.responseType) {
ResponseType.Success -> {
runBlocking {
launch {
withTimeout(timeout.toMillis()) {
callback.onStarting(this@launch::cancel)
(localVarResponse as SuccessStream<*>).data.collect { callback.onNext(it) }
callback.onFinished()
actualCallback.onStarting(this@launch::cancel)
((localVarResponse as SuccessStream<*>).data as Flow<BuildInfo?>).collect { actualCallback.onNext(it) }
actualCallback.onFinished()
}
}
}
Expand All @@ -265,6 +269,30 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
}
}

fun getImageId(buildInfos: List<BuildInfo>): ImageID? {
val firstAux = buildInfos.stream()
.filter { (_, _, _, _, _, _, _, aux): BuildInfo -> aux != null }
.findFirst()
if (firstAux.isPresent) {
return firstAux.get().aux
} else {
val idFromStream = buildInfos.stream()
.filter { (_, stream): BuildInfo -> stream?.contains("Successfully built ")!! }
.findFirst()
return if (idFromStream.isPresent) {
ImageID(idFromStream.get().stream!!.removePrefix("Successfully built ").replaceAfter('\n', "").trim())
} else {
val tagFromStream = buildInfos.stream()
.filter { (_, stream): BuildInfo -> stream?.contains("Successfully tagged ")!! }
.findFirst()
tagFromStream.map { (_, stream): BuildInfo ->
ImageID(stream!!.removePrefix("Successfully tagged ").replaceAfter('\n', "").trim())
}
.orElse(null)
}
}
}

/**
* To obtain the request config of the operation imageBuild
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.squareup.moshi.Moshi;
import de.gesellix.docker.builder.BuildContextBuilder;
import de.gesellix.docker.remote.api.BuildInfo;
import de.gesellix.docker.remote.api.BuildPruneResponse;
import de.gesellix.docker.remote.api.ContainerCreateRequest;
import de.gesellix.docker.remote.api.ContainerCreateResponse;
Expand All @@ -10,8 +11,10 @@
import de.gesellix.docker.remote.api.IdResponse;
import de.gesellix.docker.remote.api.Image;
import de.gesellix.docker.remote.api.ImageDeleteResponseItem;
import de.gesellix.docker.remote.api.ImageID;
import de.gesellix.docker.remote.api.ImageSearchResponseItem;
import de.gesellix.docker.remote.api.ImageSummary;
import de.gesellix.docker.remote.api.core.StreamCallback;
import de.gesellix.docker.remote.api.testutil.DockerEngineAvailable;
import de.gesellix.docker.remote.api.testutil.DockerRegistry;
import de.gesellix.docker.remote.api.testutil.HttpTestServer;
Expand All @@ -23,6 +26,8 @@
import de.gesellix.testutil.ResourceReader;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
Expand All @@ -33,10 +38,15 @@
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static de.gesellix.docker.remote.api.testutil.Constants.LABEL_KEY;
import static de.gesellix.docker.remote.api.testutil.Constants.LABEL_VALUE;
Expand All @@ -48,11 +58,14 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@DockerEngineAvailable
class ImageApiIntegrationTest {

private static Logger log = LoggerFactory.getLogger(ImageApiIntegrationTest.class);

@InjectDockerClient
private EngineApiClient engineApiClient;

Expand Down Expand Up @@ -81,10 +94,43 @@ public void imageBuildAndPrune() throws IOException {
File inputDirectory = ResourceReader.getClasspathResourceAsFile(dockerfile, ImageApi.class).getParentFile();
InputStream buildContext = newBuildContext(inputDirectory);

List<BuildInfo> infos = new ArrayList<>();
Duration timeout = Duration.of(1, ChronoUnit.MINUTES);
CountDownLatch latch = new CountDownLatch(1);
StreamCallback<BuildInfo> callback = new StreamCallback<BuildInfo>() {
@Override
public void onNext(BuildInfo element) {
log.info(element.toString());
infos.add(element);
}

@Override
public void onFailed(Exception e) {
log.error("Build failed", e);
latch.countDown();
}

@Override
public void onFinished() {
latch.countDown();
}
};
assertDoesNotThrow(() -> imageApi.imageBuild(Paths.get(dockerfile).getFileName().toString(), "test:build", null, null, null, null, null, null,
null, null, null, null, null, null, null,
null, null, null, null, null, null, null,
null, null, null, null, buildContext));
null, null, null, null, buildContext,
callback, timeout.toMillis()));
try {
latch.await(2, TimeUnit.MINUTES);
}
catch (InterruptedException e) {
log.error("Wait interrupted", e);
}

ImageID imageId = imageApi.getImageId(infos);
assertNotNull(imageId);
assertNotNull(imageId.getID());
assertTrue(imageId.getID().matches(".+"));

Map<String, List<String>> filter = new HashMap<>();
filter.put("label", singletonList(LABEL_KEY));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package de.gesellix.docker.remote.api.client;

import de.gesellix.docker.engine.DockerClientConfig;
import de.gesellix.docker.remote.api.BuildInfo;
import de.gesellix.docker.remote.api.ImageID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

class ImageApiTest {

private ImageApi imageApi;

@BeforeEach
public void setup() {
imageApi = new ImageApi(new DockerClientConfig());
}

@Test
public void getImageIdFromAux() {
List<BuildInfo> infos = new ArrayList<>();
infos.add(new BuildInfo(null, null, null, null, null, null, null, new ImageID("sha256:expected-id")));
infos.add(new BuildInfo(null, "Successfully built f9d5f290d048\nfoo bar", null, null, null, null, null, null));
infos.add(new BuildInfo(null, "Successfully tagged image:tag\nbar baz", null, null, null, null, null, null));

ImageID imageId = imageApi.getImageId(infos);

assertEquals("sha256:expected-id", imageId.getID());
}

@Test
public void getImageIdFromStreamWithBuildMessage() {
List<BuildInfo> infos = new ArrayList<>();
infos.add(new BuildInfo(null, "Successfully built f9d5f290d048\nfoo bar", null, null, null, null, null, null));
infos.add(new BuildInfo(null, "Successfully tagged image:tag\nbar baz", null, null, null, null, null, null));

ImageID imageId = imageApi.getImageId(infos);

assertEquals("f9d5f290d048", imageId.getID());
}

@Test
public void getImageIdFromStreamWithTagMessage() {
List<BuildInfo> infos = new ArrayList<>();
infos.add(new BuildInfo(null, "Successfully tagged image:tag\nbar baz", null, null, null, null, null, null));

ImageID imageId = imageApi.getImageId(infos);

assertEquals("image:tag", imageId.getID());
}
}