UNPKG

docker-api-nodejs

Version:
1 lines 181 kB
{"version":3,"sources":["../src/api/Modem.ts","../src/utils/Handle.ts","../src/stack/stackDeps.ts","../src/conf/config.ts","../src/conf/configs.ts","../src/container/container.ts","../src/container/containers.ts","../src/image/image.ts","../src/utils/HandleLogWrapper.ts","../src/image/images.ts","../src/network/network.ts","../src/network/networks.ts","../src/node/node.ts","../src/node/nodes.ts","../src/plugin/plugin.ts","../src/plugin/plugins.ts","../src/swarm/swarm.ts","../src/secret/secret.ts","../src/secret/secrets.ts","../src/service/services.ts","../src/service/service.ts","../src/system/system.ts","../src/task/task.ts","../src/task/tasks.ts","../src/volume/volume.ts","../src/volume/clusterVolume.ts","../src/volume/volumes.ts","../src/stack/stack.ts","../src/stack/stacks.ts"],"sourcesContent":["import https from \"https\";\nimport http from \"http\";\n\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { Socket } from \"net\";\nimport querystring from \"node:querystring\";\n\nexport type ModemDialMethod = \"GET\" | \"POST\" | \"HEAD\" | \"PUT\" | \"DELETE\" | \"PATCH\";\nexport type ModemDialResponseResult = object | null;\nexport type ModemDialStreamResult = http.IncomingMessage;\nexport type ModemDialResult = ModemDialResponseResult | ModemDialStreamResult | Socket;\n\ninterface ModemDialResultContentHandler { (raw: string): ModemDialResponseResult; }\n\nexport interface ModemDialOptions {\n path: string,\n method: ModemDialMethod,\n headers?: Map<string, string>,\n query?: Map<string, Array<string>>,\n body?: object | string,\n\n isStream?: boolean // CHECK if this is not needed in future api verisons\n}\n\nexport default class Modem {\n private static readonly version = \"v1.44\";\n private static readonly responseStreamContentType = [\n \"application/vnd.docker.raw-stream\",\n \"application/vnd.docker.multiplexed-stream\"\n ];\n\n private readonly socketPath: string;\n\n constructor(socketPath: string) {\n this.socketPath = socketPath;\n }\n\n private static responseIsStream(response: http.IncomingMessage): boolean {\n // CHECK maybe this can also be identified by the connection: keep-alive header\n const contentType = response.headers[\"content-type\"];\n return contentType ? Modem.responseStreamContentType.includes(contentType) : false;\n }\n\n private static getResponseContentHandler(response: http.IncomingMessage): ModemDialResultContentHandler {\n switch (response.headers[\"content-type\"]) {\n case \"application/json\": return (raw: string) => { return JSON.parse(raw); };\n default: return (raw: string) => null;\n }\n }\n\n private static getQueryString(query?: Map<string, Array<string>>) {\n return query && query.size > 0 ? \"?\" + querystring.stringify(Object.fromEntries(query)) : \"\";\n }\n\n public async dial(options: ModemDialOptions) {\n const socketPath = this.socketPath;\n const path = (Modem.version ? \"/\" + Modem.version : \"\") + options.path;\n\n const body = options.body ? JSON.stringify(options.body) : null;\n const headers: http.IncomingHttpHeaders = options.headers ? Object.fromEntries(options.headers) : {}; // IDEA consider switching to https later\n \n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n headers[\"Content-Length\"] = Buffer.byteLength(body).toString();\n }\n\n return await new Promise<ModemDialResult>(function(resolve, reject) {\n let finished = false;\n\n const requestOptions: http.RequestOptions = {\n socketPath: socketPath,\n path: path + Modem.getQueryString(options.query),\n method: options.method,\n headers: headers\n };\n\n const request = http.request(requestOptions);\n\n request.on(\"upgrade\", function(response, socket, head) {\n if (finished)\n return;\n\n finished = true;\n resolve(socket);\n });\n\n request.on(\"response\", function(response) {\n const status = response.statusCode;\n\n if (!status) {\n reject(new Error(\"Status is not defined\"));\n return;\n }\n\n if (Modem.responseIsStream(response) || options.isStream) {\n if (finished)\n return;\n\n finished = true;\n resolve(response);\n return;\n }\n\n // CHECK it seems like the response does not always set content-length (e.g. /networks) maybe there is another way of telling\n // const contentLength = headers[\"content-length\"] ? Number.parseInt(headers[\"content-length\"]) : 0;\n const rawHandler = Modem.getResponseContentHandler(response);\n const isOk = status < 300;\n\n let chunks = new Array();\n\n const pushChunk = function(chunk: any) { chunks.push(chunk); };\n const responseEnd = function() {\n if (finished)\n return;\n\n finished = true;\n\n const buffer = Buffer.concat(chunks);\n const raw = buffer.toString(\"utf-8\");\n const parsed = rawHandler(raw);\n\n if (isOk)\n resolve(parsed);\n else\n reject(parsed);\n };\n\n // response.setEncoding(\"utf-8\");\n response.on(\"error\", reject);\n response.on(\"data\", pushChunk);\n response.on(\"end\", responseEnd);\n response.on(\"close\", responseEnd);\n });\n\n if (body)\n request.write(body);\n\n request.on(\"error\", reject);\n request.end();\n }).catch(function(err) { return Promise.reject(\"message\" in err ? err.message : err); });\n }\n}","import Modem from \"../api/Modem\";\n\nexport default abstract class Handle {\n protected readonly modem: Modem;\n public readonly id: string;\n\n constructor(modem: Modem, id: string) {\n this.modem = modem;\n this.id = id;\n }\n}","import Modem from \"../api/Modem\";\nimport { ConfigSpec } from \"../conf/config\";\nimport { SecretSpec } from \"../secret/secret\";\nimport { ServiceSpec } from \"../service/service\";\nimport { StackScratchSpec } from \"./stack\";\n\nconst DOCKER_STACK_IMAGE_LABEL = \"com.docker.stack.image\";\nexport const DOCKER_STACK_NAMESPACE_LABEL = \"com.docker.stack.namespace\";\n\nexport function createStackFromScratch(modem: Modem, spec: StackScratchSpec) {\n \n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport { DOCKER_STACK_NAMESPACE_LABEL } from \"../stack/stackDeps\";\nimport Handle from \"../utils/Handle\";\nimport Stackable from \"../utils/Stackable\";\nimport { DockerLabels, Driver, SwarmBase } from \"../utils/types\";\n\nexport type ConfigId = string;\n\nexport type ConfigSpec = {\n Name: string,\n Labels?: DockerLabels,\n Data: string,\n Templating?: Driver\n};\n\nexport interface Config extends SwarmBase<ConfigId> {\n Spec: ConfigSpec\n}\n\nexport default class ConfigHandle extends Handle implements Stackable {\n async inspect() {\n const call: ModemDialOptions = {\n path: \"/configs/\" + this.id,\n method: \"GET\"\n };\n\n return await this.modem.dial(call) as Config;\n }\n\n /**\n * @see https://docs.docker.com/engine/api/v1.44/#tag/Config/operation/ConfigUpdate\n * @param spec \n */\n async update(spec: ConfigSpec) {\n const current = await this.inspect();\n const version = current.Version.Index;\n\n const query = new Map();\n query.set(\"version\", version);\n\n const newSpec = current.Spec;\n newSpec.Labels = spec.Labels;\n\n const call: ModemDialOptions = {\n path: \"/configs/\" + this.id + \"/update\",\n method: \"POST\",\n query: query,\n body: newSpec\n };\n\n await this.modem.dial(call);\n }\n\n async remove() {\n const current = await this.inspect();\n\n if (this.shouldThrowStackError() && ConfigHandle.isPartOfStack(current))\n throw new Error(\"The current config was created by a stack, it can not be removed individually\");\n\n const call: ModemDialOptions = {\n path: \"/configs/\" + this.id,\n method: \"DELETE\"\n };\n\n await this.modem.dial(call);\n }\n\n public shouldThrowStackError() {\n return true;\n }\n\n static isPartOfStack(config: Config) {\n return config.Spec.Labels && DOCKER_STACK_NAMESPACE_LABEL in config.Spec.Labels;\n }\n\n static fromConfig(modem: Modem, config: Config) {\n const id = config.ID;\n return new ConfigHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport ConfigHandle, { Config, ConfigId, ConfigSpec } from \"./config\";\n\ntype ConfigListFilterType = \"id\" | \"label\" | \"name\" | \"names\";\n\nexport type ConfigListFilters = Map<ConfigListFilterType, Array<string>>;\nexport type ConfigListResult = Array<Config>;\n\nexport type ConfigListQueryOptions = {\n filters?: ConfigListFilters\n};\n\ntype ConfigCreateResult = {\n ID: ConfigId\n};\n\nexport default class Configs {\n public static async list(modem: Modem, queryOptions: ConfigListQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/configs\",\n method: \"GET\",\n query: query\n };\n\n return await modem.dial(call) as ConfigListResult;\n }\n\n public static async create(modem: Modem, spec: ConfigSpec) {\n const call: ModemDialOptions = {\n path: \"/configs/create\",\n method: \"POST\",\n body: spec\n };\n\n const result = await modem.dial(call) as ConfigCreateResult;\n return new ConfigHandle(modem, result.ID);\n }\n\n public static async exists(modem: Modem, name: string) {\n const filters = new Map();\n filters.set(\"name\", Array.of(name));\n\n const configList = await Configs.list(modem, { filters });\n return { exists: configList.length > 0, handle: configList.length > 0 ? ConfigHandle.fromConfig(modem, configList[0]) : undefined };\n }\n\n public static getHandle(modem: Modem, id: ConfigId) {\n return new ConfigHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions, ModemDialResult, ModemDialStreamResult } from \"../api/Modem\";\nimport Handle from \"../utils/Handle\";\nimport { DockerLabels, Port, EndpointSettings, MountPoint, HealthConfig, Health, HostConfig, GraphDriverData, PortMap, Address, HostConfigBase, RestartPolicy } from \"../utils/types\";\n\nexport type ContainerId = string;\n\ntype ContainerHostConfig = {\n NetworkMode?: string\n};\n\ntype ContainerSummaryNetworkSettings = {\n Networks: {\n [name: string]: EndpointSettings\n }\n};\n\ninterface ContainerBase {\n Id: ContainerId,\n Image: string,\n Mounts: Array<MountPoint>\n}\n\nexport interface ContainerSummary extends ContainerBase {\n Names: Array<string>,\n ImageID: string,\n Command: string,\n Created: number,\n Ports: Array<Port>,\n SizeRw: number,\n SizeRootFs: number,\n Labels: DockerLabels,\n State: string,\n Status: string,\n HostConfig: ContainerHostConfig,\n NetworkSettings: ContainerSummaryNetworkSettings,\n}\n\nexport type ContainerStatus = {\n ContainerID: ContainerId,\n PID: string,\n ExitCode: number\n};\n\n/**\n * Format: <port>/<tcp|udp|sctp>: {}\n */\ntype ContainerSpecExposedPorts = {\n [port: string]: object\n};\n\ntype ContainerVolumes = {\n [name: string]: object\n};\n\ntype ContainerSpecNetworkingConfig = {\n EndpointsConfig: EndpointSettings\n};\n\nexport type ContainerConfig = {\n Hostname?: string,\n Domainname?: string,\n User?: string,\n AttachStdin?: boolean,\n AttachStdout?: boolean,\n AttachStderr?: boolean,\n ExposedPorts?: ContainerSpecExposedPorts,\n Tty?: boolean,\n OpenStdin?: boolean,\n StdinOnce?: boolean,\n Env?: Array<string>,\n Cmd?: Array<string>,\n Healthcheck?: HealthConfig,\n ArgsEscaped?: boolean | null,\n Image: string,\n Volumes?: ContainerVolumes,\n WorkingDir?: string,\n Entrypoint?: Array<string>,\n NetworkDisabled?: boolean | null,\n MacAddress?: string | null,\n OnBuild?: Array<string> | null,\n Labels?: DockerLabels,\n StopSignal?: string | null,\n StopTimeout?: number | null,\n Shell?: Array<string> | null,\n}\n\nexport type ContainerSpec = ContainerConfig & {\n HostConfig?: ContainerHostConfig,\n NetworkingConfig?: ContainerSpecNetworkingConfig\n};\n\ntype ContainerStateStatus = \"created\" | \"running\" | \"paused\" | \"restarting\" | \"removing\" | \"exited\" | \"dead\";\ntype ContainerState = {\n Status: ContainerStateStatus,\n Running: boolean,\n Paused: boolean,\n Restarting: boolean,\n OOMKilled: boolean,\n Dead: boolean,\n Pid: number,\n ExitCode: number,\n Error: string,\n StartedAt: string,\n FinishedAt: string,\n Health: Health\n};\n\ntype ContainerNetworkSettings = ContainerSummaryNetworkSettings & {\n Bridge: string,\n SandboxID: string,\n HairpinMode: boolean,\n LinkLocalIPv6Address: string,\n LinkLocalIPv6PrefixLen: number,\n Ports: PortMap,\n SandboxKey: string,\n SecondaryIPAddresses: Array<Address> | null,\n SecondaryIPv6Addresses: Array<Address> | null\n}\n\nexport interface Container extends ContainerBase {\n Created: string,\n Path: string,\n Args: Array<string>,\n State: ContainerState,\n ResolvConfPath: string,\n HostnamePath: string,\n HostsPath: string,\n LogPath: string,\n Name: string,\n RestartCount: number,\n Driver: string,\n Platform: string,\n MountLabel: string,\n ProcessLabel: string,\n AppArmorProfile: string,\n ExecIds: Array<string> | null,\n HostConfig: HostConfig,\n GraphDriver: GraphDriverData,\n SizeRw: number,\n SizeRootFs: number,\n Mounts: Array<MountPoint>,\n Config: ContainerConfig,\n NetworkSettings: ContainerNetworkSettings\n};\n\nexport type ContainerUpdateConfig = HostConfigBase & {\n RestartPolicy: RestartPolicy\n};\n\nexport type ContainerInspectQueryOptions = {\n size?: boolean\n};\n\nexport type ContainerListProcessesQueryOptions = {\n ps_args?: string\n};\n\nexport type ContainerLogsQueryOptions = {\n follow?: boolean,\n stdout?: boolean,\n stderr?: boolean,\n since?: number,\n until?: number,\n timestamps?: boolean,\n tail?: \"all\" | number\n}\n\nexport type ContainerListProcessesResult = {\n Titles: Array<string>,\n Processes: Array<Array<string>>\n};\n\nexport type ContainerChangesResult = Array<{\n Path: string,\n Kind: 0 | 1 | 2\n}>;\n\nexport type ContainerStatsQueryOptions = {\n stream?: boolean,\n\n /**\n * Has to be used with stream=false \n * Returns values immediately instead of waiting 2 cycles\n */\n oneShot?: boolean\n};\n\nexport type ContainerResizeQueryOptions = {\n w: number,\n h: number\n};\n\nexport type ContainerStartQueryOptions = {\n detachKeys?: string\n};\n\nexport type ContainerKillQueryOptions = {\n signal?: string,\n}\n\nexport type ContainerStopQueryOptions = ContainerKillQueryOptions & {\n t?: number\n};\n\nexport type ContainerRenameQueryOptions = {\n name: string\n};\n\nexport type ContainerWaitQueryOptions = {\n condition?: \"not-running\" | \"next-exit\" | \"removed\"\n};\n\nexport type ContainerRemoveQueryOptions = {\n v?: boolean,\n force?: boolean,\n link?: boolean\n};\n\nexport type ContainerUpdateResult = {\n Warnings: Array<string>\n};\n\nexport default class ContainerHandle extends Handle {\n async inspect(queryOptions: ContainerInspectQueryOptions = {}) {\n const query = new Map();\n if (\"size\" in queryOptions) query.set(\"size\", queryOptions.size ? \"true\" : \"false\");\n\n const call: ModemDialOptions = {\n path: \"/containers/\" + this.id + \"/json\",\n method: \"GET\",\n query: query\n };\n\n return await this.modem.dial(call) as Container;\n }\n\n async listProcesses(queryOptions: ContainerListProcessesQueryOptions = {}) {\n const query = new Map();\n query.set(\"ps_args\", queryOptions.ps_args ?? \"-ef\");\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/top`,\n method: \"GET\",\n query: query\n };\n\n return await this.modem.dial(call) as ContainerListProcessesResult;\n }\n\n public async getLogs(queryOptions: ContainerLogsQueryOptions = {}) {\n const query = new Map();\n query.set(\"follow\", queryOptions.follow ? \"true\" : \"false\");\n query.set(\"stdout\", queryOptions.stdout ? \"true\" : \"false\");\n query.set(\"stderr\", queryOptions.stderr ? \"true\" : \"false\");\n query.set(\"since\", queryOptions.since ?? 0);\n query.set(\"until\", queryOptions.until ?? 0);\n query.set(\"timestamps\", queryOptions.timestamps ? \"true\" : \"false\");\n query.set(\"tail\", queryOptions.tail ?? \"all\");\n\n const call : ModemDialOptions = {\n path: `/containers/${this.id}/logs`,\n method: \"GET\",\n query: query\n };\n\n return await this.modem.dial(call) as ModemDialStreamResult;\n }\n\n async changes() {\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/changes`,\n method: \"GET\",\n };\n\n return await this.modem.dial(call) as ContainerChangesResult;\n }\n\n async export() {\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/export`,\n method: \"GET\",\n isStream: true\n };\n\n return await this.modem.dial(call) as ModemDialStreamResult;\n }\n\n async stats(queryOptions: ContainerStatsQueryOptions = {}) {\n const query = new Map();\n if (\"stream\" in queryOptions) query.set(\"stream\", queryOptions.stream ? \"true\" : \"false\");\n if (\"oneShot\" in queryOptions) query.set(\"one-shot\", queryOptions.oneShot ? \"true\" : \"false\");\n\n const call: ModemDialOptions = {\n path: \"/containers/\" + this.id + \"/stats\",\n method: \"GET\",\n query: query,\n isStream: queryOptions.stream\n };\n\n return await this.modem.dial(call) as ModemDialStreamResult;\n }\n\n async resize(queryOptions: ContainerResizeQueryOptions) {\n const query = new Map();\n query.set(\"h\", queryOptions.h);\n query.set(\"w\", queryOptions.w);\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/resize`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async start(queryOptions: ContainerStartQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.detachKeys) query.set(\"detachKeys\", queryOptions.detachKeys);\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/start`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async stop(queryOptions: ContainerStopQueryOptions = {}) {\n const query = new Map();\n query.set(\"signal\", queryOptions.signal ?? \"SIGINT\");\n query.set(\"t\", typeof queryOptions.t === \"undefined\" ? 5 : queryOptions.t);\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/stop`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async restart(queryOptions: ContainerStopQueryOptions = {}) {\n const query = new Map();\n query.set(\"signal\", queryOptions.signal ?? \"SIGINT\");\n query.set(\"t\", queryOptions.t);\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/restart`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async kill(queryOptions: ContainerKillQueryOptions = {}) {\n const query = new Map();\n query.set(\"signal\", queryOptions.signal ?? \"SIGKILL\");\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/kill`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n public async update(updateConfig: ContainerUpdateConfig) {\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/update`,\n method: \"POST\",\n body: updateConfig\n };\n\n return await this.modem.dial(call) as ContainerUpdateResult;\n }\n\n async rename(queryOptions: ContainerRenameQueryOptions) {\n const query = new Map();\n query.set(\"name\", queryOptions.name);\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/rename`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async pause() {\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/pause`,\n method: \"GET\",\n isStream: true\n };\n\n return await this.modem.dial(call);\n }\n\n async unpause() {\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/unpause`,\n method: \"GET\",\n isStream: true\n };\n\n return await this.modem.dial(call);\n }\n\n // TODO attach to container\n // TODO attach to container via websocket\n\n async wait(queryOptions: ContainerWaitQueryOptions = {}) {\n const query = new Map();\n query.set(\"condition\", queryOptions.condition ?? \"not-running\");\n\n const call: ModemDialOptions = {\n path: `/containers/${this.id}/wait`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async remove(queryOptions: ContainerRemoveQueryOptions = {}) {\n const query = new Map();\n query.set(\"v\", queryOptions.v ? \"true\" : \"false\");\n query.set(\"force\", queryOptions.force ? \"true\" : \"false\");\n query.set(\"link\", queryOptions.link ? \"true\" : \"false\");\n\n const call: ModemDialOptions = {\n path: \"/containers/\" + this.id,\n method: \"DELETE\",\n query: query\n };\n\n await this.modem.dial(call);\n }\n\n // TODO implement archive endpoints\n\n static fromContainer(modem: Modem, container: ContainerSummary) {\n return new ContainerHandle(modem, container.Id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport ContainerHandle, { Container, ContainerId, ContainerSpec, ContainerSummary } from \"./container\";\n\ntype ContainerListFilterType = \"ancestor\" | \"before\" | \"expose\" | \"exited\" | \"isolation\" | \"is-task\" | \"label\" | \"name\" | \"network\" | \"publish\" | \"since\" | \"status\" | \"volume\";\ntype ContainerListFilters = Map<ContainerListFilterType, Array<string>>;\n\ntype ContainerPruneFilterType = \"until\" | \"label\";\ntype ContainerPruneFilters = Map<ContainerPruneFilterType, Array<string>>;\n\nexport type ContainerListQueryOptions = {\n all?: boolean,\n limit?: number,\n size?: boolean,\n filters?: ContainerListFilters\n};\n\nexport type ContainerPruneQueryOptions = {\n filters?: ContainerPruneFilters\n};\n\nexport type ContainerListResult = Array<ContainerSummary>;\ntype ContainerCreateResult = {\n Id: string,\n Warnings: Array<string>\n};\ntype ContainerPruneResult = {\n ContainersDeleted: Array<string>,\n SpaceReclaimed: number\n};\n\nexport default class Containers {\n static async list(modem: Modem, queryOptions: ContainerListQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.all) query.set(\"all\", queryOptions.all ? \"true\" : \"false\");\n if (queryOptions.size) query.set(\"size\", queryOptions.size ? \"true\" : \"false\");\n if (queryOptions.limit) query.set(\"limit\", queryOptions.limit);\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/containers/json\",\n method: \"GET\",\n query: query\n };\n\n return await modem.dial(call) as ContainerListResult;\n }\n\n static async create(modem: Modem, spec: ContainerSpec) {\n const call: ModemDialOptions = {\n path: \"/containers/create\",\n method: \"POST\",\n body: spec\n };\n\n const result = await modem.dial(call) as ContainerCreateResult;\n return new ContainerHandle(modem, result.Id);\n }\n\n static async prune(modem: Modem, queryOptions: ContainerPruneQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/containers/prune\",\n method: \"POST\",\n query: query\n };\n\n return await modem.dial(call) as ContainerPruneResult;\n }\n\n static getHandle(modem: Modem, id: ContainerId) {\n return new ContainerHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport { ContainerConfig } from \"../container/container\";\nimport Handle from \"../utils/Handle\";\nimport { DockerLabels, GraphDriverData } from \"../utils/types\";\n\nexport type ImageId = string;\n\ntype ImageConfig = ContainerConfig;\n\ninterface ImageBase {\n Id: ImageId,\n ParentId: ImageId,\n RepoTags: Array<string>,\n RepoDigests: Array<string>,\n Created: number,\n Size: number,\n VirtualSize?: number\n}\n\nexport interface ImageSummary extends ImageBase {\n SharedSize: number,\n Labels: DockerLabels,\n Containers: number\n}\n\ntype ImageRootFS = {\n Type: string,\n Layers: Array<string>\n};\n\ntype ImageMetadata = {\n LastTagTime: string | null\n}\n\nexport interface Image extends ImageBase {\n Comment: string,\n Container: string,\n ContainerConfig: ContainerConfig,\n DockerVersion: string,\n Author: string,\n Config: ImageConfig,\n Architecture: string,\n Variant: string,\n Os: string,\n OsVersion: string,\n GraphDriver: GraphDriverData,\n RootFS: ImageRootFS,\n Metadata: ImageMetadata\n}\n\ntype ImageLayer = {\n Id: string,\n Created: number,\n CreatedBy: string,\n Tags: Array<string>,\n Size: number,\n Comment: string\n};\n\nexport type ImageHistoryResult = Array<ImageLayer>;\n\nexport type ImagePushQueryOptions = {\n tag?: string\n};\n\nexport type ImageTagQueryOptions = {\n repo: string,\n tag: string\n};\n\nexport type ImageRemoveQueryOptions = {\n force?: boolean,\n noprune?: false\n};\n\nexport default class ImageHandle extends Handle {\n async inspect() {\n const call: ModemDialOptions = {\n path: \"/images/\" + this.id + \"/json\",\n method: \"GET\"\n };\n\n return await this.modem.dial(call) as Image;\n }\n\n async history() {\n const call: ModemDialOptions = {\n path: `/images/${this.id}/history`,\n method: \"GET\"\n };\n\n return await this.modem.dial(call) as ImageHistoryResult;\n }\n\n async push(queryOptions: ImagePushQueryOptions = {}, registryAuth?: string) {\n const headers = new Map();\n if (registryAuth) headers.set(\"X-Registry-Auth\", registryAuth);\n\n const query = new Map();\n if (queryOptions.tag) query.set(\"tag\", queryOptions.tag);\n\n const call: ModemDialOptions = {\n path: `/images/${this.id}/push`,\n method: \"POST\",\n headers: headers,\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async tag(queryOptions: ImageTagQueryOptions) {\n const query = new Map();\n query.set(\"repo\", queryOptions.repo);\n query.set(\"tag\", queryOptions.tag);\n\n const call: ModemDialOptions = {\n path: `/images/${this.id}/tag`,\n method: \"POST\",\n query: query\n };\n\n return await this.modem.dial(call);\n }\n\n async remove(queryOptions: ImageRemoveQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.force) query.set(\"force\", queryOptions.force ? \"true\" : \"false\");\n if (queryOptions.noprune) query.set(\"noprune\", queryOptions.noprune ? \"true\" : \"false\");\n\n const call: ModemDialOptions = {\n path: \"/images/\" + this.id,\n method: \"DELETE\",\n query: query\n };\n\n await this.modem.dial(call);\n }\n\n static fromImage(modem: Modem, image: Image) {\n return new ImageHandle(modem, image.Id);\n }\n}","import Modem, { ModemDialStreamResult } from \"../api/Modem\";\nimport Handle from \"./Handle\";\n\nexport default class HandleLogWrapper<HandleType extends Handle> {\n private readonly handle: HandleType;\n private readonly logStream?: ModemDialStreamResult;\n private readonly logChunks: Array<string>;\n\n public constructor(handle: HandleType, logStream?: ModemDialStreamResult) {\n this.handle = handle;\n this.logStream = logStream;\n this.logChunks = new Array();\n }\n\n public async drainLogStream() {\n if (!this.logStream)\n return;\n \n const handle = this;\n const stream = this.logStream;\n\n await new Promise(function(resolve) {\n stream.on(\"data\", function(data) { handle.logChunks.push(data.toString()); });\n stream.on(\"end\", resolve);\n });\n }\n\n public get Handle() { return this.handle; }\n public get LogChunks() { return this.logChunks; }\n}","import Modem, { ModemDialOptions, ModemDialStreamResult } from \"../api/Modem\";\nimport { ContainerConfig } from \"../container/container\";\nimport HandleLogWrapper from \"../utils/HandleLogWrapper\";\nimport ImageHandle, { ImageId, ImageSummary } from \"./image\";\n\ntype ImageListFilterType = \"before\" | \"dangling\" | \"label\" | \"reference\" | \"since\" | \"until\";\ntype ImageBuildPruneFilterType = \"until\" | \"id\" | \"parent\" | \"type\" | \"description\" | \"inuse\" | \"shared\" | \"private\";\ntype ImageSearchFilterTypes = \"is-official\" | \"stars\";\ntype ImagePruneFilterType = \"dangling\" | \"until\" | \"label\";\n\nexport type ImageListFilters = Map<ImageListFilterType, Array<string>>;\nexport type ImageBuildPruneFilters = Map<ImageBuildPruneFilterType, Array<string>>;\nexport type ImageSearchFilters = Map<ImageSearchFilterTypes, Array<string>>;\nexport type ImagePruneFilters = Map<ImagePruneFilterType, Array<string>>;\n\nexport type ImageListQueryOptions = {\n all?: boolean,\n filters?: ImageListFilters,\n sharedSize?: boolean,\n digests?: boolean\n};\n\nexport type ImageBuildPruneQueryOptions = {\n keepStorage: number,\n all: boolean,\n filters?: ImageBuildPruneFilters\n}\n\nexport type ImagePullQueryOptions = {\n fromImage: string,\n tag?: string,\n changes?: Array<string>\n platform?: string\n};\n\nexport type ImageImportQueryOptions = {\n fromSrc: string,\n repo: string,\n message?: string,\n changes?: Array<string>,\n platform?: string\n};\n\nexport type ImageSearchQueryOptions = {\n term: string,\n limit: number,\n filters?: ImageSearchFilters\n};\n\nexport type ImageCommitQueryOptions = {\n container?: string,\n repo: string,\n tag?: string,\n comment?: string,\n author?: string,\n pause?: boolean,\n changes?: string\n};\n\nexport type ImageListResult = Array<ImageSummary>;\n\nexport type ImageBuildPruneResult = {\n CachesDeleted: Array<string>,\n SpaceReclaimed: number\n};\n\nexport type ImagePruneResultImageDeleted = {\n Untagged: string,\n Deleted: string\n};\n\nexport type ImageSearchResult = Array<{\n description: string,\n is_official: boolean,\n name: string,\n star_count: number\n}>;\n\nexport type ImagePruneResult = {\n ImagesDeleted: Array<ImagePruneResultImageDeleted>,\n SpaceReclaimed: number\n};\n\nexport type ImageCommitResult = {\n Id: string\n};\n\nexport default class Images {\n static async list(modem: Modem, queryOptions: ImageListQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.all) query.set(\"all\", queryOptions.all ? \"true\" : \"false\");\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n if (queryOptions.sharedSize) query.set(\"shared-size\", queryOptions.sharedSize ? \"true\" : \"false\");\n if (queryOptions.digests) query.set(\"digests\", queryOptions.digests ? \"true\" : \"false\");\n\n const call: ModemDialOptions = {\n path: \"/images/json\",\n method: \"GET\",\n query: query\n };\n\n return await modem.dial(call) as ImageListResult;\n }\n\n // TODO build image\n\n static async pruneBuilderCache(modem: Modem, queryOptions: ImageBuildPruneQueryOptions) {\n const query = new Map();\n query.set(\"keep-storage\", queryOptions.keepStorage);\n query.set(\"all\", queryOptions.all ? \"true\" : \"false\");\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/build/prune\",\n method: \"POST\",\n query: query\n };\n\n return await modem.dial(call) as ImageBuildPruneResult;\n }\n\n /**\n * @see https://docs.docker.com/reference/api/engine/version/v1.44/#tag/Image/operation/ImageCreate\n * \n * @param modem \n * @param queryOptions \n * @param registryAuth \n * @returns \n */\n static async pull(modem: Modem, queryOptions: ImagePullQueryOptions, registryAuth?: string) {\n const headers = new Map();\n if (registryAuth) headers.set(\"X-Registry-Auth\", registryAuth);\n\n const query = new Map();\n query.set(\"fromImage\", queryOptions.fromImage);\n\n if (queryOptions.tag) query.set(\"tag\", queryOptions.tag);\n if (queryOptions.changes) query.set(\"changes\", queryOptions.changes);\n if (queryOptions.platform) query.set(\"platform\", queryOptions.platform);\n\n const call: ModemDialOptions = {\n path: \"/images/create\",\n method: \"POST\",\n headers: headers,\n query: query,\n isStream: true\n };\n\n const stream = await modem.dial(call) as ModemDialStreamResult;\n const handle = new ImageHandle(modem, queryOptions.fromImage);\n const logWrapper = new HandleLogWrapper(handle, stream);\n\n await logWrapper.drainLogStream();\n return logWrapper;\n }\n\n /**\n * @see https://docs.docker.com/reference/api/engine/version/v1.44/#tag/Image/operation/ImageCreate\n * \n * @param modem \n * @param queryOptions \n * @param image \n * @returns \n */\n static async import(modem: Modem, queryOptions: ImageImportQueryOptions, image?: string) {\n const query = new Map();\n query.set(\"fromSrc\", queryOptions.fromSrc);\n query.set(\"repo\", queryOptions.repo);\n\n if (queryOptions.message) query.set(\"message\", queryOptions.message);\n if (queryOptions.changes) query.set(\"changes\", queryOptions.changes);\n if (queryOptions.platform) query.set(\"platform\", queryOptions.platform);\n\n const call: ModemDialOptions = {\n path: \"/images/create\",\n method: \"POST\",\n query: query,\n body: image,\n isStream: true\n };\n\n const stream = await modem.dial(call) as ModemDialStreamResult;\n const handle = new ImageHandle(modem, queryOptions.repo);\n const logWrapper = new HandleLogWrapper(handle, stream);\n\n await logWrapper.drainLogStream();\n return logWrapper;\n }\n\n static async search(modem: Modem, queryOptions: ImageSearchQueryOptions) {\n const query = new Map();\n query.set(\"term\", queryOptions.term);\n query.set(\"limit\", queryOptions.limit);\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/images/search\",\n method: \"GET\",\n query: query\n };\n\n return await modem.dial(call) as ImageSearchResult;\n }\n\n static async prune(modem: Modem, filters?: ImagePruneFilters) {\n const query = new Map();\n if (filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(filters)));\n\n const call: ModemDialOptions = {\n path: \"/images/prune\",\n method: \"POST\",\n query: query\n };\n\n return await modem.dial(call) as ImagePruneResult;\n }\n\n /**\n * Creates a new image based on a container configuration\n * @param modem the modem to communicate with docker\n * @param queryOptions query specific options\n * @param containerConfig configuration of the container for the image\n */\n static async commit(modem: Modem, queryOptions: ImageCommitQueryOptions, containerConfig?: ContainerConfig) {\n if (!containerConfig && !queryOptions.container)\n throw new Error(\"Either containerConfig or queryOption 'container' must be specified\");\n\n const query = new Map();\n query.set(\"repo\", queryOptions.repo);\n query.set(\"tag\", queryOptions.tag ?? \"\");\n query.set(\"comment\", queryOptions.comment ?? \"\");\n query.set(\"author\", queryOptions.author ?? \"\");\n query.set(\"pause\", queryOptions.pause ? \"true\" : \"false\");\n\n if (queryOptions.container) query.set(\"container\", queryOptions.container);\n if (queryOptions.changes) query.set(\"changes\", queryOptions.changes);\n\n const call: ModemDialOptions = {\n path: \"/commit\",\n method: \"POST\",\n query: query,\n body: containerConfig\n };\n\n return await modem.dial(call) as ImageCommitResult;\n }\n\n // TODO export image\n // TODO export multiple images\n // TODO import multiple images\n \n static getHandle(modem: Modem, id: ImageId) {\n return new ImageHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport { DOCKER_STACK_NAMESPACE_LABEL } from \"../stack/stackDeps\";\nimport Handle from \"../utils/Handle\";\nimport Stackable from \"../utils/Stackable\";\nimport { DockerOptions, DockerLabels, IPAM, EndpointSpec, EndpointSettings } from \"../utils/types\";\n\nexport type NetworkId = string;\ntype NetworkScope = \"local\" | \"swarm\";\n\nexport type NetworkContainer = {\n Name: string,\n EndpointID: string,\n MacAddress: string,\n IPv4Address: string,\n IPv6Address: string\n};\n\ntype NetworkContainers = {\n [name: string]: NetworkContainer\n};\n\ntype NetworkConfigFrom = {\n Network: NetworkId\n};\n\nexport interface NetworkSpec {\n Name: string,\n Driver?: string,\n EnableIPv6?: boolean,\n IPAM?: IPAM,\n Internal?: boolean,\n Attachable?: boolean,\n Ingress?: boolean,\n Options?: DockerOptions,\n Labels?: DockerLabels,\n Scope?: NetworkScope,\n ConfigOnly?: boolean,\n ConfigFrom?: NetworkConfigFrom\n}\n\nexport interface Network extends NetworkSpec {\n Id: NetworkId,\n Created: string,\n Containers: NetworkContainers,\n}\n\nexport type NetworkInspectQueryOptions = {\n verbose?: boolean,\n scope?: NetworkScope\n};\n\nexport type NetworkConnectContainerConfig = {\n Container: string,\n EndpointConfig: EndpointSettings\n};\n\nexport type NetworkDisconnectContainerConfig = {\n Container: string,\n Force: boolean\n};\n\nexport default class NetworkHandle extends Handle implements Stackable {\n /**\n * @see https://docs.docker.com/engine/api/v1.44/#tag/Network/operation/NetworkInspect\n * @param queryOptions query options to use \n */\n public async inspect(queryOptions: NetworkInspectQueryOptions = {}) {\n const query = new Map();\n query.set(\"verbose\", queryOptions.verbose ? \"true\" : \"false\");\n if (queryOptions.scope) query.set(\"scope\", queryOptions.scope);\n\n const call: ModemDialOptions = {\n path: \"/networks/\" + this.id,\n method: \"GET\",\n query: query\n };\n\n return await this.modem.dial(call) as Network;\n }\n\n public async remove() {\n const current = await this.inspect();\n\n if (this.shouldThrowStackError() && NetworkHandle.isPartOfStack(current))\n throw new Error(\"The current network was created by a stack, it can not be removed individually\");\n\n const call: ModemDialOptions = {\n path: \"/networks/\" + this.id,\n method: \"DELETE\"\n };\n\n await this.modem.dial(call);\n }\n\n public async connectContainer(connectConfig: NetworkConnectContainerConfig) {\n const call: ModemDialOptions = {\n path: `/networks/${this.id}/connect`,\n method: \"POST\",\n body: connectConfig\n };\n\n return await this.modem.dial(call);\n }\n\n public async disconnectContainer(disconnectConfig: NetworkDisconnectContainerConfig) {\n const call: ModemDialOptions = {\n path: `/networks/${this.id}/disconnect`,\n method: \"POST\",\n body: disconnectConfig\n };\n\n return await this.modem.dial(call);\n }\n\n public shouldThrowStackError() {\n return true;\n }\n\n public static isPartOfStack(network: Network) {\n return network.Labels && DOCKER_STACK_NAMESPACE_LABEL in network.Labels;\n }\n\n public static fromNetwork(modem: Modem, network: Network) {\n const id = network.Id;\n return new NetworkHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport NetworkHandle, { Network, NetworkId, NetworkSpec } from \"./network\";\n\ntype NetworkListFilterType = \"dangling\" | \"driver\" | \"id\" | \"label\" | \"name\" | \"scope\" | \"type\";\ntype NetworkPruneFilterType = \"until\" | \"label\";\n\nexport type NetworkListFilters = Map<NetworkListFilterType, Array<string>>;\nexport type NetworkPruneFilters = Map<NetworkPruneFilterType, Array<string>>;\n\nexport type NetworkListQueryOptions = {\n filters?: NetworkListFilters\n};\n\nexport type NetworkPruneQueryOptions = {\n filters?: NetworkPruneFilters\n};\n\nexport type NetworkListResult = Array<Network>;\ntype NetworkCreateResult = {\n Id: NetworkId,\n Warning: string\n};\ntype NetworkPruneResult = {\n NetworksDeleted: Array<string>\n};\n\nexport default class Networks {\n /**\n * @see https://docs.docker.com/engine/api/v1.44/#tag/Network/operation/NetworkList\n */\n public static async list(modem: Modem, queryOptions: NetworkListQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/networks\",\n method: \"GET\",\n query: query\n };\n\n return await modem.dial(call) as NetworkListResult;\n }\n\n /**\n * @see https://docs.docker.com/engine/api/v1.44/#tag/Network/operation/NetworkCreate\n * \n * @param modem the modem to use\n * @param spec the spec to use to create the network\n */\n public static async create(modem: Modem, spec: NetworkSpec) {\n const call: ModemDialOptions = {\n path: \"/networks/create\",\n method: \"POST\",\n body: spec\n };\n\n const result = await modem.dial(call) as NetworkCreateResult;\n return new NetworkHandle(modem, result.Id);\n }\n\n public static async existsCluster(modem: Modem, name: string) {\n const filters = new Map();\n filters.set(\"name\", Array.of(name));\n filters.set(\"scope\", Array.of(\"swarm\"));\n\n const list = await Networks.list(modem, { filters });\n return { exists: list.length > 0, handle: list.length > 0 ? NetworkHandle.fromNetwork(modem, list[0]) : undefined };\n }\n\n public static async prune(modem: Modem, queryOptions: NetworkPruneQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/networks/prune\",\n method: \"POST\",\n query: query\n };\n\n return await modem.dial(call) as NetworkPruneResult;\n }\n\n public static getHandle(modem: Modem, id: NetworkId) {\n return new NetworkHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport Handle from \"../utils/Handle\";\nimport { DockerLabels, EngineDescription, Platform, ResourceObject, SwarmBase, TLSInfo } from \"../utils/types\";\n\nexport type NodeId = string;\n\ntype NodeRole = \"worker\" | \"manager\";\ntype NodeAvailability = \"active\" | \"pause\" | \"drain\";\n\nexport type NodeSpec = {\n Name: string,\n Labels?: DockerLabels,\n Role: NodeRole,\n Availability: NodeAvailability\n};\n\ntype NodeDescription = {\n Hostname: string,\n Platform: Platform,\n Resources: ResourceObject,\n Engine: EngineDescription,\n TLSInfo: TLSInfo\n};\n\ntype NodeState = \"unknown\" | \"down\" | \"ready\" | \"disconnected\";\ntype NodeStatus = {\n State: NodeState,\n Message: string,\n Addr: string\n};\n\ntype NodeManagerStatusReachability = \"unknown\" | \"unreachable\" | \"reachable\";\ntype NodeManagerStatus = {\n Leader: boolean,\n Reachability: NodeManagerStatusReachability,\n Addr: string\n};\n\nexport interface Node extends SwarmBase<NodeId> {\n Spec: NodeSpec,\n Description: NodeDescription,\n Status: NodeStatus,\n ManagerStatus: NodeManagerStatus\n};\n\nexport type NodeRemoveQueryOptions = {\n force?: boolean\n};\n\nexport default class NodeHandle extends Handle {\n async inspect() {\n const call: ModemDialOptions = {\n path: \"/nodes/\" + this.id,\n method: \"GET\"\n };\n\n return await this.modem.dial(call) as Node;\n }\n\n async update(newSpec: NodeSpec) {\n const current = await this.inspect();\n const version = current.Version.Index;\n\n const query = new Map();\n query.set(\"version\", version);\n\n const call: ModemDialOptions = {\n path: \"/nodes/\" + this.id + \"/update\",\n method: \"POST\",\n query: query,\n body: newSpec\n };\n\n await this.modem.dial(call);\n }\n\n async remove(queryOptions: NodeRemoveQueryOptions) {\n const query = new Map();\n query.set(\"force\", queryOptions.force ? \"true\" : \"false\");\n\n const call: ModemDialOptions = {\n path: \"/nodes/\" + this.id,\n method: \"DELETE\",\n query: query\n };\n\n await this.modem.dial(call);\n }\n\n static fromNode(modem: Modem, node: Node) {\n const id = node.ID;\n return new NodeHandle(modem, id);\n }\n}","import Modem, { ModemDialOptions } from \"../api/Modem\";\nimport NodeHandle, { Node, NodeId } from \"./node\";\n\ntype NodeListFilterType = \"id\" | \"label\" | \"membership\" | \"name\" | \"node.label\" | \"role\";\n\nexport type NodeListFilters = Map<NodeListFilterType, Array<string>>;\nexport type NodeListResult = Array<Node>;\n\nexport type NodeListQueryOptions = {\n filters?: NodeListFilters\n};\n\nexport default class Nodes {\n static async list(modem: Modem, queryOptions: NodeListQueryOptions = {}) {\n const query = new Map();\n if (queryOptions.filters) query.set(\"filters\", JSON.stringify(Object.fromEntries(queryOptions.filters)));\n\n const call: ModemDialOptions = {\n path: \"/nodes\",\n method: \"GET\",\n query: query\n