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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 0.2.0 (unreleased)

* Enhanced Java interoperability (#97)
* Implementation of /events to monitor system events in real time (#101)

# 0.1.1 (08-05-2023)

* MacOS and Linux native targets are now published to Maven Central as well
Expand Down
4 changes: 2 additions & 2 deletions SUPPORTED_ENDPOINTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Yoki supported Docker API endpoints

Supports 42 of 106 endpoints
Supports 43 of 106 endpoints

### Containers (15/25)
* [x] List containers - GET **/containers/json**
Expand Down Expand Up @@ -130,7 +130,7 @@ Supports 42 of 106 endpoints
* [x] Get version - GET **/version**
* [x] Ping - GET **/_ping**
* [x] Ping - HEAD **/_ping**
* [ ] Monitor events - GET **/events**
* [x] Monitor events - GET **/events**
* [ ] Get data usage information - GET **/system/df**

### Distribution (0/1)
Expand Down
203 changes: 134 additions & 69 deletions api/yoki.api

Large diffs are not rendered by default.

185 changes: 185 additions & 0 deletions src/commonMain/kotlin/me/devnatan/yoki/models/system/Event.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package me.devnatan.yoki.models.system

import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
public data class Event internal constructor(
@SerialName("Type") public val type: EventType,
@SerialName("Action") public val action: String,
@SerialName("Actor") public val actor: EventActor,
@SerialName("scope") public val scope: EventScope,
@SerialName("time") public val timeMillis: Long,
@SerialName("timeNano") public val timeNanos: Long,
)

public val Event.time: Instant get() = Instant.fromEpochMilliseconds(timeMillis)

@Serializable
public enum class EventAction {
@SerialName("attach")
ATTACH,

@SerialName("commit")
COMMIT,

@SerialName("copy")
COPY,

@SerialName("create")
CREATE,

@SerialName("delete")
DELETE,

@SerialName("destroy")
DESTROY,

@SerialName("detach")
DETACH,

@SerialName("die")
DIE,

@SerialName("exec_create")
EXEC_CREATE,

@SerialName("exec_detach")
EXEC_DETACH,

@SerialName("exec_start")
EXEC_START,

@SerialName("exec_die")
EXEC_DIE,

@SerialName("import")
IMPORT,

@SerialName("export")
EXPORT,

@SerialName("health_status")
HEALTH,

@SerialName("kill")
KILL,

@SerialName("oom")
OOM,

@SerialName("pause")
PAUSE,

@SerialName("rename")
RENAME,

@SerialName("resize")
RESIZE,

@SerialName("restart")
RESTART,

@SerialName("start")
START,

@SerialName("stop")
STOP,

@SerialName("top")
TOP,

@SerialName("unpause")
UNPAUSE,

@SerialName("update")
UPDATE,

@SerialName("prune")
PRUNE,

@SerialName("remove")
REMOVE,

@SerialName("load")
IMAGE_LOAD,

@SerialName("pull")
IMAGE_PULL,

@SerialName("push")
IMAGE_PUSH,

@SerialName("save")
IMAGE_SAVE,

@SerialName("tag")
IMAGE_TAG,

@SerialName("untag")
IMAGE_UNTAG,

@SerialName("reload")
DAEMON_RELOAD,

@SerialName("connect")
NET_CONNECT,

@SerialName("disconnect")
NET_DISCONNECT,
UNKNOWN,
}

@Serializable
public enum class EventType {
@SerialName("builder")
BUILDER,

@SerialName("config")
CONFIG,

@SerialName("container")
CONTAINER,

@SerialName("daemon")
DAEMON,

@SerialName("image")
IMAGE,

@SerialName("network")
NETWORK,

@SerialName("node")
NODE,

@SerialName("plugin")
PLUGIN,

@SerialName("secret")
SECRET,

@SerialName("service")
SERVICE,

@SerialName("volume")
VOLUME,
UNKNOWN,
}

@Serializable
public data class EventActor internal constructor(
@SerialName("ID") public val id: String,
@SerialName("Attributes") public val attributes: Map<String, String> = emptyMap(),
)

@Serializable
public enum class EventScope {
@SerialName("local")
LOCAL,

@SerialName("swarm")
SWARM,
UNKNOWN,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package me.devnatan.yoki.models.system

import kotlinx.datetime.Instant

public data class MonitorEventsOptions(
public var since: String? = null,
public var until: String? = null,
public val filters: MutableMap<String, MutableList<String>> = mutableMapOf(),
) {

/**
* Filters event by one or more types.
*
* @param types The event types to filter.
*/
public fun filterByType(vararg types: EventType): MonitorEventsOptions = apply {
types.forEach { type -> addFilter("type", type.name.lowercase()) }
}

/**
* Adds a new filter to the monitor events.
*
* @param key The filter key.
* @param value The filter value.
*/
public fun addFilter(key: String, value: String) {
filters.getOrPut(key, ::mutableListOf).add(value)
}
}

public fun MonitorEventsOptions.setSince(since: Instant?) {
this.since = since?.toString()
}

public fun MonitorEventsOptions.setUntil(until: Instant?) {
this.until = until?.toString()
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.head
import io.ktor.client.request.parameter
import io.ktor.client.request.prepareGet
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.readUTF8Line
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import me.devnatan.yoki.io.requestCatching
import me.devnatan.yoki.models.system.Event
import me.devnatan.yoki.models.system.MonitorEventsOptions
import me.devnatan.yoki.models.system.SystemPingData
import me.devnatan.yoki.models.system.SystemVersion
import kotlin.jvm.JvmOverloads
Expand All @@ -14,6 +24,7 @@ import kotlin.jvm.JvmOverloads
*/
public class SystemResource internal constructor(
private val httpClient: HttpClient,
private val json: Json,
) {

private companion object {
Expand Down Expand Up @@ -57,4 +68,34 @@ public class SystemResource internal constructor(
)
}
}

/**
* Monitors events in real-time from the server.
*
* @param options Options to filter the received events.
*/
public fun events(options: MonitorEventsOptions = MonitorEventsOptions()): Flow<Event> = flow {
requestCatching {
httpClient.prepareGet("/events") {
parameter("until", options.until)
parameter("since", options.since)
parameter("filters", json.encodeToString(options.filters))
}.execute { response ->
val channel = response.body<ByteReadChannel>()
while (true) {
val raw = channel.readUTF8Line() ?: break
val decoded = json.decodeFromString<Event>(raw)
emit(decoded)
}
}
}
}
}

/**
* Monitors events in real-time from the server.
*
* @param options Options to filter the received events.
*/
public inline fun SystemResource.events(options: MonitorEventsOptions.() -> Unit): Flow<Event> =
events(MonitorEventsOptions().apply(options))
2 changes: 1 addition & 1 deletion src/jvmMain/kotlin/me/devnatan/yoki/Yoki.jvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public actual class Yoki public actual constructor(public actual val config: Yok
public actual val secrets: SecretResource = SecretResource(httpClient, json)

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

public actual fun close() {
cancel()
Expand Down
2 changes: 1 addition & 1 deletion src/nativeMain/kotlin/me/devnatan/yoki/Yoki.native.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public actual class Yoki public actual constructor(public actual val config: Yok
public actual val networks: NetworkResource = NetworkResource(httpClient, json)
public actual val volumes: VolumeResource = VolumeResource(httpClient, json)
public actual val secrets: SecretResource = SecretResource(httpClient, json)
public actual val system: SystemResource = SystemResource(httpClient)
public actual val system: SystemResource = SystemResource(httpClient, json)

actual override fun close() {
cancel()
Expand Down