Skip to content
Merged
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