diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75d16cd..112e3ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,10 @@ version.ref = "ktor" module = "io.ktor:ktor-client-json" version.ref = "ktor" +[libraries.ktor-client-logging] +module = "io.ktor:ktor-client-logging" +version.ref = "ktor" + [libraries.ktor-client-mock] module = "io.ktor:ktor-client-mock" version.ref = "ktor" @@ -96,7 +100,7 @@ module = "org.jetbrains.kotlinx:kotlinx-io-core" version.ref = "kotlinx-io" [bundles] -ktor = ["ktor-client-core", "ktor-client-serialization", "ktor-client-json", "ktor-client-content-negotiation", "ktor-serialization-kotlinx-json", "ktor-network"] +ktor = ["ktor-client-core", "ktor-client-serialization", "ktor-client-json", "ktor-client-logging", "ktor-client-content-negotiation", "ktor-serialization-kotlinx-json", "ktor-network"] ktx = ["ktx-coroutines-core", "ktx-serialization-core", "ktx-serialization-json"] [plugins] @@ -106,4 +110,4 @@ kotlinter = { id = "org.jmailen.kotlinter", version.ref = "plugin-kotlinter" } publishOnCentral = { id = "org.danilopianini.publish-on-central", version.ref = "plugin-publishOnCentral" } binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "plugin-binaryCompatibilityValidator" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "plugin-kover" } -detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "plugin-detekt" } \ No newline at end of file +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "plugin-detekt" } diff --git a/src/commonMain/kotlin/me/devnatan/yoki/YokiConfig.kt b/src/commonMain/kotlin/me/devnatan/yoki/YokiConfig.kt index aab86c3..3a71b09 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/YokiConfig.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/YokiConfig.kt @@ -17,7 +17,11 @@ internal val DefaultYokiConfig = YokiConfig.builder().forCurrentPlatform().build * @param apiVersion The version of the Docker API that will be used during communication. * See more: [Versioned API and SDK](https://docs.docker.com/engine/api/#versioned-api-and-sdk). */ -public class YokiConfig(public val socketPath: String, public val apiVersion: String) { +public class YokiConfig( + public val socketPath: String, + public val apiVersion: String, + public val debugHttpCalls: Boolean, +) { init { check(socketPath.isNotBlank()) { "Socket path must be provided and cannot be blank" } @@ -49,6 +53,11 @@ public class YokiConfigBuilder { prefix = null, ) + /** + * Whether to debug the HTTP calls to the Docker daemon. + */ + private var debugHttpCalls: Boolean = false + /** * Sets the Docker socket path. * @@ -69,6 +78,16 @@ public class YokiConfigBuilder { return this } + /** + * Sets the debug logging of HTTP calls. + * + * @param debugHttpCalls whether to log the HTTP calls + */ + public fun debugHttpCalls(debugHttpCalls: Boolean = true): YokiConfigBuilder { + this.debugHttpCalls = debugHttpCalls + return this + } + /** * Configures to use a Unix socket defaults common to the standard Docker configuration. * @@ -116,7 +135,7 @@ public class YokiConfigBuilder { * Builds this class to a [YokiConfig]. */ public fun build(): YokiConfig { - return YokiConfig(socketPath, apiVersion) + return YokiConfig(socketPath, apiVersion, debugHttpCalls) } /** diff --git a/src/commonMain/kotlin/me/devnatan/yoki/io/Http.kt b/src/commonMain/kotlin/me/devnatan/yoki/io/Http.kt index 698ea8b..ad51626 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/io/Http.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/io/Http.kt @@ -9,6 +9,7 @@ import io.ktor.client.plugins.ResponseException import io.ktor.client.plugins.UserAgent import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.* import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.URLBuilder @@ -37,6 +38,13 @@ internal fun createHttpClient(client: Yoki): HttpClient { ) } + if (client.config.debugHttpCalls) { + install(Logging) { + logger = Logger.SIMPLE + level = LogLevel.ALL + } + } + install(UserAgent) { agent = "Yoki" } configureHttpClient(client) diff --git a/src/commonMain/kotlin/me/devnatan/yoki/models/ExposedPort.kt b/src/commonMain/kotlin/me/devnatan/yoki/models/ExposedPort.kt index 660a8f2..27a6cad 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/models/ExposedPort.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/models/ExposedPort.kt @@ -4,20 +4,12 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.JsonTransformingSerializer -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive +import me.devnatan.yoki.util.ListAsMapToEmptyObjectsSerializer @Serializable public data class ExposedPort internal constructor( @@ -77,14 +69,4 @@ internal object ExposedPortSerializer : KSerializer { } } -internal object ExposedPortsSerializer : - JsonTransformingSerializer>(ListSerializer(ExposedPortSerializer)) { - - override fun transformDeserialize(element: JsonElement): JsonElement { - return JsonArray(element.jsonObject.entries.map { JsonPrimitive(it.key) }) - } - - override fun transformSerialize(element: JsonElement): JsonElement { - return JsonObject(element.jsonArray.associate { it.jsonPrimitive.content to JsonObject(mapOf()) }) - } -} +internal object ExposedPortsSerializer : ListAsMapToEmptyObjectsSerializer(ExposedPortSerializer) diff --git a/src/commonMain/kotlin/me/devnatan/yoki/models/ProcessConfig.kt b/src/commonMain/kotlin/me/devnatan/yoki/models/ProcessConfig.kt index 3dc218b..43e164d 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/models/ProcessConfig.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/models/ProcessConfig.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Serializable public data class ProcessConfig internal constructor( val privileged: Boolean, - val user: String, + val user: String? = null, val tty: Boolean, val entrypoint: String, val arguments: List, diff --git a/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerConfig.kt b/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerConfig.kt index 6be68a1..f4afe4b 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerConfig.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerConfig.kt @@ -2,9 +2,11 @@ package me.devnatan.yoki.models.container import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer import me.devnatan.yoki.models.ExposedPort import me.devnatan.yoki.models.ExposedPortsSerializer import me.devnatan.yoki.models.HealthConfig +import me.devnatan.yoki.util.ListAsMapToEmptyObjectsSerializer @Serializable public data class ContainerConfig( @@ -23,7 +25,7 @@ public data class ContainerConfig( @SerialName("Healthcheck") public val healthcheck: HealthConfig? = null, @SerialName("ArgsEscaped") public val argsEscaped: Boolean? = null, @SerialName("Image") public val image: String? = null, - @SerialName("Volumes") public val volumes: Map? = emptyMap(), + @SerialName("Volumes") public val volumes: @Serializable(with = VolumesSerializer::class) List? = emptyList(), @SerialName("WorkingDir") public val workingDir: String? = null, @SerialName("Entrypoint") public val entrypoint: List? = emptyList(), @SerialName("NetworkDisabled") public val networkDisabled: Boolean? = null, @@ -34,3 +36,5 @@ public data class ContainerConfig( @SerialName("StopTimeout") public val stopTimeout: Int? = null, @SerialName("Shell") public val shell: List = emptyList(), ) + +internal object VolumesSerializer : ListAsMapToEmptyObjectsSerializer(String.serializer()) diff --git a/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerCreateOptions.kt b/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerCreateOptions.kt index 2a69d4c..77eee45 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerCreateOptions.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/models/container/ContainerCreateOptions.kt @@ -1,6 +1,5 @@ package me.devnatan.yoki.models.container -import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import me.devnatan.yoki.models.ExposedPort @@ -25,7 +24,7 @@ public data class ContainerCreateOptions( @SerialName("Healthcheck") public var healthcheck: HealthConfig? = null, @SerialName("ArgsEscaped") public var escapedArgs: Boolean? = null, @SerialName("Image") public var image: String? = null, - @SerialName("Volumes") public var volumes: Map? = null, + @SerialName("Volumes") public var volumes: @Serializable(with = VolumesSerializer::class) List? = null, @SerialName("WorkingDir") public var workingDirectory: String? = null, @SerialName("Entrypoint") public var entrypoint: List? = null, @SerialName("NetworkDisabled") public var disabledNetwork: Boolean? = null, @@ -57,7 +56,7 @@ public fun ContainerCreateOptions.hostConfig(block: HostConfig.() -> Unit) { } public fun ContainerCreateOptions.volume(string: String) { - this.volumes = mapOf(string to emptyMap()) + volumes.orEmpty() + this.volumes = this.volumes.orEmpty() + string } public fun ContainerCreateOptions.networkingConfig(block: NetworkingConfig.() -> Unit) { diff --git a/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.kt b/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.kt index 15971fb..ba08fb2 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.kt @@ -14,7 +14,6 @@ import me.devnatan.yoki.models.container.ContainerPruneResult import me.devnatan.yoki.models.container.ContainerRemoveOptions import me.devnatan.yoki.models.container.ContainerSummary import me.devnatan.yoki.models.container.ContainerWaitResult -import me.devnatan.yoki.models.exec.ExecCreateOptions import me.devnatan.yoki.resource.image.ImageNotFoundException import kotlin.jvm.JvmOverloads import kotlin.time.Duration @@ -125,14 +124,6 @@ public expect class ContainerResource { */ public suspend fun resizeTTY(container: String, options: ResizeTTYOptions = ResizeTTYOptions()) - /** - * Runs a command inside a running container. - * - * @param container The container id to execute the command. - * @param options Exec instance command options. - */ - public suspend fun exec(container: String, options: ExecCreateOptions = ExecCreateOptions()): String - // TODO documentation public fun attach(container: String): Flow diff --git a/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResourceExt.kt b/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResourceExt.kt index 795c5ae..3e48e78 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResourceExt.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResourceExt.kt @@ -59,16 +59,6 @@ public suspend inline fun ContainerResource.resizeTTY(container: String, options resizeTTY(container, ResizeTTYOptions().apply(options)) } -/** - * Runs a command inside a running container. - * - * @param container The container id to execute the command. - * @param options Exec instance command options. - */ -public suspend inline fun ContainerResource.exec(container: String, options: ExecCreateOptions.() -> Unit) { - exec(container, ExecCreateOptions().apply(options)) -} - // public inline fun ContainerResource.logs( // id: String, // block: ContainerLogsOptions.() -> Unit, diff --git a/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResource.kt b/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResource.kt index a4c8d35..05fc811 100644 --- a/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResource.kt +++ b/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResource.kt @@ -6,10 +6,15 @@ import io.ktor.client.request.get import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.http.HttpStatusCode +import kotlin.jvm.JvmSynthetic import me.devnatan.yoki.io.requestCatching +import me.devnatan.yoki.models.IdOnlyResponse import me.devnatan.yoki.models.ResizeTTYOptions +import me.devnatan.yoki.models.exec.ExecCreateOptions import me.devnatan.yoki.models.exec.ExecInspectResponse import me.devnatan.yoki.models.exec.ExecStartOptions +import me.devnatan.yoki.resource.ResourcePaths.CONTAINERS +import me.devnatan.yoki.resource.container.ContainerNotFoundException import me.devnatan.yoki.resource.container.ContainerNotRunningException /** @@ -27,6 +32,35 @@ public class ExecResource internal constructor( const val BASE_PATH = "/exec" } + /** + * Runs a command inside a running container. + * + * @param id The container id to execute the command. + * @param options Exec instance command options. + * @throws ContainerNotFoundException If container instance is not found. + * @throws ContainerNotRunningException If the container is not running. + */ + @JvmSynthetic + public suspend fun create(id: String, options: ExecCreateOptions): String = + requestCatching( + HttpStatusCode.NotFound to { cause -> + ContainerNotFoundException( + cause, + id, + ) + }, + HttpStatusCode.Conflict to { cause -> + ContainerNotRunningException( + cause, + id, + ) + }, + ) { + httpClient.post("$CONTAINERS/$id/exec") { + setBody(options) + } + }.body().id + /** * Inspects an exec instance and returns low-level information about it. * `docker exec inspect` diff --git a/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResourceExt.kt b/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResourceExt.kt new file mode 100644 index 0000000..8fbb5dc --- /dev/null +++ b/src/commonMain/kotlin/me/devnatan/yoki/resource/exec/ExecResourceExt.kt @@ -0,0 +1,33 @@ +package me.devnatan.yoki.resource.exec + +import me.devnatan.yoki.models.exec.ExecCreateOptions +import me.devnatan.yoki.models.exec.ExecStartOptions +import me.devnatan.yoki.resource.container.ContainerNotFoundException +import me.devnatan.yoki.resource.container.ContainerNotRunningException + +/** + * Creates a new container. + * + * @param id The container id to execute the command. + * @param options Options to customize the container creation. + * @throws ContainerNotFoundException If container instance is not found. + * @throws ContainerNotRunningException If the container is not running. + */ +public suspend inline fun ExecResource.create(id: String, options: ExecCreateOptions.() -> Unit): String { + return create(id, ExecCreateOptions().apply(options)) +} + +/** + * Starts a previously set up exec instance. + * + * If detach is true, this endpoint returns immediately after starting the command. + * Otherwise, it sets up an interactive session with the command. + * + * @param id The exec instance id to be started. + * @param options Options to customize the exec start. + * @throws ExecNotFoundException If exec instance is not found. + * @throws ContainerNotRunningException If the container in which the exec instance was created is not running. + */ +public suspend inline fun ExecResource.start(id: String, options: ExecStartOptions.() -> Unit = {}) { + start(id, ExecStartOptions().apply(options)) +} diff --git a/src/commonMain/kotlin/me/devnatan/yoki/util/ListAsMapToEmptyObjectsSerializer.kt b/src/commonMain/kotlin/me/devnatan/yoki/util/ListAsMapToEmptyObjectsSerializer.kt new file mode 100644 index 0000000..fe0b5d7 --- /dev/null +++ b/src/commonMain/kotlin/me/devnatan/yoki/util/ListAsMapToEmptyObjectsSerializer.kt @@ -0,0 +1,25 @@ +package me.devnatan.yoki.util + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.JsonTransformingSerializer +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +internal open class ListAsMapToEmptyObjectsSerializer( + private val tSerializer: KSerializer, +) : JsonTransformingSerializer>(ListSerializer(tSerializer)) { + + override fun transformDeserialize(element: JsonElement): JsonElement { + return JsonArray(element.jsonObject.entries.map { JsonPrimitive(it.key) }) + } + + override fun transformSerialize(element: JsonElement): JsonElement { + return JsonObject(element.jsonArray.associate { it.jsonPrimitive.content to JsonObject(mapOf()) }) + } +} diff --git a/src/commonTest/kotlin/me/devnatan/yoki/resource/ResourceIT.kt b/src/commonTest/kotlin/me/devnatan/yoki/resource/ResourceIT.kt index b915784..8113355 100644 --- a/src/commonTest/kotlin/me/devnatan/yoki/resource/ResourceIT.kt +++ b/src/commonTest/kotlin/me/devnatan/yoki/resource/ResourceIT.kt @@ -3,9 +3,13 @@ package me.devnatan.yoki.resource import me.devnatan.yoki.Yoki import me.devnatan.yoki.createTestYoki -open class ResourceIT { +open class ResourceIT( + private val debugHttpCalls: Boolean = false, +) { - companion object { - val testClient: Yoki by lazy(::createTestYoki) + val testClient: Yoki by lazy { + createTestYoki { + debugHttpCalls(this@ResourceIT.debugHttpCalls) + } } } diff --git a/src/commonTest/kotlin/me/devnatan/yoki/resource/container/InspectContainerIT.kt b/src/commonTest/kotlin/me/devnatan/yoki/resource/container/InspectContainerIT.kt new file mode 100644 index 0000000..403ab81 --- /dev/null +++ b/src/commonTest/kotlin/me/devnatan/yoki/resource/container/InspectContainerIT.kt @@ -0,0 +1,34 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package me.devnatan.yoki.resource.container + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import me.devnatan.yoki.models.container.volume +import me.devnatan.yoki.resource.ResourceIT +import me.devnatan.yoki.withContainer +import kotlin.test.Test +import kotlin.test.assertEquals + +class InspectContainerIT : ResourceIT() { + + @Test + fun `inspects container with volumes`() = runTest { + testClient.withContainer( + "busybox:latest", + { + volume("/opt") + command = listOf("sleep", "infinity") + }, + ) { id -> + testClient.containers.start(id) + try { + val container = testClient.containers.inspect(id) + val volumes = container.config.volumes + assertEquals(volumes, listOf("/opt")) + } finally { + testClient.containers.stop(id) + } + } + } +} diff --git a/src/commonTest/kotlin/me/devnatan/yoki/resource/exec/ExecContainerIT.kt b/src/commonTest/kotlin/me/devnatan/yoki/resource/exec/ExecContainerIT.kt new file mode 100644 index 0000000..10ecea0 --- /dev/null +++ b/src/commonTest/kotlin/me/devnatan/yoki/resource/exec/ExecContainerIT.kt @@ -0,0 +1,56 @@ +package me.devnatan.yoki.resource.exec + +import kotlinx.coroutines.test.runTest +import me.devnatan.yoki.resource.ResourceIT +import me.devnatan.yoki.withContainer +import kotlin.test.Test +import kotlin.test.assertEquals + +class ExecContainerIT : ResourceIT() { + + @Test + fun `exec a command in a container`() = runTest { + testClient.withContainer( + "busybox:latest", + { + command = listOf("sleep", "infinity") + }, + ) { id -> + testClient.containers.start(id) + + val execId = testClient.exec.create(id) { + command = listOf("true") + } + + testClient.exec.start(execId) + + val exec = testClient.exec.inspect(execId) + assertEquals(exec.exitCode, 0) + + testClient.containers.stop(id) + } + } + + @Test + fun `exec a failing command in a container`() = runTest { + testClient.withContainer( + "busybox:latest", + { + command = listOf("sleep", "infinity") + }, + ) { id -> + testClient.containers.start(id) + + val execId = testClient.exec.create(id) { + command = listOf("false") + } + + testClient.exec.start(execId) + + val exec = testClient.exec.inspect(execId) + assertEquals(exec.exitCode, 1) + + testClient.containers.stop(id) + } + } +} diff --git a/src/jvmMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.jvm.kt b/src/jvmMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.jvm.kt index d2d67e5..2aeb77f 100644 --- a/src/jvmMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.jvm.kt +++ b/src/jvmMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.jvm.kt @@ -13,9 +13,16 @@ import io.ktor.client.request.put import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode +import io.ktor.util.cio.toByteReadChannel import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.jvm.javaio.toByteReadChannel import io.ktor.utils.io.readUTF8Line +import java.io.InputStream +import java.util.concurrent.CompletableFuture +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow @@ -32,7 +39,6 @@ import me.devnatan.yoki.io.readTarFile import me.devnatan.yoki.io.requestCatching import me.devnatan.yoki.io.writeTarFile import me.devnatan.yoki.models.Frame -import me.devnatan.yoki.models.IdOnlyResponse import me.devnatan.yoki.models.ResizeTTYOptions import me.devnatan.yoki.models.Stream import me.devnatan.yoki.models.container.Container @@ -45,16 +51,8 @@ import me.devnatan.yoki.models.container.ContainerPruneResult import me.devnatan.yoki.models.container.ContainerRemoveOptions import me.devnatan.yoki.models.container.ContainerSummary import me.devnatan.yoki.models.container.ContainerWaitResult -import me.devnatan.yoki.models.exec.ExecCreateOptions import me.devnatan.yoki.resource.ResourcePaths.CONTAINERS import me.devnatan.yoki.resource.image.ImageNotFoundException -import java.io.InputStream -import java.util.concurrent.CompletableFuture -import kotlin.io.encoding.Base64 -import kotlin.io.encoding.ExperimentalEncodingApi -import kotlin.time.Duration -import kotlin.time.DurationUnit -import kotlin.time.toDuration public actual class ContainerResource( private val coroutineScope: CoroutineScope, @@ -86,19 +84,6 @@ public actual class ContainerResource( public fun listAsync(options: ContainerListOptions = ContainerListOptions(all = true)): CompletableFuture> = coroutineScope.async { list(options) }.asCompletableFuture() - /** - * Runs a command inside a running container. - * - * @param container Unique identifier or name of the container. - * @param options Exec instance command options. - */ - @JvmOverloads - public fun execAsync( - container: String, - options: ExecCreateOptions = ExecCreateOptions(), - ): CompletableFuture = - coroutineScope.async { exec(container, options) }.asCompletableFuture() - /** * Creates a new container. * @@ -438,33 +423,6 @@ public actual class ContainerResource( ): CompletableFuture = coroutineScope.async { resizeTTY(container, options) }.asCompletableFuture() - /** - * Runs a command inside a running container. - * - * @param container The container id to execute the command. - * @param options Exec instance command options. - */ - @JvmSynthetic - public actual suspend fun exec(container: String, options: ExecCreateOptions): String = - requestCatching( - HttpStatusCode.NotFound to { cause -> - ContainerNotFoundException( - cause, - container, - ) - }, - HttpStatusCode.Conflict to { cause -> - ContainerNotRunningException( - cause, - container, - ) - }, - ) { - httpClient.post("$CONTAINERS/$container/exec") { - setBody(options) - } - }.body().id - @JvmSynthetic public actual fun attach(container: String): Flow = flow { httpClient.preparePost("$CONTAINERS/$container/attach") { diff --git a/src/nativeMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.native.kt b/src/nativeMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.native.kt index 390211e..bdea261 100644 --- a/src/nativeMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.native.kt +++ b/src/nativeMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.native.kt @@ -13,7 +13,6 @@ import me.devnatan.yoki.models.container.ContainerPruneResult import me.devnatan.yoki.models.container.ContainerRemoveOptions import me.devnatan.yoki.models.container.ContainerSummary import me.devnatan.yoki.models.container.ContainerWaitResult -import me.devnatan.yoki.models.exec.ExecCreateOptions import kotlin.time.Duration public actual class ContainerResource { @@ -141,19 +140,6 @@ public actual class ContainerResource { public actual suspend fun resizeTTY(container: String, options: ResizeTTYOptions) { } - /** - * Runs a command inside a running container. - * - * @param container The container id to execute the command. - * @param options Exec instance command options. - */ - public actual suspend fun exec( - container: String, - options: ExecCreateOptions, - ): String { - TODO("Not yet implemented") - } - public actual fun attach(container: String): Flow { TODO("Not yet implemented") }