Skip to content

Commit 0799f9f

Browse files
authored
Handle image not found/access denied in image pull operation (#252)
1 parent 3afbc80 commit 0799f9f

5 files changed

Lines changed: 61 additions & 10 deletions

File tree

api/docker-kotlin.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5135,6 +5135,11 @@ public final class me/devnatan/dockerkt/resource/image/ImageNotFoundException :
51355135
public final fun getImage ()Ljava/lang/String;
51365136
}
51375137

5138+
public final class me/devnatan/dockerkt/resource/image/ImagePullDeniedException : me/devnatan/dockerkt/resource/image/ImageException {
5139+
public final fun getImage ()Ljava/lang/String;
5140+
public fun getMessage ()Ljava/lang/String;
5141+
}
5142+
51385143
public final class me/devnatan/dockerkt/resource/image/ImageResource {
51395144
public static final field Companion Lme/devnatan/dockerkt/resource/image/ImageResource$Companion;
51405145
public final fun build (Ljava/lang/String;Lme/devnatan/dockerkt/models/image/ImageBuildOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

api/docker-kotlin.klib.api

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Klib ABI Dump
2-
// Targets: [linuxX64, macosX64, mingwX64]
2+
// Targets: [linuxArm64, linuxX64, macosArm64, macosX64, mingwX64]
33
// Rendering settings:
44
// - Signature version: 2
55
// - Show manifest properties: true
@@ -5224,6 +5224,13 @@ final class me.devnatan.dockerkt.resource.image/ImageNotFoundException : me.devn
52245224
final fun <get-image>(): kotlin/String // me.devnatan.dockerkt.resource.image/ImageNotFoundException.image.<get-image>|<get-image>(){}[0]
52255225
}
52265226

5227+
final class me.devnatan.dockerkt.resource.image/ImagePullDeniedException : me.devnatan.dockerkt.resource.image/ImageException { // me.devnatan.dockerkt.resource.image/ImagePullDeniedException|null[0]
5228+
final val image // me.devnatan.dockerkt.resource.image/ImagePullDeniedException.image|{}image[0]
5229+
final fun <get-image>(): kotlin/String // me.devnatan.dockerkt.resource.image/ImagePullDeniedException.image.<get-image>|<get-image>(){}[0]
5230+
final val message // me.devnatan.dockerkt.resource.image/ImagePullDeniedException.message|{}message[0]
5231+
final fun <get-message>(): kotlin/String // me.devnatan.dockerkt.resource.image/ImagePullDeniedException.message.<get-message>|<get-message>(){}[0]
5232+
}
5233+
52275234
final class me.devnatan.dockerkt.resource.image/ImageResource { // me.devnatan.dockerkt.resource.image/ImageResource|null[0]
52285235
final fun pull(kotlin/String): kotlinx.coroutines.flow/Flow<me.devnatan.dockerkt.models.image/ImagePull> // me.devnatan.dockerkt.resource.image/ImageResource.pull|pull(kotlin.String){}[0]
52295236
final suspend fun build(kotlin/String, me.devnatan.dockerkt.models.image/ImageBuildOptions) // me.devnatan.dockerkt.resource.image/ImageResource.build|build(kotlin.String;me.devnatan.dockerkt.models.image.ImageBuildOptions){}[0]

src/commonMain/kotlin/me/devnatan/dockerkt/resource/image/ImageException.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ public class ImageNotFoundException internal constructor(
1010
cause: Throwable?,
1111
public val image: String,
1212
) : ImageException(cause)
13+
14+
public class ImagePullDeniedException internal constructor(
15+
cause: Throwable?,
16+
public val image: String,
17+
public override val message: String,
18+
) : ImageException(cause)

src/commonMain/kotlin/me/devnatan/dockerkt/resource/image/ImageResource.kt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,27 @@ public class ImageResource internal constructor(
3737

3838
public fun pull(image: String): Flow<ImagePull> =
3939
channelFlow {
40-
httpClient
41-
.preparePost("$BasePath/create") {
42-
parameter("fromImage", image)
43-
}.execute { response ->
44-
val channel = response.body<ByteReadChannel>()
45-
while (true) {
46-
val line = channel.readUTF8Line() ?: break
47-
send(json.decodeFromString(line))
40+
requestCatching(
41+
HttpStatusCode.NotFound to { exception ->
42+
val errorMessage = exception.message.orEmpty().lowercase()
43+
if (errorMessage.contains("no such image") || errorMessage.contains("manifest unknown")) {
44+
ImageNotFoundException(exception, image)
45+
} else {
46+
ImagePullDeniedException(exception, image, exception.message.orEmpty())
4847
}
49-
}
48+
},
49+
) {
50+
httpClient
51+
.preparePost("$BasePath/create") {
52+
parameter("fromImage", image)
53+
}.execute { response ->
54+
val channel = response.body<ByteReadChannel>()
55+
while (true) {
56+
val line = channel.readUTF8Line() ?: break
57+
send(json.decodeFromString(line))
58+
}
59+
}
60+
}
5061
}
5162

5263
public suspend fun remove(

src/commonTest/kotlin/me/devnatan/dockerkt/resource/image/ImageIT.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import kotlinx.coroutines.test.runTest
55
import me.devnatan.dockerkt.resource.ResourceIT
66
import me.devnatan.dockerkt.withImage
77
import kotlin.test.Test
8+
import kotlin.test.assertFailsWith
89
import kotlin.test.assertNotNull
910
import kotlin.test.assertTrue
1011
import kotlin.test.fail
@@ -27,6 +28,27 @@ class ImageIT : ResourceIT() {
2728
}
2829
}
2930

31+
@Test
32+
fun `image pull access denied image`() =
33+
runTest {
34+
assertFailsWith<ImagePullDeniedException> {
35+
testClient.images.pull("inexistent:image").collect()
36+
}
37+
}
38+
39+
@Test
40+
fun `image pull unknown image`() =
41+
runTest {
42+
try {
43+
testClient.images.remove("busybox:billiejean", force = true)
44+
} catch (_: ImageNotFoundException) {
45+
}
46+
47+
assertFailsWith<ImageNotFoundException> {
48+
testClient.images.pull("busybox:billiejean").collect()
49+
}
50+
}
51+
3052
@Test
3153
fun `image remove`() =
3254
runTest {

0 commit comments

Comments
 (0)