Skip to content

Commit 9cd97d1

Browse files
committed
Allow custom callbacks at imageBuild()
1 parent 76761d0 commit 9cd97d1

3 files changed

Lines changed: 141 additions & 12 deletions

File tree

api-client/src/main/kotlin/de/gesellix/docker/remote/api/client/ImageApi.kt

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import de.gesellix.docker.engine.DockerClientConfig
1515
import de.gesellix.docker.engine.RequestMethod.DELETE
1616
import de.gesellix.docker.engine.RequestMethod.GET
1717
import de.gesellix.docker.engine.RequestMethod.POST
18+
import de.gesellix.docker.remote.api.BuildInfo
1819
import de.gesellix.docker.remote.api.BuildPruneResponse
1920
import de.gesellix.docker.remote.api.ContainerConfig
2021
import de.gesellix.docker.remote.api.HistoryResponseItem
2122
import de.gesellix.docker.remote.api.IdResponse
2223
import de.gesellix.docker.remote.api.Image
2324
import de.gesellix.docker.remote.api.ImageDeleteResponseItem
25+
import de.gesellix.docker.remote.api.ImageID
2426
import de.gesellix.docker.remote.api.ImagePruneResponse
2527
import de.gesellix.docker.remote.api.ImageSearchResponseItem
2628
import de.gesellix.docker.remote.api.ImageSummary
@@ -169,6 +171,7 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
169171
// with Dockerfiles relying on `buildx`. Maybe we can simply shell out to a locally installed docker cli,
170172
// but that's something the user might solve themselves.
171173
@Throws(UnsupportedOperationException::class, ClientException::class, ServerException::class)
174+
@JvmOverloads
172175
fun imageBuild(
173176
dockerfile: String?,
174177
t: String?,
@@ -196,7 +199,8 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
196199
platform: String?,
197200
target: String?,
198201
outputs: String?,
199-
inputStream: InputStream
202+
inputStream: InputStream,
203+
callback: StreamCallback<BuildInfo?>? = null, timeoutMillis: Long? = null /*= 24.hours.toLongMilliseconds()*/
200204
) {
201205
val localVariableConfig = imageBuildRequestConfig(
202206
dockerfile = dockerfile,
@@ -228,25 +232,25 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
228232
inputStream = inputStream
229233
)
230234

231-
val localVarResponse = requestStream<Any?>(
235+
val localVarResponse = requestStream<BuildInfo?>(
232236
localVariableConfig
233237
)
234238

235-
val timeout = Duration.of(1, ChronoUnit.MINUTES)
236-
// TODO collect events and/or extract the image id
237-
// from either `aux ID=sha:.*`,
238-
// or `stream Successfully built .*`,
239-
// or `stream Successfully tagged .*` messages.
240-
val callback = LoggingCallback<Any>()
239+
val timeout = if (timeoutMillis == null) {
240+
Duration.of(10, ChronoUnit.MINUTES)
241+
} else {
242+
Duration.of(timeoutMillis, ChronoUnit.MILLIS)
243+
}
244+
val actualCallback = callback ?: LoggingCallback<BuildInfo?>()
241245

242246
return when (localVarResponse.responseType) {
243247
ResponseType.Success -> {
244248
runBlocking {
245249
launch {
246250
withTimeout(timeout.toMillis()) {
247-
callback.onStarting(this@launch::cancel)
248-
(localVarResponse as SuccessStream<*>).data.collect { callback.onNext(it) }
249-
callback.onFinished()
251+
actualCallback.onStarting(this@launch::cancel)
252+
((localVarResponse as SuccessStream<*>).data as Flow<BuildInfo?>).collect { actualCallback.onNext(it) }
253+
actualCallback.onFinished()
250254
}
251255
}
252256
}
@@ -265,6 +269,30 @@ class ImageApi(dockerClientConfig: DockerClientConfig = defaultClientConfig, pro
265269
}
266270
}
267271

272+
fun getImageId(buildInfos: List<BuildInfo>): ImageID? {
273+
val firstAux = buildInfos.stream()
274+
.filter { (_, _, _, _, _, _, _, aux): BuildInfo -> aux != null }
275+
.findFirst()
276+
if (firstAux.isPresent) {
277+
return firstAux.get().aux
278+
} else {
279+
val idFromStream = buildInfos.stream()
280+
.filter { (_, stream): BuildInfo -> stream?.contains("Successfully built ")!! }
281+
.findFirst()
282+
return if (idFromStream.isPresent) {
283+
ImageID(idFromStream.get().stream!!.removePrefix("Successfully built ").replaceAfter('\n', "").trim())
284+
} else {
285+
val tagFromStream = buildInfos.stream()
286+
.filter { (_, stream): BuildInfo -> stream?.contains("Successfully tagged ")!! }
287+
.findFirst()
288+
tagFromStream.map { (_, stream): BuildInfo ->
289+
ImageID(stream!!.removePrefix("Successfully tagged ").replaceAfter('\n', "").trim())
290+
}
291+
.orElse(null)
292+
}
293+
}
294+
}
295+
268296
/**
269297
* To obtain the request config of the operation imageBuild
270298
*

api-client/src/test/java/de/gesellix/docker/remote/api/client/ImageApiIntegrationTest.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.squareup.moshi.Moshi;
44
import de.gesellix.docker.builder.BuildContextBuilder;
5+
import de.gesellix.docker.remote.api.BuildInfo;
56
import de.gesellix.docker.remote.api.BuildPruneResponse;
67
import de.gesellix.docker.remote.api.ContainerCreateRequest;
78
import de.gesellix.docker.remote.api.ContainerCreateResponse;
@@ -10,8 +11,10 @@
1011
import de.gesellix.docker.remote.api.IdResponse;
1112
import de.gesellix.docker.remote.api.Image;
1213
import de.gesellix.docker.remote.api.ImageDeleteResponseItem;
14+
import de.gesellix.docker.remote.api.ImageID;
1315
import de.gesellix.docker.remote.api.ImageSearchResponseItem;
1416
import de.gesellix.docker.remote.api.ImageSummary;
17+
import de.gesellix.docker.remote.api.core.StreamCallback;
1518
import de.gesellix.docker.remote.api.testutil.DockerEngineAvailable;
1619
import de.gesellix.docker.remote.api.testutil.DockerRegistry;
1720
import de.gesellix.docker.remote.api.testutil.HttpTestServer;
@@ -23,6 +26,8 @@
2326
import de.gesellix.testutil.ResourceReader;
2427
import org.junit.jupiter.api.BeforeEach;
2528
import org.junit.jupiter.api.Test;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
2631

2732
import java.io.ByteArrayInputStream;
2833
import java.io.ByteArrayOutputStream;
@@ -33,10 +38,15 @@
3338
import java.net.InetSocketAddress;
3439
import java.net.URL;
3540
import java.nio.file.Paths;
41+
import java.time.Duration;
42+
import java.time.temporal.ChronoUnit;
43+
import java.util.ArrayList;
3644
import java.util.HashMap;
3745
import java.util.List;
3846
import java.util.Map;
3947
import java.util.Optional;
48+
import java.util.concurrent.CountDownLatch;
49+
import java.util.concurrent.TimeUnit;
4050

4151
import static de.gesellix.docker.remote.api.testutil.Constants.LABEL_KEY;
4252
import static de.gesellix.docker.remote.api.testutil.Constants.LABEL_VALUE;
@@ -48,11 +58,14 @@
4858
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4959
import static org.junit.jupiter.api.Assertions.assertEquals;
5060
import static org.junit.jupiter.api.Assertions.assertFalse;
61+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5162
import static org.junit.jupiter.api.Assertions.assertTrue;
5263

5364
@DockerEngineAvailable
5465
class ImageApiIntegrationTest {
5566

67+
private static Logger log = LoggerFactory.getLogger(ImageApiIntegrationTest.class);
68+
5669
@InjectDockerClient
5770
private EngineApiClient engineApiClient;
5871

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

97+
List<BuildInfo> infos = new ArrayList<>();
98+
Duration timeout = Duration.of(1, ChronoUnit.MINUTES);
99+
CountDownLatch latch = new CountDownLatch(1);
100+
StreamCallback<BuildInfo> callback = new StreamCallback<BuildInfo>() {
101+
@Override
102+
public void onNext(BuildInfo element) {
103+
log.info(element.toString());
104+
infos.add(element);
105+
}
106+
107+
@Override
108+
public void onFailed(Exception e) {
109+
log.error("Build failed", e);
110+
latch.countDown();
111+
}
112+
113+
@Override
114+
public void onFinished() {
115+
latch.countDown();
116+
}
117+
};
84118
assertDoesNotThrow(() -> imageApi.imageBuild(Paths.get(dockerfile).getFileName().toString(), "test:build", null, null, null, null, null, null,
85119
null, null, null, null, null, null, null,
86120
null, null, null, null, null, null, null,
87-
null, null, null, null, buildContext));
121+
null, null, null, null, buildContext,
122+
callback, timeout.toMillis()));
123+
try {
124+
latch.await(2, TimeUnit.MINUTES);
125+
}
126+
catch (InterruptedException e) {
127+
log.error("Wait interrupted", e);
128+
}
129+
130+
ImageID imageId = imageApi.getImageId(infos);
131+
assertNotNull(imageId);
132+
assertNotNull(imageId.getID());
133+
assertTrue(imageId.getID().matches(".+"));
88134

89135
Map<String, List<String>> filter = new HashMap<>();
90136
filter.put("label", singletonList(LABEL_KEY));
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package de.gesellix.docker.remote.api.client;
2+
3+
import de.gesellix.docker.engine.DockerClientConfig;
4+
import de.gesellix.docker.remote.api.BuildInfo;
5+
import de.gesellix.docker.remote.api.ImageID;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
14+
class ImageApiTest {
15+
16+
private ImageApi imageApi;
17+
18+
@BeforeEach
19+
public void setup() {
20+
imageApi = new ImageApi(new DockerClientConfig());
21+
}
22+
23+
@Test
24+
public void getImageIdFromAux() {
25+
List<BuildInfo> infos = new ArrayList<>();
26+
infos.add(new BuildInfo(null, null, null, null, null, null, null, new ImageID("sha256:expected-id")));
27+
infos.add(new BuildInfo(null, "Successfully built f9d5f290d048\nfoo bar", null, null, null, null, null, null));
28+
infos.add(new BuildInfo(null, "Successfully tagged image:tag\nbar baz", null, null, null, null, null, null));
29+
30+
ImageID imageId = imageApi.getImageId(infos);
31+
32+
assertEquals("sha256:expected-id", imageId.getID());
33+
}
34+
35+
@Test
36+
public void getImageIdFromStreamWithBuildMessage() {
37+
List<BuildInfo> infos = new ArrayList<>();
38+
infos.add(new BuildInfo(null, "Successfully built f9d5f290d048\nfoo bar", null, null, null, null, null, null));
39+
infos.add(new BuildInfo(null, "Successfully tagged image:tag\nbar baz", null, null, null, null, null, null));
40+
41+
ImageID imageId = imageApi.getImageId(infos);
42+
43+
assertEquals("f9d5f290d048", imageId.getID());
44+
}
45+
46+
@Test
47+
public void getImageIdFromStreamWithTagMessage() {
48+
List<BuildInfo> infos = new ArrayList<>();
49+
infos.add(new BuildInfo(null, "Successfully tagged image:tag\nbar baz", null, null, null, null, null, null));
50+
51+
ImageID imageId = imageApi.getImageId(infos);
52+
53+
assertEquals("image:tag", imageId.getID());
54+
}
55+
}

0 commit comments

Comments
 (0)