@alchemy.run/docker 0.0.0-stage → 2.0.0-beta.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +50 -2
- package/lib/Bootstrap.d.ts +7 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +25 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/BuildHash.d.ts +47 -0
- package/lib/BuildHash.d.ts.map +1 -0
- package/lib/BuildHash.js +165 -0
- package/lib/BuildHash.js.map +1 -0
- package/lib/Container.d.ts +352 -0
- package/lib/Container.d.ts.map +1 -0
- package/lib/Container.js +531 -0
- package/lib/Container.js.map +1 -0
- package/lib/Context.d.ts +86 -0
- package/lib/Context.d.ts.map +1 -0
- package/lib/Context.js +146 -0
- package/lib/Context.js.map +1 -0
- package/lib/Docker.d.ts +448 -0
- package/lib/Docker.d.ts.map +1 -0
- package/lib/Docker.js +416 -0
- package/lib/Docker.js.map +1 -0
- package/lib/Dockerfile.d.ts +72 -0
- package/lib/Dockerfile.d.ts.map +1 -0
- package/lib/Dockerfile.js +12 -0
- package/lib/Dockerfile.js.map +1 -0
- package/lib/HealthcheckCommand.d.ts +17 -0
- package/lib/HealthcheckCommand.d.ts.map +1 -0
- package/lib/HealthcheckCommand.js +33 -0
- package/lib/HealthcheckCommand.js.map +1 -0
- package/lib/Image.d.ts +151 -0
- package/lib/Image.d.ts.map +1 -0
- package/lib/Image.js +192 -0
- package/lib/Image.js.map +1 -0
- package/lib/ImageSource.d.ts +234 -0
- package/lib/ImageSource.d.ts.map +1 -0
- package/lib/ImageSource.js +469 -0
- package/lib/ImageSource.js.map +1 -0
- package/lib/Network.d.ts +70 -0
- package/lib/Network.d.ts.map +1 -0
- package/lib/Network.js +111 -0
- package/lib/Network.js.map +1 -0
- package/lib/Providers.d.ts +21 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +26 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Registry.d.ts +47 -0
- package/lib/Registry.d.ts.map +1 -0
- package/lib/Registry.js +67 -0
- package/lib/Registry.js.map +1 -0
- package/lib/RegistryError.d.ts +21 -0
- package/lib/RegistryError.d.ts.map +1 -0
- package/lib/RegistryError.js +28 -0
- package/lib/RegistryError.js.map +1 -0
- package/lib/RemoteImage.d.ts +118 -0
- package/lib/RemoteImage.d.ts.map +1 -0
- package/lib/RemoteImage.js +164 -0
- package/lib/RemoteImage.js.map +1 -0
- package/lib/Service.d.ts +478 -0
- package/lib/Service.d.ts.map +1 -0
- package/lib/Service.js +588 -0
- package/lib/Service.js.map +1 -0
- package/lib/ServiceImage.d.ts +78 -0
- package/lib/ServiceImage.d.ts.map +1 -0
- package/lib/ServiceImage.js +162 -0
- package/lib/ServiceImage.js.map +1 -0
- package/lib/Swarm.d.ts +119 -0
- package/lib/Swarm.d.ts.map +1 -0
- package/lib/Swarm.js +148 -0
- package/lib/Swarm.js.map +1 -0
- package/lib/Volume.d.ts +94 -0
- package/lib/Volume.d.ts.map +1 -0
- package/lib/Volume.js +133 -0
- package/lib/Volume.js.map +1 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -0
- package/package.json +72 -3
- package/src/Bootstrap.ts +51 -0
- package/src/BuildHash.ts +255 -0
- package/src/Container.ts +792 -0
- package/src/Context.ts +215 -0
- package/src/Docker.ts +1079 -0
- package/src/Dockerfile.ts +81 -0
- package/src/HealthcheckCommand.ts +35 -0
- package/src/Image.ts +293 -0
- package/src/ImageSource.ts +769 -0
- package/src/Network.ts +169 -0
- package/src/Providers.ts +46 -0
- package/src/Registry.ts +91 -0
- package/src/RegistryError.ts +48 -0
- package/src/RemoteImage.ts +255 -0
- package/src/Service.ts +1080 -0
- package/src/ServiceImage.ts +252 -0
- package/src/Swarm.ts +221 -0
- package/src/Volume.ts +193 -0
- package/src/index.ts +17 -0
package/lib/Network.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
2
|
+
import { isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import { createInternalTags, hasAlchemyTags, stripInternalTags } from "alchemy/Tags";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Equal from "effect/Equal";
|
|
8
|
+
import { Docker, dockerEngineContextName, dockerPhysicalName } from "./Docker.js";
|
|
9
|
+
/**
|
|
10
|
+
* A Docker network managed through the active Docker context.
|
|
11
|
+
*
|
|
12
|
+
* Existing same-name networks are treated as foreign unless the engine is
|
|
13
|
+
* explicitly allowed to adopt them with `--adopt` or `adopt(true)`.
|
|
14
|
+
*
|
|
15
|
+
*
|
|
16
|
+
* ### Creating Networks
|
|
17
|
+
* **Example:** Basic bridge network
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const network = yield* Docker.Network("app-network", {
|
|
20
|
+
* name: "app-network",
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ### Adoption
|
|
25
|
+
* **Example:** Adopt a pre-existing network
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const network = yield* Docker.Network("app-network", {
|
|
28
|
+
* name: "shared-app-network",
|
|
29
|
+
* }).pipe(adopt(true));
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @resource
|
|
33
|
+
* @product Network
|
|
34
|
+
*/
|
|
35
|
+
export const Network = Resource("Docker.Network");
|
|
36
|
+
export const NetworkProvider = () => Provider.effect(Network, Effect.gen(function* () {
|
|
37
|
+
const docker = yield* Docker;
|
|
38
|
+
return Network.Provider.of({
|
|
39
|
+
list: () => Effect.succeed([]),
|
|
40
|
+
read: Effect.fn(function* ({ id, instanceId, olds, output }) {
|
|
41
|
+
const context = dockerEngineContextName(olds?.context);
|
|
42
|
+
const name = yield* dockerPhysicalName(id, olds, instanceId);
|
|
43
|
+
const info = yield* docker.network
|
|
44
|
+
.inspect(name, context)
|
|
45
|
+
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined));
|
|
46
|
+
if (!info)
|
|
47
|
+
return undefined;
|
|
48
|
+
const attrs = toNetworkAttributes(info);
|
|
49
|
+
if (output)
|
|
50
|
+
return attrs;
|
|
51
|
+
// Without prior state, only adopt a network that carries our branding;
|
|
52
|
+
// anything else is foreign and gated behind `--adopt`.
|
|
53
|
+
const owned = yield* hasAlchemyTags(id, info.Labels ?? undefined);
|
|
54
|
+
return owned ? attrs : Unowned(attrs);
|
|
55
|
+
}),
|
|
56
|
+
diff: Effect.fn(function* ({ id, output, instanceId, news, olds }) {
|
|
57
|
+
if (!isResolved(news) || !output)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (dockerEngineContextName(olds?.context) !== dockerEngineContextName(news?.context)) {
|
|
60
|
+
return { action: "replace", deleteFirst: true };
|
|
61
|
+
}
|
|
62
|
+
const args = yield* makeNetworkArgs(id, news, instanceId);
|
|
63
|
+
// Auto-generated names are engine-owned: the deployed name stays
|
|
64
|
+
// authoritative even if the generator would name this id differently
|
|
65
|
+
// today. Only an explicit user-provided name can force a replace.
|
|
66
|
+
const desiredName = news?.name ?? output.name;
|
|
67
|
+
if (output.name !== desiredName ||
|
|
68
|
+
output.driver !== args.driver ||
|
|
69
|
+
output.enableIPv6 !== args.ipv6 ||
|
|
70
|
+
// Compare only user labels; internal `alchemy::*` branding lives on
|
|
71
|
+
// the observed network but must not drive replacement.
|
|
72
|
+
!Equal.equals(stripInternalTags(output.labels), args.label ?? {})) {
|
|
73
|
+
return { action: "replace", deleteFirst: true };
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
reconcile: Effect.fn(function* ({ output, id, instanceId, news }) {
|
|
77
|
+
const context = dockerEngineContextName(news?.context);
|
|
78
|
+
if (output) {
|
|
79
|
+
const refreshed = yield* docker.network.inspect(output.id, context).pipe(Effect.map(toNetworkAttributes), Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined));
|
|
80
|
+
if (refreshed)
|
|
81
|
+
return refreshed;
|
|
82
|
+
}
|
|
83
|
+
const args = yield* makeNetworkArgs(id, news, instanceId);
|
|
84
|
+
const internalTags = yield* createInternalTags(id);
|
|
85
|
+
const { stdout: createdId } = yield* docker.network.create({
|
|
86
|
+
...args,
|
|
87
|
+
label: { ...internalTags, ...args.label },
|
|
88
|
+
context,
|
|
89
|
+
});
|
|
90
|
+
return toNetworkAttributes(yield* docker.network.inspect(createdId, context));
|
|
91
|
+
}),
|
|
92
|
+
delete: Effect.fn(({ olds, output }) => docker.network
|
|
93
|
+
.remove(output.id, dockerEngineContextName(olds?.context))
|
|
94
|
+
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.void))),
|
|
95
|
+
});
|
|
96
|
+
}));
|
|
97
|
+
const makeNetworkArgs = (id, props, instanceId) => dockerPhysicalName(id, props, instanceId).pipe(Effect.map((name) => ({
|
|
98
|
+
name,
|
|
99
|
+
driver: props?.driver ?? "bridge",
|
|
100
|
+
ipv6: props?.enableIPv6 ?? false,
|
|
101
|
+
label: props?.labels ?? {},
|
|
102
|
+
})));
|
|
103
|
+
export const toNetworkAttributes = (info) => ({
|
|
104
|
+
id: info.Id,
|
|
105
|
+
name: info.Name,
|
|
106
|
+
driver: info.Driver,
|
|
107
|
+
enableIPv6: info.EnableIPv6,
|
|
108
|
+
labels: info.Labels ?? {},
|
|
109
|
+
createdAt: Date.parse(info.Created) || Date.now(),
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=Network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Network.js","sourceRoot":"","sources":["../src/Network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA8ClF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAU,gBAAgB,CAAC,CAAC;AAE3D,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,EAAE,CAClC,QAAQ,CAAC,MAAM,CACb,OAAO,EACP,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC;IAE7B,OAAO,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE;YACzD,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;YAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO;iBAC/B,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;iBACtB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;YACjF,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YAC5B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,MAAM;gBAAE,OAAO,KAAK,CAAC;YACzB,uEAAuE;YACvE,uDAAuD;YACvD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;YAClE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC,CAAC;QACF,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE;YAC/D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YACnD,IAAI,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;gBACtF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAClD,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;YAC1D,iEAAiE;YACjE,qEAAqE;YACrE,kEAAkE;YAClE,MAAM,WAAW,GAAG,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;YAC9C,IACE,MAAM,CAAC,IAAI,KAAK,WAAW;gBAC3B,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;gBAC7B,MAAM,CAAC,UAAU,KAAK,IAAI,CAAC,IAAI;gBAC/B,oEAAoE;gBACpE,uDAAuD;gBACvD,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EACjE,CAAC;gBACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAClD,CAAC;QACH,CAAC,CAAC;QACF,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YAC9D,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvD,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,CACtE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAC/B,MAAM,CAAC,WAAW,CAAC,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CACxE,CAAC;gBACF,IAAI,SAAS;oBAAE,OAAO,SAAS,CAAC;YAClC,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;YAC1D,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;gBACzD,GAAG,IAAI;gBACP,KAAK,EAAE,EAAE,GAAG,YAAY,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE;gBACzC,OAAO;aACR,CAAC,CAAC;YACH,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QAChF,CAAC,CAAC;QACF,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CACrC,MAAM,CAAC,OAAO;aACX,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;aACzD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAC5E;KACF,CAAC,CAAC;AACL,CAAC,CAAC,CACH,CAAC;AAEJ,MAAM,eAAe,GAAG,CAAC,EAAU,EAAE,KAA+B,EAAE,UAAkB,EAAE,EAAE,CAC1F,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,IAAI,CAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAyD,EAAE,CAAC,CAAC;IAC3E,IAAI;IACJ,MAAM,EAAE,KAAK,EAAE,MAAM,IAAI,QAAQ;IACjC,IAAI,EAAE,KAAK,EAAE,UAAU,IAAI,KAAK;IAChC,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,EAAE;CAC3B,CAAC,CAAC,CACJ,CAAC;AAEJ,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAoB,EAAyB,EAAE,CAAC,CAAC;IACnF,EAAE,EAAE,IAAI,CAAC,EAAE;IACX,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,MAAM,EAAE,IAAI,CAAC,MAAM;IACnB,UAAU,EAAE,IAAI,CAAC,UAAU;IAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;IACzB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;CAClD,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as Provider from "alchemy/Provider";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import { type DockerOptions } from "./Docker.ts";
|
|
4
|
+
declare const Providers_base: Provider.ProviderCollection<Providers, "Docker">;
|
|
5
|
+
export declare class Providers extends Providers_base {
|
|
6
|
+
}
|
|
7
|
+
export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
|
|
8
|
+
/**
|
|
9
|
+
* Registers all Docker resource providers.
|
|
10
|
+
*
|
|
11
|
+
* Docker providers use the active Docker CLI context and are intentionally
|
|
12
|
+
* separate from `Cloudflare.Container`. Pass `bin` to run a Docker-compatible
|
|
13
|
+
* CLI such as Podman; the `DOCKER_BIN` environment variable overrides it.
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* providers: Docker.providers({ bin: "podman" }),
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare const providers: (options?: DockerOptions) => Layer.Layer<import("./Docker.ts").Docker | Providers, never, import("alchemy/AlchemyContext").AlchemyContext | import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("alchemy/Stack").Stack | import("alchemy/Stage").Stage>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=Providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Providers.d.ts","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,aAAa,CAAC;;AAQ7D,qBAAa,SAAU,SAAQ,cAAkD;CAAG;AAEpF,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;AAEhF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,aAAa,aAAa,2TAkB7C,CAAC"}
|
package/lib/Providers.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as Provider from "alchemy/Provider";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import { Container, ContainerProvider } from "./Container.js";
|
|
4
|
+
import { Context, ContextProvider } from "./Context.js";
|
|
5
|
+
import { dockerLive } from "./Docker.js";
|
|
6
|
+
import { Image, ImageProvider } from "./Image.js";
|
|
7
|
+
import { Network, NetworkProvider } from "./Network.js";
|
|
8
|
+
import { RemoteImage, RemoteImageProvider } from "./RemoteImage.js";
|
|
9
|
+
import { Service, ServiceProvider } from "./Service.js";
|
|
10
|
+
import { Swarm, SwarmProvider } from "./Swarm.js";
|
|
11
|
+
import { Volume, VolumeProvider } from "./Volume.js";
|
|
12
|
+
export class Providers extends Provider.ProviderCollection()("Docker") {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Registers all Docker resource providers.
|
|
16
|
+
*
|
|
17
|
+
* Docker providers use the active Docker CLI context and are intentionally
|
|
18
|
+
* separate from `Cloudflare.Container`. Pass `bin` to run a Docker-compatible
|
|
19
|
+
* CLI such as Podman; the `DOCKER_BIN` environment variable overrides it.
|
|
20
|
+
*
|
|
21
|
+
* ```typescript
|
|
22
|
+
* providers: Docker.providers({ bin: "podman" }),
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const providers = (options = {}) => Layer.effect(Providers, Provider.collection([Container, Image, Network, RemoteImage, Volume, Context, Service, Swarm])).pipe(Layer.provide(Layer.mergeAll(ContainerProvider(), ImageProvider(), NetworkProvider(), RemoteImageProvider(), VolumeProvider(), ContextProvider(), ServiceProvider(), SwarmProvider())), Layer.provideMerge(dockerLive(options)));
|
|
26
|
+
//# sourceMappingURL=Providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Providers.js","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAsB,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,OAAO,SAAU,SAAQ,QAAQ,CAAC,kBAAkB,EAAa,CAAC,QAAQ,CAAC;CAAG;AAIpF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAO,GAAkB,EAAE,EAAE,EAAE,CACvD,KAAK,CAAC,MAAM,CACV,SAAS,EACT,QAAQ,CAAC,UAAU,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAC/F,CAAC,IAAI,CACJ,KAAK,CAAC,OAAO,CACX,KAAK,CAAC,QAAQ,CACZ,iBAAiB,EAAE,EACnB,aAAa,EAAE,EACf,eAAe,EAAE,EACjB,mBAAmB,EAAE,EACrB,cAAc,EAAE,EAChB,eAAe,EAAE,EACjB,eAAe,EAAE,EACjB,aAAa,EAAE,CAChB,CACF,EACD,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CACxC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type * as Redacted from "effect/Redacted";
|
|
2
|
+
export interface ImageRegistry {
|
|
3
|
+
/** Registry host, e.g. `ghcr.io`. */
|
|
4
|
+
server: string;
|
|
5
|
+
/** Registry username. */
|
|
6
|
+
username: string;
|
|
7
|
+
/** Registry password. Use `Redacted.make(...)` or `Config.Redacted(...)`. */
|
|
8
|
+
password: Redacted.Redacted<string>;
|
|
9
|
+
}
|
|
10
|
+
/** Strips the tag and digest from an image reference, leaving the repository. */
|
|
11
|
+
export declare const repositoryFromImageRef: (imageRef: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Prefixes an image reference with the registry host unless the reference
|
|
14
|
+
* already carries a registry prefix (a dotted host, a host:port, or `localhost`).
|
|
15
|
+
*/
|
|
16
|
+
export declare const withRegistryHost: (imageRef: string, registry: {
|
|
17
|
+
server: string;
|
|
18
|
+
}) => string;
|
|
19
|
+
/** Extracts the `repository@sha256:...` digest from `docker push` output. */
|
|
20
|
+
export declare const parseRepoDigest: (imageRef: string, output: string) => string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Picks the `repository@digest` that inspect reports for this image.
|
|
23
|
+
*
|
|
24
|
+
* A fallback when push output has no `digest:` line. Podman pushes report the
|
|
25
|
+
* published digest through `--digestfile` instead, because Podman can list a
|
|
26
|
+
* pulled image's source digest under the pushed name here.
|
|
27
|
+
*/
|
|
28
|
+
export declare const repoDigestFromInspect: (imageRef: string, repoDigests: ReadonlyArray<string> | null | undefined) => string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Digest from push output when the engine prints one, otherwise the matching
|
|
31
|
+
* `RepoDigests` entry. `registryRef` is the host-qualified name when it
|
|
32
|
+
* differs from the local reference that was pushed.
|
|
33
|
+
*/
|
|
34
|
+
export declare const publishedRepoDigest: (imageRef: string, output: {
|
|
35
|
+
stdout: string;
|
|
36
|
+
stderr: string;
|
|
37
|
+
}, repoDigests: ReadonlyArray<string> | null | undefined, registryRef?: string) => string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Parses an image's RFC 3339 `Created` timestamp into epoch milliseconds.
|
|
40
|
+
*
|
|
41
|
+
* Docker only reports `Created` when the image config carries a creation time:
|
|
42
|
+
* API >= 1.44 omits it, older APIs backfill the year-1 zero value
|
|
43
|
+
* (`0001-01-01T00:00:00Z`), and 25.0.0–25.0.3 returned an empty string. In any
|
|
44
|
+
* of those cases there is no real build time, so we fall back to the wall clock.
|
|
45
|
+
*/
|
|
46
|
+
export declare const parseCreatedAt: (created: string | undefined) => number;
|
|
47
|
+
//# sourceMappingURL=Registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Registry.d.ts","sourceRoot":"","sources":["../src/Registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CACrC;AAED,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,aAAc,MAAM,KAAG,MAOzD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,aAAc,MAAM,YAAY;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,KAAG,MAOjF,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,eAAe,aAAc,MAAM,UAAU,MAAM,KAAG,MAAM,GAAG,SAI3E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,aACtB,MAAM,eACH,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,KACpD,MAAM,GAAG,SAOX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,aACpB,MAAM,UACR;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,eAC7B,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,gBACvC,MAAM,KACnB,MAAM,GAAG,SAOX,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,YAAa,MAAM,GAAG,SAAS,KAAG,MAG5D,CAAC"}
|
package/lib/Registry.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Strips the tag and digest from an image reference, leaving the repository. */
|
|
2
|
+
export const repositoryFromImageRef = (imageRef) => {
|
|
3
|
+
const withoutDigest = imageRef.includes("@")
|
|
4
|
+
? imageRef.slice(0, imageRef.indexOf("@"))
|
|
5
|
+
: imageRef;
|
|
6
|
+
const tagSeparator = withoutDigest.lastIndexOf(":");
|
|
7
|
+
const pathSeparator = withoutDigest.lastIndexOf("/");
|
|
8
|
+
return tagSeparator > pathSeparator ? withoutDigest.slice(0, tagSeparator) : withoutDigest;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Prefixes an image reference with the registry host unless the reference
|
|
12
|
+
* already carries a registry prefix (a dotted host, a host:port, or `localhost`).
|
|
13
|
+
*/
|
|
14
|
+
export const withRegistryHost = (imageRef, registry) => {
|
|
15
|
+
const registryHost = registry.server.replace(/\/$/, "");
|
|
16
|
+
const firstSegment = imageRef.split("/")[0];
|
|
17
|
+
const hasRegistryPrefix = imageRef.includes("/") &&
|
|
18
|
+
(firstSegment.includes(".") || firstSegment.includes(":") || firstSegment === "localhost");
|
|
19
|
+
return hasRegistryPrefix ? imageRef : `${registryHost}/${imageRef}`;
|
|
20
|
+
};
|
|
21
|
+
/** Extracts the `repository@sha256:...` digest from `docker push` output. */
|
|
22
|
+
export const parseRepoDigest = (imageRef, output) => {
|
|
23
|
+
const match = /digest:\s+([a-z0-9]+:[a-f0-9]{64})/i.exec(output);
|
|
24
|
+
if (!match)
|
|
25
|
+
return undefined;
|
|
26
|
+
return `${repositoryFromImageRef(imageRef)}@${match[1]}`;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Picks the `repository@digest` that inspect reports for this image.
|
|
30
|
+
*
|
|
31
|
+
* A fallback when push output has no `digest:` line. Podman pushes report the
|
|
32
|
+
* published digest through `--digestfile` instead, because Podman can list a
|
|
33
|
+
* pulled image's source digest under the pushed name here.
|
|
34
|
+
*/
|
|
35
|
+
export const repoDigestFromInspect = (imageRef, repoDigests) => {
|
|
36
|
+
const repository = repositoryFromImageRef(imageRef);
|
|
37
|
+
for (const digest of repoDigests ?? []) {
|
|
38
|
+
const at = digest.lastIndexOf("@");
|
|
39
|
+
if (at > 0 && digest.slice(0, at) === repository)
|
|
40
|
+
return digest;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Digest from push output when the engine prints one, otherwise the matching
|
|
46
|
+
* `RepoDigests` entry. `registryRef` is the host-qualified name when it
|
|
47
|
+
* differs from the local reference that was pushed.
|
|
48
|
+
*/
|
|
49
|
+
export const publishedRepoDigest = (imageRef, output, repoDigests, registryRef) => {
|
|
50
|
+
const text = `${output.stdout}\n${output.stderr}`;
|
|
51
|
+
return (parseRepoDigest(imageRef, text) ??
|
|
52
|
+
(registryRef === undefined ? undefined : repoDigestFromInspect(registryRef, repoDigests)) ??
|
|
53
|
+
repoDigestFromInspect(imageRef, repoDigests));
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Parses an image's RFC 3339 `Created` timestamp into epoch milliseconds.
|
|
57
|
+
*
|
|
58
|
+
* Docker only reports `Created` when the image config carries a creation time:
|
|
59
|
+
* API >= 1.44 omits it, older APIs backfill the year-1 zero value
|
|
60
|
+
* (`0001-01-01T00:00:00Z`), and 25.0.0–25.0.3 returned an empty string. In any
|
|
61
|
+
* of those cases there is no real build time, so we fall back to the wall clock.
|
|
62
|
+
*/
|
|
63
|
+
export const parseCreatedAt = (created) => {
|
|
64
|
+
const parsed = created ? Date.parse(created) : Number.NaN;
|
|
65
|
+
return Number.isNaN(parsed) || parsed <= 0 ? Date.now() : parsed;
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=Registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Registry.js","sourceRoot":"","sources":["../src/Registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
2
|
+
declare const DockerRegistryBlobUnknown_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
3
|
+
readonly _tag: "DockerRegistryBlobUnknown";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
export declare class DockerRegistryBlobUnknown extends DockerRegistryBlobUnknown_base<{
|
|
6
|
+
readonly cause: PlatformError;
|
|
7
|
+
}> {
|
|
8
|
+
get message(): string;
|
|
9
|
+
}
|
|
10
|
+
declare const DockerRegistryUnavailable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
11
|
+
readonly _tag: "DockerRegistryUnavailable";
|
|
12
|
+
} & Readonly<A>;
|
|
13
|
+
export declare class DockerRegistryUnavailable extends DockerRegistryUnavailable_base<{
|
|
14
|
+
readonly cause: PlatformError;
|
|
15
|
+
}> {
|
|
16
|
+
get message(): string;
|
|
17
|
+
}
|
|
18
|
+
export type DockerImagePublicationError = PlatformError | DockerRegistryBlobUnknown | DockerRegistryUnavailable;
|
|
19
|
+
export declare const classifyDockerRegistryError: (error: PlatformError) => DockerImagePublicationError;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=RegistryError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryError.d.ts","sourceRoot":"","sources":["../src/RegistryError.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;;;;AAE1D,qBAAa,yBAA0B,SAAQ,+BAA8C;IAC3F,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;CAC/B,CAAC;IACA,IAAa,OAAO,WAEnB;CACF;;;;AAED,qBAAa,yBAA0B,SAAQ,+BAA8C;IAC3F,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;CAC/B,CAAC;IACA,IAAa,OAAO,WAEnB;CACF;AAED,MAAM,MAAM,2BAA2B,GACnC,aAAa,GACb,yBAAyB,GACzB,yBAAyB,CAAC;AAE9B,eAAO,MAAM,2BAA2B,UAAW,aAAa,KAAG,2BAuBlE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
export class DockerRegistryBlobUnknown extends Data.TaggedError("DockerRegistryBlobUnknown") {
|
|
3
|
+
get message() {
|
|
4
|
+
return this.cause.message;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class DockerRegistryUnavailable extends Data.TaggedError("DockerRegistryUnavailable") {
|
|
8
|
+
get message() {
|
|
9
|
+
return this.cause.message;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const classifyDockerRegistryError = (error) => {
|
|
13
|
+
// Build logs can contain arbitrary Dockerfile output before the final error.
|
|
14
|
+
const message = error.reason.description
|
|
15
|
+
?.split("\n")
|
|
16
|
+
.map((line) => line.trim())
|
|
17
|
+
.filter((line) => line.length > 0 &&
|
|
18
|
+
!line.startsWith("View build details: docker-desktop://dashboard/build/"))
|
|
19
|
+
.at(-1) ?? "";
|
|
20
|
+
if (/(?:^|:\s*)(?:unknown:\s*)?blob unknown to registry$/i.test(message)) {
|
|
21
|
+
return new DockerRegistryBlobUnknown({ cause: error });
|
|
22
|
+
}
|
|
23
|
+
if (/(?:unexpected (?:http )?status(?:[^\n]*:\s*|\s+)5\d\d(?:\s|$)|(?:^|:\s*)(?:500 internal server error|502 bad gateway|503 service unavailable|504 gateway timeout)$)/i.test(message)) {
|
|
24
|
+
return new DockerRegistryUnavailable({ cause: error });
|
|
25
|
+
}
|
|
26
|
+
return error;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=RegistryError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryError.js","sourceRoot":"","sources":["../src/RegistryError.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AAGpC,MAAM,OAAO,yBAA0B,SAAQ,IAAI,CAAC,WAAW,CAAC,2BAA2B,CAEzF;IACA,IAAa,OAAO;QAClB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,yBAA0B,SAAQ,IAAI,CAAC,WAAW,CAAC,2BAA2B,CAEzF;IACA,IAAa,OAAO;QAClB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;IAC5B,CAAC;CACF;AAOD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,KAAoB,EAA+B,EAAE;IAC/F,6EAA6E;IAC7E,MAAM,OAAO,GACX,KAAK,CAAC,MAAM,CAAC,WAAW;QACtB,EAAE,KAAK,CAAC,IAAI,CAAC;SACZ,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,MAAM,GAAG,CAAC;QACf,CAAC,IAAI,CAAC,UAAU,CAAC,uDAAuD,CAAC,CAC5E;SACA,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClB,IAAI,sDAAsD,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACzE,OAAO,IAAI,yBAAyB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,IACE,sKAAsK,CAAC,IAAI,CACzK,OAAO,CACR,EACD,CAAC;QACD,OAAO,IAAI,yBAAyB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as Provider from "alchemy/Provider";
|
|
2
|
+
import { Resource } from "alchemy/Resource";
|
|
3
|
+
import { Docker } from "./Docker.ts";
|
|
4
|
+
import type { Providers } from "./Providers.ts";
|
|
5
|
+
import { type ImageRegistry } from "./Registry.ts";
|
|
6
|
+
export interface RemoteImageProps {
|
|
7
|
+
/** Docker image name to use as the source, without tag. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Docker source image tag. @default "latest" */
|
|
10
|
+
tag?: string;
|
|
11
|
+
/** Pull for this platform when the source is not already local or alwaysPull is enabled. */
|
|
12
|
+
platform?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Always pull the source image, even when that reference exists locally.
|
|
15
|
+
*
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
alwaysPull?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Re-tag the pulled image under this repository/name. When omitted the pulled
|
|
21
|
+
* `name` is kept.
|
|
22
|
+
*/
|
|
23
|
+
targetName?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Tag applied to the re-tagged image.
|
|
26
|
+
*
|
|
27
|
+
* @default The pulled `tag`.
|
|
28
|
+
*/
|
|
29
|
+
targetTag?: string;
|
|
30
|
+
/** Registry credentials. When set, the (re-tagged) image is pushed. */
|
|
31
|
+
registry?: ImageRegistry;
|
|
32
|
+
/**
|
|
33
|
+
* Skip registry push even when `registry` is set.
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
skipPush?: boolean;
|
|
38
|
+
/** Docker context name or context resource. */
|
|
39
|
+
context?: Docker.ContextRef;
|
|
40
|
+
}
|
|
41
|
+
export interface RemoteImage extends Resource<"Docker.RemoteImage", RemoteImageProps, {
|
|
42
|
+
/** Final image reference. Includes the registry host when pushed there. */
|
|
43
|
+
imageRef: string;
|
|
44
|
+
/** Local image id after reuse or pull. */
|
|
45
|
+
imageId: string;
|
|
46
|
+
/** Source image id used for the last reconcile, preserved across refresh. */
|
|
47
|
+
sourceImageId?: string;
|
|
48
|
+
/** Pull timestamp in milliseconds since epoch. */
|
|
49
|
+
createdAt: number;
|
|
50
|
+
/** Final image repository/name. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Final image tag. */
|
|
53
|
+
tag: string;
|
|
54
|
+
/** Registry digest after push when available. */
|
|
55
|
+
repoDigest?: string;
|
|
56
|
+
}, never, Providers> {
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Makes a source Docker image available through the active Docker context,
|
|
60
|
+
* optionally re-tagging it and pushing it to a registry.
|
|
61
|
+
*
|
|
62
|
+
* The image is available to other Docker resources by `imageRef`. Use
|
|
63
|
+
* `alwaysPull: false` reuses the source tag when it exists in the configured
|
|
64
|
+
* Docker daemon, pulling only when it is missing. Set `targetName`/`targetTag`
|
|
65
|
+
* to re-tag the source image, and `registry` to push it (mirroring it from a
|
|
66
|
+
* source registry into your own, for example).
|
|
67
|
+
*
|
|
68
|
+
*
|
|
69
|
+
* ### Pulling Images
|
|
70
|
+
* **Example:** Pull nginx
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const nginx = yield* Docker.RemoteImage("nginx", {
|
|
73
|
+
* name: "nginx",
|
|
74
|
+
* tag: "alpine",
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* **Example:** Reuse an existing daemon tag
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const postgres = yield* Docker.RemoteImage("postgres", {
|
|
81
|
+
* name: "postgres",
|
|
82
|
+
* tag: "18-alpine",
|
|
83
|
+
* alwaysPull: false,
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* ### Re-tagging and Pushing
|
|
88
|
+
* **Example:** Mirror a public image into your registry
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const mirrored = yield* Docker.RemoteImage("nginx-mirror", {
|
|
91
|
+
* name: "nginx",
|
|
92
|
+
* tag: "alpine",
|
|
93
|
+
* targetName: "acme/nginx",
|
|
94
|
+
* targetTag: "alpine",
|
|
95
|
+
* registry: {
|
|
96
|
+
* server: "ghcr.io",
|
|
97
|
+
* username: "octocat",
|
|
98
|
+
* password: Config.Redacted("GITHUB_TOKEN"),
|
|
99
|
+
* },
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* ### Docker Context
|
|
104
|
+
* **Example:** Pull through a named Docker context
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const nginx = yield* Docker.RemoteImage("nginx", {
|
|
107
|
+
* name: "nginx",
|
|
108
|
+
* tag: "alpine",
|
|
109
|
+
* context: "remote-build",
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @resource
|
|
114
|
+
* @product Image
|
|
115
|
+
*/
|
|
116
|
+
export declare const RemoteImage: import("alchemy/Resource").ResourceClass<RemoteImage>;
|
|
117
|
+
export declare const RemoteImageProvider: () => import("effect/Layer").Layer<Provider.Provider<RemoteImage>, never, Docker>;
|
|
118
|
+
//# sourceMappingURL=RemoteImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RemoteImage.d.ts","sourceRoot":"","sources":["../src/RemoteImage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAAE,MAAM,EAAqB,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EACL,KAAK,aAAa,EAKnB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC7B;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,oBAAoB,EACpB,gBAAgB,EAChB;IACE,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,EACD,KAAK,EACL,SAAS,CACV;CAAG;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,WAAW,uDAA8C,CAAC;AAEvE,eAAO,MAAM,mBAAmB,mFAqG7B,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { isResolved } from "alchemy/Diff";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import { Resource } from "alchemy/Resource";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { Docker, dockerContextName } from "./Docker.js";
|
|
6
|
+
import { parseCreatedAt, publishedRepoDigest, repositoryFromImageRef, withRegistryHost, } from "./Registry.js";
|
|
7
|
+
/**
|
|
8
|
+
* Makes a source Docker image available through the active Docker context,
|
|
9
|
+
* optionally re-tagging it and pushing it to a registry.
|
|
10
|
+
*
|
|
11
|
+
* The image is available to other Docker resources by `imageRef`. Use
|
|
12
|
+
* `alwaysPull: false` reuses the source tag when it exists in the configured
|
|
13
|
+
* Docker daemon, pulling only when it is missing. Set `targetName`/`targetTag`
|
|
14
|
+
* to re-tag the source image, and `registry` to push it (mirroring it from a
|
|
15
|
+
* source registry into your own, for example).
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* ### Pulling Images
|
|
19
|
+
* **Example:** Pull nginx
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const nginx = yield* Docker.RemoteImage("nginx", {
|
|
22
|
+
* name: "nginx",
|
|
23
|
+
* tag: "alpine",
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* **Example:** Reuse an existing daemon tag
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const postgres = yield* Docker.RemoteImage("postgres", {
|
|
30
|
+
* name: "postgres",
|
|
31
|
+
* tag: "18-alpine",
|
|
32
|
+
* alwaysPull: false,
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ### Re-tagging and Pushing
|
|
37
|
+
* **Example:** Mirror a public image into your registry
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const mirrored = yield* Docker.RemoteImage("nginx-mirror", {
|
|
40
|
+
* name: "nginx",
|
|
41
|
+
* tag: "alpine",
|
|
42
|
+
* targetName: "acme/nginx",
|
|
43
|
+
* targetTag: "alpine",
|
|
44
|
+
* registry: {
|
|
45
|
+
* server: "ghcr.io",
|
|
46
|
+
* username: "octocat",
|
|
47
|
+
* password: Config.Redacted("GITHUB_TOKEN"),
|
|
48
|
+
* },
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* ### Docker Context
|
|
53
|
+
* **Example:** Pull through a named Docker context
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const nginx = yield* Docker.RemoteImage("nginx", {
|
|
56
|
+
* name: "nginx",
|
|
57
|
+
* tag: "alpine",
|
|
58
|
+
* context: "remote-build",
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @resource
|
|
63
|
+
* @product Image
|
|
64
|
+
*/
|
|
65
|
+
export const RemoteImage = Resource("Docker.RemoteImage");
|
|
66
|
+
export const RemoteImageProvider = () => Provider.effect(RemoteImage, Effect.gen(function* () {
|
|
67
|
+
const docker = yield* Docker;
|
|
68
|
+
const inspectImage = (ref, context) => docker.image
|
|
69
|
+
.inspect(ref, context)
|
|
70
|
+
.pipe(Effect.catchIf(isMissingImageError, () => Effect.succeed(undefined)));
|
|
71
|
+
return RemoteImage.Provider.of({
|
|
72
|
+
list: () => Effect.succeed([]),
|
|
73
|
+
read: Effect.fn(function* ({ olds, output }) {
|
|
74
|
+
const context = dockerContextName(olds.context);
|
|
75
|
+
const ref = output?.imageRef ?? targetImageRef(olds);
|
|
76
|
+
return yield* inspectImage(ref, context).pipe(Effect.map((image) => image
|
|
77
|
+
? {
|
|
78
|
+
imageRef: ref,
|
|
79
|
+
imageId: image.Id,
|
|
80
|
+
createdAt: output?.createdAt ?? parseCreatedAt(image.Created),
|
|
81
|
+
sourceImageId: output?.sourceImageId ?? output?.imageId ?? image.Id,
|
|
82
|
+
name: output?.name ?? repositoryFromImageRef(ref),
|
|
83
|
+
tag: output?.tag ?? targetTag(olds),
|
|
84
|
+
repoDigest: output?.repoDigest,
|
|
85
|
+
}
|
|
86
|
+
: undefined));
|
|
87
|
+
}),
|
|
88
|
+
diff: Effect.fn(function* ({ output, news, olds }) {
|
|
89
|
+
if (!isResolved(news))
|
|
90
|
+
return undefined;
|
|
91
|
+
if (dockerContextName(olds.context) !== dockerContextName(news.context) ||
|
|
92
|
+
!output ||
|
|
93
|
+
news.alwaysPull !== false ||
|
|
94
|
+
output.imageRef !== targetImageRef(news)) {
|
|
95
|
+
return { action: "update" };
|
|
96
|
+
}
|
|
97
|
+
const source = yield* inspectImage(remoteImageRef(news), dockerContextName(news.context));
|
|
98
|
+
const target = yield* inspectImage(targetImageRef(news), dockerContextName(news.context));
|
|
99
|
+
if (!source ||
|
|
100
|
+
!target ||
|
|
101
|
+
source.Id !== (output.sourceImageId ?? output.imageId) ||
|
|
102
|
+
source.Id !== target.Id) {
|
|
103
|
+
return { action: "update" };
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
reconcile: Effect.fn(function* ({ news, session }) {
|
|
107
|
+
const context = dockerContextName(news.context);
|
|
108
|
+
const sourceRef = remoteImageRef(news);
|
|
109
|
+
let source;
|
|
110
|
+
if (news.alwaysPull === false) {
|
|
111
|
+
source = yield* inspectImage(sourceRef, context);
|
|
112
|
+
if (source) {
|
|
113
|
+
yield* session.note(`Reusing local Docker image: ${sourceRef}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!source) {
|
|
117
|
+
yield* session.note(`Pulling Docker image: ${sourceRef}`);
|
|
118
|
+
yield* docker.image.pull(sourceRef, news.platform, context);
|
|
119
|
+
source = yield* docker.image.inspect(sourceRef, context);
|
|
120
|
+
}
|
|
121
|
+
const finalRef = targetImageRef(news);
|
|
122
|
+
if (finalRef !== sourceRef || (news.registry && !news.skipPush)) {
|
|
123
|
+
yield* session.note(`Tagging Docker image: ${source.Id} -> ${finalRef}`);
|
|
124
|
+
yield* docker.image.tag(source.Id, finalRef, context);
|
|
125
|
+
}
|
|
126
|
+
let pushed;
|
|
127
|
+
if (news.registry && !news.skipPush) {
|
|
128
|
+
yield* session.note(`Pushing image to registry "${news.registry.server}"`);
|
|
129
|
+
pushed = yield* docker.image.push(finalRef, news.registry, undefined, context);
|
|
130
|
+
}
|
|
131
|
+
const inspected = yield* docker.image.inspect(finalRef, context);
|
|
132
|
+
const repoDigest = pushed === undefined
|
|
133
|
+
? undefined
|
|
134
|
+
: publishedRepoDigest(finalRef, pushed, inspected.RepoDigests);
|
|
135
|
+
return {
|
|
136
|
+
imageRef: finalRef,
|
|
137
|
+
imageId: inspected.Id,
|
|
138
|
+
sourceImageId: source.Id,
|
|
139
|
+
createdAt: parseCreatedAt(inspected.Created),
|
|
140
|
+
name: repositoryFromImageRef(finalRef),
|
|
141
|
+
tag: targetTag(news),
|
|
142
|
+
repoDigest,
|
|
143
|
+
};
|
|
144
|
+
}),
|
|
145
|
+
delete: Effect.fn(function* () {
|
|
146
|
+
// Remote images are not removed on destroy because tags may be shared by
|
|
147
|
+
// unrelated local stacks or developer workflows.
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
}));
|
|
151
|
+
/** The reference the image is pulled from. */
|
|
152
|
+
const remoteImageRef = (props) => `${props.name}:${props.tag ?? "latest"}`;
|
|
153
|
+
const isMissingImageError = (error) => error.reason._tag === "NotFound" &&
|
|
154
|
+
/no such image|image not known|image not found/i.test(error.reason.description ?? "");
|
|
155
|
+
const targetTag = (props) => props.targetTag ?? props.tag ?? "latest";
|
|
156
|
+
/**
|
|
157
|
+
* The final reference after re-tagging and registry-host prefixing. Equals the
|
|
158
|
+
* pulled reference when no re-tag/registry is configured.
|
|
159
|
+
*/
|
|
160
|
+
const targetImageRef = (props) => {
|
|
161
|
+
const local = `${props.targetName ?? props.name}:${targetTag(props)}`;
|
|
162
|
+
return props.registry && !props.skipPush ? withRegistryHost(local, props.registry) : local;
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=RemoteImage.js.map
|