Skip to content
Merged
12 changes: 5 additions & 7 deletions api/docker-kotlin.api
Original file line number Diff line number Diff line change
Expand Up @@ -2546,19 +2546,18 @@ public final class me/devnatan/dockerkt/models/image/ImageRootFs$Companion {

public final class me/devnatan/dockerkt/models/image/ImageSummary {
public static final field Companion Lme/devnatan/dockerkt/models/image/ImageSummary$Companion;
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;IJIJLjava/util/Map;I)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;IJILjava/util/Map;I)V
public final fun component1 ()Ljava/lang/String;
public final fun component10 ()I
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Ljava/util/List;
public final fun component5 ()I
public final fun component6 ()J
public final fun component7 ()I
public final fun component8 ()J
public final fun component9 ()Ljava/util/Map;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;IJIJLjava/util/Map;I)Lme/devnatan/dockerkt/models/image/ImageSummary;
public static synthetic fun copy$default (Lme/devnatan/dockerkt/models/image/ImageSummary;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;IJIJLjava/util/Map;IILjava/lang/Object;)Lme/devnatan/dockerkt/models/image/ImageSummary;
public final fun component8 ()Ljava/util/Map;
public final fun component9 ()I
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;IJILjava/util/Map;I)Lme/devnatan/dockerkt/models/image/ImageSummary;
public static synthetic fun copy$default (Lme/devnatan/dockerkt/models/image/ImageSummary;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;IJILjava/util/Map;IILjava/lang/Object;)Lme/devnatan/dockerkt/models/image/ImageSummary;
public fun equals (Ljava/lang/Object;)Z
public final fun getContainers ()I
public final fun getCreated ()I
Expand All @@ -2569,7 +2568,6 @@ public final class me/devnatan/dockerkt/models/image/ImageSummary {
public final fun getRepositoryTags ()Ljava/util/List;
public final fun getSharedSize ()I
public final fun getSize ()J
public final fun getVirtualSize ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
Expand Down
11 changes: 4 additions & 7 deletions api/docker-kotlin.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -1571,7 +1571,7 @@ final class me.devnatan.dockerkt.models.image/ImageRootFs { // me.devnatan.docke
}

final class me.devnatan.dockerkt.models.image/ImageSummary { // me.devnatan.dockerkt.models.image/ImageSummary|null[0]
constructor <init>(kotlin/String, kotlin/String, kotlin.collections/List<kotlin/String>, kotlin.collections/List<kotlin/String>, kotlin/Int, kotlin/Long, kotlin/Int, kotlin/Long, kotlin.collections/Map<kotlin/String, kotlin/String>?, kotlin/Int) // me.devnatan.dockerkt.models.image/ImageSummary.<init>|<init>(kotlin.String;kotlin.String;kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>;kotlin.Int;kotlin.Long;kotlin.Int;kotlin.Long;kotlin.collections.Map<kotlin.String,kotlin.String>?;kotlin.Int){}[0]
constructor <init>(kotlin/String, kotlin/String, kotlin.collections/List<kotlin/String>, kotlin.collections/List<kotlin/String>, kotlin/Int, kotlin/Long, kotlin/Int, kotlin.collections/Map<kotlin/String, kotlin/String>?, kotlin/Int) // me.devnatan.dockerkt.models.image/ImageSummary.<init>|<init>(kotlin.String;kotlin.String;kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>;kotlin.Int;kotlin.Long;kotlin.Int;kotlin.collections.Map<kotlin.String,kotlin.String>?;kotlin.Int){}[0]

final val containers // me.devnatan.dockerkt.models.image/ImageSummary.containers|{}containers[0]
final fun <get-containers>(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.containers.<get-containers>|<get-containers>(){}[0]
Expand All @@ -1591,20 +1591,17 @@ final class me.devnatan.dockerkt.models.image/ImageSummary { // me.devnatan.dock
final fun <get-sharedSize>(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.sharedSize.<get-sharedSize>|<get-sharedSize>(){}[0]
final val size // me.devnatan.dockerkt.models.image/ImageSummary.size|{}size[0]
final fun <get-size>(): kotlin/Long // me.devnatan.dockerkt.models.image/ImageSummary.size.<get-size>|<get-size>(){}[0]
final val virtualSize // me.devnatan.dockerkt.models.image/ImageSummary.virtualSize|{}virtualSize[0]
final fun <get-virtualSize>(): kotlin/Long // me.devnatan.dockerkt.models.image/ImageSummary.virtualSize.<get-virtualSize>|<get-virtualSize>(){}[0]

final fun component1(): kotlin/String // me.devnatan.dockerkt.models.image/ImageSummary.component1|component1(){}[0]
final fun component10(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.component10|component10(){}[0]
final fun component2(): kotlin/String // me.devnatan.dockerkt.models.image/ImageSummary.component2|component2(){}[0]
final fun component3(): kotlin.collections/List<kotlin/String> // me.devnatan.dockerkt.models.image/ImageSummary.component3|component3(){}[0]
final fun component4(): kotlin.collections/List<kotlin/String> // me.devnatan.dockerkt.models.image/ImageSummary.component4|component4(){}[0]
final fun component5(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.component5|component5(){}[0]
final fun component6(): kotlin/Long // me.devnatan.dockerkt.models.image/ImageSummary.component6|component6(){}[0]
final fun component7(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.component7|component7(){}[0]
final fun component8(): kotlin/Long // me.devnatan.dockerkt.models.image/ImageSummary.component8|component8(){}[0]
final fun component9(): kotlin.collections/Map<kotlin/String, kotlin/String>? // me.devnatan.dockerkt.models.image/ImageSummary.component9|component9(){}[0]
final fun copy(kotlin/String = ..., kotlin/String = ..., kotlin.collections/List<kotlin/String> = ..., kotlin.collections/List<kotlin/String> = ..., kotlin/Int = ..., kotlin/Long = ..., kotlin/Int = ..., kotlin/Long = ..., kotlin.collections/Map<kotlin/String, kotlin/String>? = ..., kotlin/Int = ...): me.devnatan.dockerkt.models.image/ImageSummary // me.devnatan.dockerkt.models.image/ImageSummary.copy|copy(kotlin.String;kotlin.String;kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>;kotlin.Int;kotlin.Long;kotlin.Int;kotlin.Long;kotlin.collections.Map<kotlin.String,kotlin.String>?;kotlin.Int){}[0]
final fun component8(): kotlin.collections/Map<kotlin/String, kotlin/String>? // me.devnatan.dockerkt.models.image/ImageSummary.component8|component8(){}[0]
final fun component9(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.component9|component9(){}[0]
final fun copy(kotlin/String = ..., kotlin/String = ..., kotlin.collections/List<kotlin/String> = ..., kotlin.collections/List<kotlin/String> = ..., kotlin/Int = ..., kotlin/Long = ..., kotlin/Int = ..., kotlin.collections/Map<kotlin/String, kotlin/String>? = ..., kotlin/Int = ...): me.devnatan.dockerkt.models.image/ImageSummary // me.devnatan.dockerkt.models.image/ImageSummary.copy|copy(kotlin.String;kotlin.String;kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>;kotlin.Int;kotlin.Long;kotlin.Int;kotlin.collections.Map<kotlin.String,kotlin.String>?;kotlin.Int){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // me.devnatan.dockerkt.models.image/ImageSummary.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // me.devnatan.dockerkt.models.image/ImageSummary.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // me.devnatan.dockerkt.models.image/ImageSummary.toString|toString(){}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public data class ImageSummary(
@SerialName("Created") @Required public val created: Int,
@SerialName("Size") @Required public val size: Long,
@SerialName("SharedSize") @Required public val sharedSize: Int,
@SerialName("VirtualSize") @Required public val virtualSize: Long,
@SerialName("Labels") @Required public val labels: Map<String, String>?,
@SerialName("Containers") @Required public val containers: Int,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public class ImageResource internal constructor(
}.execute { response ->
val channel = response.body<ByteReadChannel>()
while (true) {
emit(json.decodeFromString(channel.readUTF8Line() ?: break))
val line = channel.readUTF8Line() ?: break
emit(json.decodeFromString(line))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ package me.devnatan.dockerkt
*/
fun createTestDockerClient(block: DockerClientConfigBuilder.() -> Unit = {}): DockerClient =
runCatching {
DockerClient { apply(block) }
DockerClient {
debugHttpCalls(true)
apply(block)
}
}.onFailure {
@Suppress("TooGenericExceptionThrown")
throw RuntimeException("Failed to initialize Docker test client", it)
Expand Down
43 changes: 27 additions & 16 deletions src/commonTest/kotlin/me/devnatan/dockerkt/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,46 @@ suspend fun <R> DockerClient.withContainer(
image: String,
options: ContainerCreateOptions.() -> Unit = {},
block: suspend (String) -> R,
) = withImage(image) { imageTag ->
try {
val id =
containers.create {
this.image = imageTag
apply(options)
): Unit =
withImage(image) { imageTag ->
val containerId: String
try {
containerId =
containers.create {
this.image = imageTag
apply(options)
}
} catch (e: Throwable) {
fail("Failed to create container", e)
}

try {
block(containerId)
} finally {
containers.remove(containerId) {
force = true
removeAnonymousVolumes = true
}
block(id)
containers.remove(id) {
force = true
removeAnonymousVolumes = true
}
} catch (e: Throwable) {
fail("Failed to create container", e)
}
}

suspend fun <R> DockerClient.withVolume(
config: VolumeCreateOptions.() -> Unit = {},
block: suspend (Volume) -> R,
) {
val volume: Volume =
try {
volumes.create(config)
} catch (e: Throwable) {
fail("Failed to create volume", e)
}

try {
val volume = volumes.create(config)
block(volume)
} finally {
volumes.remove(volume.name) {
force = true
}
} catch (e: Throwable) {
fail("Failed to create volume", e)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class StartContainerIT : ResourceIT() {
fun `start a container with auto-assigned port bindings`() =
runTest {
testClient.withContainer(
"busybox:latest",
"nginx:latest",
Copy link
Owner Author

@devnatan devnatan Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk why but it doesnt work with busybox in all environments

{
exposedPort(80u)
hostConfig {
Expand All @@ -41,27 +41,29 @@ class StartContainerIT : ResourceIT() {
},
) { id ->
testClient.containers.start(id)
val container = testClient.containers.inspect(id)

val container = testClient.containers.inspect(id)
val ports = container.networkSettings.ports

assertTrue { ports.isNotEmpty() }

val exposedPort = ExposedPort(80u, ExposedPortProtocol.TCP)
assertContains(ports, exposedPort)
assertContains(map = ports, key = exposedPort)

val port80Bindings = container.networkSettings.ports[exposedPort]
assertNotNull(port80Bindings)
assertTrue { port80Bindings.size == 2 }
assertTrue { port80Bindings.isNotEmpty() }

val ipv4Binding = port80Bindings[0]
assertEquals(ipv4Binding.ip, "0.0.0.0")
assertNotNull(ipv4Binding.port)
assertTrue { ipv4Binding.port!!.toInt() > 0 }

val ipv6Binding = port80Bindings[1]
assertEquals(ipv6Binding.ip, "::")
assertNotNull(ipv6Binding.port)
assertTrue { ipv6Binding.port!!.toInt() > 0 }
if (port80Bindings.size > 1) {
val ipv6Binding = port80Bindings[1]
assertEquals(ipv6Binding.ip, "::")
assertNotNull(ipv6Binding.port)
assertTrue { ipv6Binding.port!!.toInt() > 0 }
}
Comment on lines +61 to +66
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not all Docker versions automatically assigns IPV6 ports

}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package me.devnatan.dockerkt.resource.image

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.test.runTest
import me.devnatan.dockerkt.resource.ResourceIT
import me.devnatan.dockerkt.withImage
import kotlin.test.Test
import kotlin.test.assertTrue
import kotlin.test.fail

class ImageIT : ResourceIT() {
@Test
fun `list images`() =
runTest {
testClient.images.list()
}

class PullImageIT : ResourceIT() {
@Test
fun `image pull`() =
runTest {
Expand All @@ -20,4 +25,18 @@ class PullImageIT : ResourceIT() {
)
}
}

@Test
fun `image remove`() =
runTest {
val image = "busybox:latest"

try {
testClient.images.pull(image).collect()
} catch (e: Throwable) {
fail("Failed to pull image", e)
}

testClient.images.remove(image)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class VolumeResourceIT : ResourceIT() {
@Test
fun `list volumes`() =
runTest {
val volumes = testClient.volumes.list().volumes
assertTrue("Volumes must be empty, given: $volumes") { volumes.isEmpty() }
testClient.volumes.list().volumes
// Just expect no exception is thrown
}

@Test
Expand All @@ -58,6 +58,7 @@ class VolumeResourceIT : ResourceIT() {
testClient.volumes
.list()
.volumes.size

val newCount = 5
repeat(newCount) {
testClient.volumes.create()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ public actual class ContainerResource(
}
}.body<ContainerCreateResult>()

// TODO log warns
// result.warnings.forEach(logger::warn)
result.warnings.forEach { warn -> println("Warning from Docker API: $warn") }
return result.id
}

Expand Down
Loading