Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 46 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,74 +16,87 @@ dependencies {
}
```

## Configuration
## Getting Started

By default, at client startup if no configuration parameters are passed, the settings that will be applied will depend
on the current platform and environment variables.

For example, the socket path will be set to the value of
the [`DOCKER_HOST` environment variable](https://docs.docker.com/compose/reference/envvars/#docker_host) if set,
otherwise it will use the platform default.
Use `Yoki.create()` to create a new Yoki client instance with the default settings, default settings are based on the
current platform or environment variables, e.g.: socket path will be set to [`DOCKER_HOST`](https://docs.docker.com/compose/environment-variables/envvars/#docker_host)
if present otherwise `unix://var/run/docker.sock` if the current platform is Unix-like.

```kotlin
import me.devnatan.yoki

val dockerClient = Yoki()
val client = Yoki.create()
```

You can still configure the client by expanding the initialization block
To change the default configuration properties use `YokiConfig` and `Yoki` overload.

```kotlin
Yoki {
val client = Yoki {
// this: YokiConfigBuilder
}
```

Change socket path (docker host) or target api version
In Java code you can use `YokiConfigBuilder` with `YokiConfig.builder()`.

```kotlin
Yoki {
socketPath = "unix:///var/run/docker.sock"
apiVersion = "1.40"
}
```java
YokiConfig config = YokiConfig.builder().socketPath(...).build()
Yoki client = Yoki.create(config)
```

## Usage
To Docker resources, functions will return `CompletableFuture<T>` or `YokiFlow<T>` (for streaming) due to Java Interoperatibility
but there are extensions for Kotlin that are `suspend` and for streaming returns `Flow<T>`.

The way to access resources is straight to the point, all functions (for Kotlin) are suspend.

##### Get info about system version
##### Get System Information

```kotlin
val version: SystemVersion = dockerClient.system.version()
val version: SystemVersion = client.system.version()
```

##### Listing all containers
##### List All Containers

```kotlin
val containers: List<Container> = dockerClient.containers.list {
all = true
}
val containers: List<Container> = client.containers.list()
```

##### Creating a new network
##### Create a new Network

```kotlin
val networkId: String = dockerClient.networks.create {
val networkId: String = client.networks.create {
name = "octopus-net"
driver = "overlay"
}
```

##### Streaming container logs

All streaming methods will always return a [Flow](https://kotlinlang.org/docs/flow.html).
##### Stream Container Logs

```kotlin
val logs: Flow<Frame> = dockerClient.containers.logs("floral-fury") {
val logs: Flow<Frame> = client.containers.logs("floral-fury") {
stderr = true
stdout = true
}

logs.onStart { /* streaming started */ }
.onCompletion { /* streaming finished */ }
.catch { /* something went wrong */ }
.collect { log -> /* do something with each log */ }
```
```java
final YokiFlow<Frame> callback = new YokiFlow<Frame>() {
@Override
public void onEach(Frame log) { /* do something with each log */ }

@Override
public void onStart() { /* streaming started */ }

@Override
public void onComplete(Throwable error) { /* streaming finished */ }

@Override
public void onError(Throwable cause) { /* something went wrong */ }
};

client.containers.logsAsync("floral-fury", callback);

// Short version
client.containers.logsAsync("floral-fury", (log) -> /* do something with each log */);
```

## License
Expand Down
3,802 changes: 3,802 additions & 0 deletions api/yoki.api

Large diffs are not rendered by default.

35 changes: 19 additions & 16 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ kotlin {
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
}
compilations.all {
kotlinOptions {
freeCompilerArgs = listOf("-Xjvm-default=all")
}
}
}

val hostOs = System.getProperty("os.name")
var isNativeUnsupported = false
when (hostOs) {
"Mac OS X" -> macosX64("native")
"Linux" -> linuxX64("native")
else -> isNativeUnsupported = true
}
linuxX64()
macosX64()

sourceSets {
val commonMain by getting {
Expand Down Expand Up @@ -80,18 +80,21 @@ kotlin {
}
}

if (!isNativeUnsupported) {
val nativeMain by getting {
dependsOn(commonMain)
dependencies {
implementation(libs.ktor.client.engine.cio)
}
val nativeMain by creating {
dependsOn(commonMain)
dependencies {
implementation(libs.ktor.client.engine.cio)
}
}

val nativeTest by getting {
dependsOn(commonTest)
}
val nativeTest by creating {
dependsOn(commonTest)
}

val linuxX64Main by getting { dependsOn(nativeMain) }
val linuxX64Test by getting { dependsOn(nativeTest) }
val macosX64Main by getting { dependsOn(nativeMain) }
val macosX64Test by getting { dependsOn(nativeTest) }
}
}

Expand Down
118 changes: 13 additions & 105 deletions src/commonMain/kotlin/me/devnatan/yoki/Yoki.kt
Original file line number Diff line number Diff line change
@@ -1,120 +1,28 @@
package me.devnatan.yoki

import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.json.Json
import me.devnatan.yoki.YokiConfigBuilder.Companion.DEFAULT_DOCKER_API_VERSION
import me.devnatan.yoki.logging.Logger
import me.devnatan.yoki.logging.createLogger
import me.devnatan.yoki.net.createHttpClient
import me.devnatan.yoki.resource.container.ContainerResource
import me.devnatan.yoki.resource.exec.ExecResource
import me.devnatan.yoki.resource.image.ImageResource
import me.devnatan.yoki.resource.network.NetworkResource
import me.devnatan.yoki.resource.secret.SecretResource
import me.devnatan.yoki.resource.system.SystemResource
import me.devnatan.yoki.resource.volume.VolumeResource
import kotlin.jvm.JvmStatic

/**
* Creates a new Yoki instance with platform default socket path and [DEFAULT_DOCKER_API_VERSION] Docker API version
* that'll be merged with specified configuration.
*
* @param config Yoki configuration.
*/
public inline fun Yoki(
crossinline config: YokiConfigBuilder.() -> Unit = {},
): Yoki {
return Yoki(
YokiConfigBuilder()
.forCurrentPlatform()
.apply(config)
.build(),
)
}

/**
* Yoki's heart where all resource accessors and other things are located.
*
* Create and configure a fresh Yoki instance by calling [Yoki.create] or [me.devnatan.yoki.Yoki].
*
* Note: This class must be a singleton, that is, don't instantiate it more than once in your code, and, implements
* [Closeable] so be sure to [close] it after use.
*/
@YokiDsl
public class Yoki @PublishedApi internal constructor(public val config: YokiConfig) : Closeable {

private val httpClient: HttpClient = createHttpClient(this)
private val json: Json = Json {
ignoreUnknownKeys = true
}
private val logger: Logger = createLogger()

@get:JvmName("containers")
public val containers: ContainerResource = ContainerResource(httpClient, json, logger)

@get:JvmName("networks")
public val networks: NetworkResource = NetworkResource(httpClient, json)

@get:JvmName("volumes")
public val volumes: VolumeResource = VolumeResource(httpClient, json)

@get:JvmName("secrets")
public val secrets: SecretResource = SecretResource(httpClient, json)

@get:JvmName("images")
public val images: ImageResource = ImageResource(httpClient, json)
public expect class Yoki(config: YokiConfig) : CoroutineScope {

@get:JvmName("exec")
public val exec: ExecResource = ExecResource(httpClient)
public val config: YokiConfig
public val json: Json
public val httpClient: HttpClient
public val images: ImageResource
public val exec: ExecResource
public val containers: ContainerResource
public val networks: NetworkResource
public val volumes: VolumeResource
public val secrets: SecretResource
public val system: SystemResource

@get:JvmName("system")
public val system: SystemResource = SystemResource(httpClient)

public override fun close() {
httpClient.close()
}

public companion object {

/**
* Creates a new Yoki instance with platform default socket path and targeting [DEFAULT_DOCKER_API_VERSION]
* Docker API version.
*/
@JvmStatic
public fun create(): Yoki = Yoki()

/**
* Creates a new Yoki instance.
*
* @param config Configurations to the instance.
*/
@JvmStatic
public fun create(config: YokiConfig): Yoki = Yoki(config)

/**
* Creates a new Yoki instance with the specified socket path configuration.
*
* @param socketPath The socket path that'll be used on connection.
*/
@JvmStatic
public fun create(socketPath: String): Yoki = Yoki { socketPath(socketPath) }

/**
* Creates a new Yoki instance using UNIX defaults configuration.
*/
@JvmStatic
public fun createWithUnixDefaults(): Yoki = Yoki { useUnixDefaults() }

/**
* Creates a new Yoki instance using HTTP defaults configuration.
*/
@JvmStatic
public fun createWithHttpDefaults(): Yoki = Yoki { useHttpDefaults() }
}
public fun close()
}

/**
* DslMarker for Yoki.
*/
@DslMarker
public annotation class YokiDsl
16 changes: 8 additions & 8 deletions src/commonMain/kotlin/me/devnatan/yoki/YokiConfig.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package me.devnatan.yoki

import me.devnatan.yoki.net.DEFAULT_DOCKER_HTTP_SOCKET
import me.devnatan.yoki.net.DEFAULT_DOCKER_UNIX_SOCKET
import me.devnatan.yoki.net.HTTP_SOCKET_PREFIX
import me.devnatan.yoki.net.UNIX_SOCKET_PREFIX
import me.devnatan.yoki.io.DEFAULT_DOCKER_HTTP_SOCKET
import me.devnatan.yoki.io.DEFAULT_DOCKER_UNIX_SOCKET
import me.devnatan.yoki.io.HTTP_SOCKET_PREFIX
import me.devnatan.yoki.io.UNIX_SOCKET_PREFIX
import kotlin.jvm.JvmStatic

internal val DefaultYokiConfig = YokiConfig.builder().forCurrentPlatform().build()

/**
* Class to store all Yoki configurations.
Expand All @@ -14,10 +17,7 @@ import me.devnatan.yoki.net.UNIX_SOCKET_PREFIX
* @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) {

init {
check(socketPath.isNotBlank()) { "Socket path must be provided and cannot be blank" }
Expand Down
19 changes: 19 additions & 0 deletions src/commonMain/kotlin/me/devnatan/yoki/YokiFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@file:JvmSynthetic

package me.devnatan.yoki

import me.devnatan.yoki.YokiConfigBuilder.Companion.DEFAULT_DOCKER_API_VERSION
import kotlin.jvm.JvmSynthetic

/**
* Creates a new Yoki instance with platform default socket path and [DEFAULT_DOCKER_API_VERSION] Docker API version
* that'll be merged with specified configuration.
*
* @param configure The client configuration.
*/
public inline fun Yoki(crossinline configure: YokiConfigBuilder.() -> Unit): Yoki = Yoki(
YokiConfigBuilder()
.forCurrentPlatform()
.apply(configure)
.build(),
)
Loading