Skip to content

Commit 0effc2c

Browse files
authored
Implement /containers/archive (#103)
1 parent c32ed16 commit 0effc2c

File tree

16 files changed

+379
-22
lines changed

16 files changed

+379
-22
lines changed

SUPPORTED_ENDPOINTS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Yoki supported Docker API endpoints
22

3-
Supports 43 of 106 endpoints
3+
Supports 46 of 106 endpoints
44

55
### Containers (15/25)
66
* [x] List containers - GET **/containers/json**
@@ -24,9 +24,9 @@ Supports 43 of 106 endpoints
2424
* [ ] Attach to a container via a websocket - **POST /containers/:id/attach/ws**
2525
* [x] Wait for a container - **POST /containers/:id/wait**
2626
* [x] Remove a container - **DELETE /containers/:id**
27-
* [ ] Get information about files in a container - **HEAD /containers/:id/archive**
28-
* [ ] Get an archive of a filesystem resource in a container - **GET /containers/:id/archive**
29-
* [ ] Extract an archive of files or folders to a directory in a container - **PUT /containers/:id/archive**
27+
* [x] Get information about files in a container - **HEAD /containers/:id/archive**
28+
* [x] Get an archive of a filesystem resource in a container - **GET /containers/:id/archive**
29+
* [x] Extract an archive of files or folders to a directory in a container - **PUT /containers/:id/archive**
3030
* [x] Delete stopped containers - **PUT /containers/prune**
3131

3232
### Images (3/15)

THIRDPARTY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ This document contains a list of third-party libraries that Yoki uses.
77
* [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines)
88
* [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization)
99
* [kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime)
10+
* [kotlinx-io](https://github.com/Kotlin/kotlinx-io)
1011
* [JetBrains Annotations](https://github.com/JetBrains/java-annotations)
11-
* [Okio](https://github.com/square/okio)
1212
* [publish-on-central](https://github.com/DanySK/publish-on-central)
1313
* [kotlinter](https://github.com/jeremymailen/kotlinter-gradle)
1414
* [detekt](https://github.com/detekt/detekt)

api/yoki.api

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,48 @@ public final class me/devnatan/yoki/models/container/Container$Companion {
12831283
public final fun serializer ()Lkotlinx/serialization/KSerializer;
12841284
}
12851285

1286+
public final class me/devnatan/yoki/models/container/ContainerArchiveInfo {
1287+
public static final field Companion Lme/devnatan/yoki/models/container/ContainerArchiveInfo$Companion;
1288+
public synthetic fun <init> (ILjava/lang/String;JILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
1289+
public fun <init> (Ljava/lang/String;JILjava/lang/String;Ljava/lang/String;)V
1290+
public synthetic fun <init> (Ljava/lang/String;JILjava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1291+
public final fun component1 ()Ljava/lang/String;
1292+
public final fun component2 ()J
1293+
public final fun component3 ()I
1294+
public final fun component4 ()Ljava/lang/String;
1295+
public final fun component5 ()Ljava/lang/String;
1296+
public final fun copy (Ljava/lang/String;JILjava/lang/String;Ljava/lang/String;)Lme/devnatan/yoki/models/container/ContainerArchiveInfo;
1297+
public static synthetic fun copy$default (Lme/devnatan/yoki/models/container/ContainerArchiveInfo;Ljava/lang/String;JILjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/devnatan/yoki/models/container/ContainerArchiveInfo;
1298+
public fun equals (Ljava/lang/Object;)Z
1299+
public final fun getLinkTarget ()Ljava/lang/String;
1300+
public final fun getMode ()I
1301+
public final fun getModifiedAtRaw ()Ljava/lang/String;
1302+
public final fun getName ()Ljava/lang/String;
1303+
public final fun getSize ()J
1304+
public fun hashCode ()I
1305+
public fun toString ()Ljava/lang/String;
1306+
public static final synthetic fun write$Self (Lme/devnatan/yoki/models/container/ContainerArchiveInfo;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
1307+
}
1308+
1309+
public final class me/devnatan/yoki/models/container/ContainerArchiveInfo$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
1310+
public static final field INSTANCE Lme/devnatan/yoki/models/container/ContainerArchiveInfo$$serializer;
1311+
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
1312+
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
1313+
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/devnatan/yoki/models/container/ContainerArchiveInfo;
1314+
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
1315+
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
1316+
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/devnatan/yoki/models/container/ContainerArchiveInfo;)V
1317+
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
1318+
}
1319+
1320+
public final class me/devnatan/yoki/models/container/ContainerArchiveInfo$Companion {
1321+
public final fun serializer ()Lkotlinx/serialization/KSerializer;
1322+
}
1323+
1324+
public final class me/devnatan/yoki/models/container/ContainerArchiveInfoKt {
1325+
public static final fun getModifiedAt (Lme/devnatan/yoki/models/container/ContainerArchiveInfo;)Lkotlinx/datetime/Instant;
1326+
}
1327+
12861328
public final class me/devnatan/yoki/models/container/ContainerConfig {
12871329
public static final field Companion Lme/devnatan/yoki/models/container/ContainerConfig$Companion;
12881330
public fun <init> ()V
@@ -3667,9 +3709,12 @@ public final class me/devnatan/yoki/resource/container/ContainerRenameConflictEx
36673709

36683710
public final class me/devnatan/yoki/resource/container/ContainerResource {
36693711
public fun <init> (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;)V
3712+
public final fun archive (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
3713+
public static synthetic fun archive$default (Lme/devnatan/yoki/resource/container/ContainerResource;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
36703714
public final synthetic fun attach (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow;
36713715
public final synthetic fun create (Lme/devnatan/yoki/models/container/ContainerCreateOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
36723716
public final fun createAsync (Lme/devnatan/yoki/models/container/ContainerCreateOptions;)Ljava/util/concurrent/CompletableFuture;
3717+
public final fun downloadArchive (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
36733718
public final synthetic fun exec (Ljava/lang/String;Lme/devnatan/yoki/models/exec/ExecCreateOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
36743719
public static synthetic fun exec$default (Lme/devnatan/yoki/resource/container/ContainerResource;Ljava/lang/String;Lme/devnatan/yoki/models/exec/ExecCreateOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
36753720
public final fun execAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture;
@@ -3724,6 +3769,7 @@ public final class me/devnatan/yoki/resource/container/ContainerResource {
37243769
public final fun stopAsync (Ljava/lang/String;I)Ljava/util/concurrent/CompletableFuture;
37253770
public final synthetic fun unpause (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
37263771
public final fun unpauseAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture;
3772+
public final fun uploadArchive (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
37273773
public final synthetic fun wait (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
37283774
public static synthetic fun wait$default (Lme/devnatan/yoki/resource/container/ContainerResource;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
37293775
public final fun waitAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture;

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ kotlin {
5252
implementation(libs.ktx.datetime)
5353
implementation(libs.bundles.ktor)
5454
implementation(libs.bundles.ktx)
55-
implementation(libs.okio)
55+
implementation(libs.kotlinx.io.core)
5656
}
5757
}
5858

@@ -71,6 +71,7 @@ kotlin {
7171
implementation(libs.junixsocket.common)
7272
implementation(libs.ktor.client.engine.okhttp)
7373
implementation(libs.slf4j.api)
74+
implementation(libs.apache.compress)
7475
}
7576
}
7677

gradle/libs.versions.toml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ ktor = "2.3.3"
66
junixsocket = "2.6.1"
77
ktx-datetime = "0.4.0"
88
junit = "5.10.0"
9-
okio = "3.5.0"
109
slf4j = "2.0.7"
10+
apache-compress = "1.23.0"
11+
kotlinx-io = "0.2.1"
1112
plugin-kotlinter = "3.16.0"
1213
plugin-publishOnCentral = "5.0.11"
1314
plugin-binaryCompatibilityValidator = "0.13.2"
@@ -82,14 +83,18 @@ version.ref = "ktx-datetime"
8283
module = "org.junit.jupiter:junit-jupiter-engine"
8384
version.ref = "junit"
8485

85-
[libraries.okio]
86-
module = "com.squareup.okio:okio"
87-
version.ref = "okio"
88-
8986
[libraries.slf4j-api]
9087
module = "org.slf4j:slf4j-api"
9188
version.ref = "slf4j"
9289

90+
[libraries.apache-compress]
91+
module = "org.apache.commons:commons-compress"
92+
version.ref = "apache-compress"
93+
94+
[libraries.kotlinx-io-core]
95+
module = "org.jetbrains.kotlinx:kotlinx-io-core"
96+
version.ref = "kotlinx-io"
97+
9398
[bundles]
9499
ktor = ["ktor-client-core", "ktor-client-serialization", "ktor-client-json", "ktor-client-content-negotiation", "ktor-serialization-kotlinx-json", "ktor-network"]
95100
ktx = ["ktx-coroutines-core", "ktx-serialization-core", "ktx-serialization-json"]

src/commonMain/kotlin/me/devnatan/yoki/io/Http.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import kotlinx.serialization.json.Json
2222
import me.devnatan.yoki.GenericDockerErrorResponse
2323
import me.devnatan.yoki.Yoki
2424
import me.devnatan.yoki.YokiResponseException
25-
import okio.ByteString.Companion.encodeUtf8
2625

2726
internal expect fun <T : HttpClientEngineConfig> HttpClientConfig<out T>.configureHttpClient(client: Yoki)
2827

@@ -45,11 +44,14 @@ internal fun createHttpClient(client: Yoki): HttpClient {
4544
handleResponseExceptionWithRequest { exception, _ ->
4645
val responseException = exception as? ResponseException ?: return@handleResponseExceptionWithRequest
4746
val exceptionResponse = responseException.response
47+
println("exceptionResponse = ${exceptionResponse.body<String>()}")
4848

49-
val error = exceptionResponse.body<GenericDockerErrorResponse>()
49+
val errorMessage = runCatching {
50+
exceptionResponse.body<GenericDockerErrorResponse>()
51+
}.getOrNull()?.message
5052
throw YokiResponseException(
5153
cause = exception,
52-
message = error.message,
54+
message = errorMessage,
5355
statusCode = exceptionResponse.status,
5456
)
5557
}
@@ -70,11 +72,12 @@ internal fun createHttpClient(client: Yoki): HttpClient {
7072
}
7173
}
7274

75+
@OptIn(ExperimentalStdlibApi::class)
7376
private fun createUrlBuilder(socketPath: String): URLBuilder = if (isUnixSocket(socketPath)) {
7477
URLBuilder(
7578
protocol = URLProtocol.HTTP,
7679
port = DOCKER_SOCKET_PORT,
77-
host = socketPath.substringAfter(UNIX_SOCKET_PREFIX).encodeUtf8().hex() + ENCODED_HOSTNAME_SUFFIX,
80+
host = socketPath.substringAfter(UNIX_SOCKET_PREFIX).toInt().toHexString() + ENCODED_HOSTNAME_SUFFIX,
7881
)
7982
} else {
8083
val url = Url(socketPath)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package me.devnatan.yoki.io
2+
3+
import kotlinx.io.RawSource
4+
5+
internal expect fun readTarFile(input: RawSource): RawSource
6+
7+
internal expect fun writeTarFile(filePath: String): RawSource
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package me.devnatan.yoki.models.container
2+
3+
import kotlinx.datetime.Instant
4+
import kotlinx.datetime.toInstant
5+
import kotlinx.serialization.SerialName
6+
import kotlinx.serialization.Serializable
7+
8+
@Serializable
9+
public data class ContainerArchiveInfo(
10+
val name: String,
11+
val size: Long,
12+
val mode: Int,
13+
@SerialName("mtime") val modifiedAtRaw: String,
14+
val linkTarget: String = "",
15+
)
16+
17+
public val ContainerArchiveInfo.modifiedAt: Instant
18+
get() = modifiedAtRaw.toInstant()

src/commonMain/kotlin/me/devnatan/yoki/resource/container/ContainerResource.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package me.devnatan.yoki.resource.container
22

33
import kotlinx.coroutines.flow.Flow
4+
import kotlinx.io.RawSource
45
import me.devnatan.yoki.YokiResponseException
56
import me.devnatan.yoki.models.Frame
67
import me.devnatan.yoki.models.ResizeTTYOptions
78
import me.devnatan.yoki.models.container.Container
9+
import me.devnatan.yoki.models.container.ContainerArchiveInfo
810
import me.devnatan.yoki.models.container.ContainerCreateOptions
911
import me.devnatan.yoki.models.container.ContainerListOptions
1012
import me.devnatan.yoki.models.container.ContainerPruneFilters
@@ -14,8 +16,11 @@ import me.devnatan.yoki.models.container.ContainerSummary
1416
import me.devnatan.yoki.models.container.ContainerWaitResult
1517
import me.devnatan.yoki.models.exec.ExecCreateOptions
1618
import me.devnatan.yoki.resource.image.ImageNotFoundException
19+
import kotlin.jvm.JvmOverloads
1720
import kotlin.time.Duration
1821

22+
internal const val FS_ROOT = "/"
23+
1924
public expect class ContainerResource {
2025

2126
/**
@@ -136,4 +141,30 @@ public expect class ContainerResource {
136141

137142
// TODO documentation
138143
public suspend fun prune(filters: ContainerPruneFilters = ContainerPruneFilters()): ContainerPruneResult
144+
145+
/**
146+
* Retrieves information about files of a container file system.
147+
*
148+
* @param container The container id.
149+
* @param path The path to the file or directory inside the container file system.
150+
*/
151+
@JvmOverloads
152+
public suspend fun archive(container: String, path: String = FS_ROOT): ContainerArchiveInfo
153+
154+
/**
155+
* Downloads files from a container file system.
156+
*
157+
* @param container The container id.
158+
* @param remotePath The path to the file or directory inside the container file system.
159+
*/
160+
public suspend fun downloadArchive(container: String, remotePath: String): RawSource
161+
162+
/**
163+
* Uploads files into a container file system.
164+
*
165+
* @param container The container id.
166+
* @param inputPath Path to the file that will be uploaded.
167+
* @param remotePath Path to the file or directory inside the container file system.
168+
*/
169+
public suspend fun uploadArchive(container: String, inputPath: String, remotePath: String)
139170
}

src/jvmMain/kotlin/me/devnatan/yoki/Yoki.jvm.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ public actual class Yoki public actual constructor(public actual val config: Yok
4747

4848
public actual fun close() {
4949
cancel()
50+
httpClient.close()
5051
}
5152
}

0 commit comments

Comments
 (0)