@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.
Files changed (98) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +50 -2
  3. package/lib/Bootstrap.d.ts +7 -0
  4. package/lib/Bootstrap.d.ts.map +1 -0
  5. package/lib/Bootstrap.js +25 -0
  6. package/lib/Bootstrap.js.map +1 -0
  7. package/lib/BuildHash.d.ts +47 -0
  8. package/lib/BuildHash.d.ts.map +1 -0
  9. package/lib/BuildHash.js +165 -0
  10. package/lib/BuildHash.js.map +1 -0
  11. package/lib/Container.d.ts +352 -0
  12. package/lib/Container.d.ts.map +1 -0
  13. package/lib/Container.js +531 -0
  14. package/lib/Container.js.map +1 -0
  15. package/lib/Context.d.ts +86 -0
  16. package/lib/Context.d.ts.map +1 -0
  17. package/lib/Context.js +146 -0
  18. package/lib/Context.js.map +1 -0
  19. package/lib/Docker.d.ts +448 -0
  20. package/lib/Docker.d.ts.map +1 -0
  21. package/lib/Docker.js +416 -0
  22. package/lib/Docker.js.map +1 -0
  23. package/lib/Dockerfile.d.ts +72 -0
  24. package/lib/Dockerfile.d.ts.map +1 -0
  25. package/lib/Dockerfile.js +12 -0
  26. package/lib/Dockerfile.js.map +1 -0
  27. package/lib/HealthcheckCommand.d.ts +17 -0
  28. package/lib/HealthcheckCommand.d.ts.map +1 -0
  29. package/lib/HealthcheckCommand.js +33 -0
  30. package/lib/HealthcheckCommand.js.map +1 -0
  31. package/lib/Image.d.ts +151 -0
  32. package/lib/Image.d.ts.map +1 -0
  33. package/lib/Image.js +192 -0
  34. package/lib/Image.js.map +1 -0
  35. package/lib/ImageSource.d.ts +234 -0
  36. package/lib/ImageSource.d.ts.map +1 -0
  37. package/lib/ImageSource.js +469 -0
  38. package/lib/ImageSource.js.map +1 -0
  39. package/lib/Network.d.ts +70 -0
  40. package/lib/Network.d.ts.map +1 -0
  41. package/lib/Network.js +111 -0
  42. package/lib/Network.js.map +1 -0
  43. package/lib/Providers.d.ts +21 -0
  44. package/lib/Providers.d.ts.map +1 -0
  45. package/lib/Providers.js +26 -0
  46. package/lib/Providers.js.map +1 -0
  47. package/lib/Registry.d.ts +47 -0
  48. package/lib/Registry.d.ts.map +1 -0
  49. package/lib/Registry.js +67 -0
  50. package/lib/Registry.js.map +1 -0
  51. package/lib/RegistryError.d.ts +21 -0
  52. package/lib/RegistryError.d.ts.map +1 -0
  53. package/lib/RegistryError.js +28 -0
  54. package/lib/RegistryError.js.map +1 -0
  55. package/lib/RemoteImage.d.ts +118 -0
  56. package/lib/RemoteImage.d.ts.map +1 -0
  57. package/lib/RemoteImage.js +164 -0
  58. package/lib/RemoteImage.js.map +1 -0
  59. package/lib/Service.d.ts +478 -0
  60. package/lib/Service.d.ts.map +1 -0
  61. package/lib/Service.js +588 -0
  62. package/lib/Service.js.map +1 -0
  63. package/lib/ServiceImage.d.ts +78 -0
  64. package/lib/ServiceImage.d.ts.map +1 -0
  65. package/lib/ServiceImage.js +162 -0
  66. package/lib/ServiceImage.js.map +1 -0
  67. package/lib/Swarm.d.ts +119 -0
  68. package/lib/Swarm.d.ts.map +1 -0
  69. package/lib/Swarm.js +148 -0
  70. package/lib/Swarm.js.map +1 -0
  71. package/lib/Volume.d.ts +94 -0
  72. package/lib/Volume.d.ts.map +1 -0
  73. package/lib/Volume.js +133 -0
  74. package/lib/Volume.js.map +1 -0
  75. package/lib/index.d.ts +14 -0
  76. package/lib/index.d.ts.map +1 -0
  77. package/lib/index.js +13 -0
  78. package/lib/index.js.map +1 -0
  79. package/package.json +72 -3
  80. package/src/Bootstrap.ts +51 -0
  81. package/src/BuildHash.ts +255 -0
  82. package/src/Container.ts +792 -0
  83. package/src/Context.ts +215 -0
  84. package/src/Docker.ts +1079 -0
  85. package/src/Dockerfile.ts +81 -0
  86. package/src/HealthcheckCommand.ts +35 -0
  87. package/src/Image.ts +293 -0
  88. package/src/ImageSource.ts +769 -0
  89. package/src/Network.ts +169 -0
  90. package/src/Providers.ts +46 -0
  91. package/src/Registry.ts +91 -0
  92. package/src/RegistryError.ts +48 -0
  93. package/src/RemoteImage.ts +255 -0
  94. package/src/Service.ts +1080 -0
  95. package/src/ServiceImage.ts +252 -0
  96. package/src/Swarm.ts +221 -0
  97. package/src/Volume.ts +193 -0
  98. package/src/index.ts +17 -0
package/src/Context.ts ADDED
@@ -0,0 +1,215 @@
1
+ import { deepEqual, 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 * as Equal from "effect/Equal";
6
+ import { Docker, dockerPhysicalName } from "./Docker.ts";
7
+ import type { Providers } from "./Providers.ts";
8
+
9
+ export interface ContextProps {
10
+ /**
11
+ * Context name.
12
+ *
13
+ * @default Generated from stack, stage, logical id, and instance id.
14
+ */
15
+ name?: string;
16
+ /** Human-readable description displayed in `docker context ls`. */
17
+ description?: string;
18
+ /**
19
+ * Docker endpoint spec, for example `host=ssh://user@host`.
20
+ *
21
+ * Connection metadata only — never credentials. SSH endpoints
22
+ * authenticate with keys/agent (Docker rejects passwords in the URL), and
23
+ * TLS endpoints reference cert/key material by file path
24
+ * (`host=tcp://...,ca=...,cert=...,key=...`), not by value. Docker itself
25
+ * stores this spec in plaintext under `~/.docker/contexts`.
26
+ */
27
+ docker?: string;
28
+ }
29
+
30
+ export interface Context extends Resource<
31
+ "Docker.Context",
32
+ ContextProps,
33
+ {
34
+ /** Docker context name (contexts are identified by name). */
35
+ id: string;
36
+ /** Docker context name. */
37
+ name: string;
38
+ /** Human-readable description. */
39
+ description: string;
40
+ /** Docker endpoint the context targets. */
41
+ docker?: string;
42
+ },
43
+ never,
44
+ Providers
45
+ > {}
46
+
47
+ /**
48
+ * A named Docker CLI context — a pointer to a Docker engine (local socket,
49
+ * SSH host, or TCP endpoint) that other Docker resources deploy through.
50
+ *
51
+ * Pass the context (or its name) to any Docker resource's `context` prop to
52
+ * run that resource's operations against the referenced engine instead of the
53
+ * default one. Changing the endpoint updates the context in place; renaming
54
+ * it, or clearing a previously-set endpoint, replaces it.
55
+ *
56
+ *
57
+ * ### Creating Contexts
58
+ * **Example:** Remote engine over SSH
59
+ * ```typescript
60
+ * const vps = yield* Docker.Context("vps", {
61
+ * docker: "host=ssh://deploy@example.com",
62
+ * description: "production swarm manager",
63
+ * });
64
+ * ```
65
+ *
66
+ * ### Using a Context
67
+ * **Example:** Deploy resources through the context
68
+ * ```typescript
69
+ * const vps = yield* Docker.Context("vps", {
70
+ * docker: "host=ssh://deploy@example.com",
71
+ * });
72
+ * const network = yield* Docker.Network("app-net", {
73
+ * context: vps,
74
+ * driver: "overlay",
75
+ * });
76
+ * const app = yield* Docker.Service("app", {
77
+ * context: vps,
78
+ * image: "nginx:alpine",
79
+ * networks: [network.name],
80
+ * });
81
+ * ```
82
+ *
83
+ * **Example:** Local development vs production
84
+ * ```typescript
85
+ * const dev = yield* Alchemy.ALCHEMY_DEV;
86
+ * const context = yield* Docker.Context("target", {
87
+ * name: dev ? "local" : "vps",
88
+ * docker: dev ? undefined : "host=ssh://deploy@example.com",
89
+ * });
90
+ * ```
91
+ *
92
+ * @resource
93
+ * @product Context
94
+ */
95
+ export const Context = Resource<Context>("Docker.Context");
96
+
97
+ export const ContextProvider = () =>
98
+ Provider.effect(
99
+ Context,
100
+ Effect.gen(function* () {
101
+ const docker = yield* Docker;
102
+
103
+ const inspect = (nameOrId: string) =>
104
+ docker.context
105
+ .inspect(nameOrId)
106
+ .pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined));
107
+
108
+ return Context.Provider.of({
109
+ list: () => Effect.succeed([]),
110
+ read: Effect.fn(function* ({ id, instanceId, olds, output }) {
111
+ const name = yield* dockerPhysicalName(id, olds, instanceId);
112
+ const live = yield* inspect(name);
113
+ if (!live) return undefined;
114
+ const attrs = toContextAttributes(live);
115
+ if (output) return attrs;
116
+ return attrs;
117
+ }),
118
+ diff: Effect.fn(function* ({ id, instanceId, news, olds, output }) {
119
+ if (!isResolved(news)) return undefined;
120
+
121
+ const oldDesired = yield* normalizeDesired(id, olds, instanceId);
122
+ const newDesired = yield* normalizeDesired(id, news, instanceId);
123
+
124
+ if (oldDesired.name !== newDesired.name) {
125
+ return { action: "replace" as const, deleteFirst: true };
126
+ }
127
+
128
+ // `docker context update` can set docker host, but cannot reliably
129
+ // clear it once set. Recreate when transitioning to no docker.
130
+ if (oldDesired.docker && !newDesired.docker) {
131
+ return { action: "replace" as const, deleteFirst: true };
132
+ }
133
+
134
+ if (!Equal.equals(oldDesired, newDesired)) {
135
+ return { action: "update" as const };
136
+ }
137
+
138
+ if (output && !(yield* inspect(output.id))) {
139
+ return { action: "update" as const };
140
+ }
141
+
142
+ return { action: "noop" as const };
143
+ }),
144
+ reconcile: Effect.fn(function* ({ id, instanceId, news, olds, output }) {
145
+ const desired = yield* normalizeDesired(id, news, instanceId);
146
+
147
+ if (output && olds) {
148
+ const oldDesired = yield* normalizeDesired(id, olds, instanceId);
149
+ if (deepEqual(oldDesired, desired)) {
150
+ const current = yield* inspect(output.id);
151
+ if (current) {
152
+ return toContextAttributes(current);
153
+ }
154
+ }
155
+ }
156
+
157
+ const existing = output ? yield* inspect(output.id) : yield* inspect(desired.name);
158
+
159
+ if (!existing) {
160
+ const createArgs = {
161
+ name: desired.name,
162
+ ...(desired.docker ? { docker: desired.docker } : {}),
163
+ ...(desired.description.length > 0 ? { description: desired.description } : {}),
164
+ };
165
+ yield* docker.context.create(createArgs);
166
+ return toContextAttributes(yield* docker.context.inspect(desired.name));
167
+ }
168
+
169
+ const current = toContextAttributes(existing);
170
+ const needsUpdate =
171
+ current.description !== desired.description || current.docker !== desired.docker;
172
+
173
+ if (needsUpdate) {
174
+ yield* docker.context.update({
175
+ name: desired.name,
176
+ ...(desired.docker ? { docker: desired.docker } : {}),
177
+ description: desired.description,
178
+ });
179
+ }
180
+
181
+ return toContextAttributes(yield* docker.context.inspect(desired.name));
182
+ }),
183
+ delete: Effect.fn(({ output }) =>
184
+ docker.context.remove(output.id, true).pipe(
185
+ Effect.catchReason("PlatformError", "NotFound", () => Effect.void),
186
+ Effect.as(undefined),
187
+ ),
188
+ ),
189
+ });
190
+ }),
191
+ );
192
+
193
+ // Every prop is optional, so `Docker.Context("ctx")` arrives with no props.
194
+ const normalizeDesired = (id: string, props: ContextProps | undefined, instanceId: string) =>
195
+ dockerPhysicalName(id, props, instanceId).pipe(
196
+ Effect.map((name) => ({
197
+ name,
198
+ description: normalizeDescription(props?.description),
199
+ docker: normalizeDocker(props?.docker),
200
+ })),
201
+ );
202
+
203
+ const normalizeDescription = (description: string | undefined): string => description?.trim() ?? "";
204
+
205
+ const normalizeDocker = (docker: string | undefined): string | undefined => {
206
+ const value = docker?.trim();
207
+ return value && value.length > 0 ? value : undefined;
208
+ };
209
+
210
+ const toContextAttributes = (context: Docker.Context): Context["Attributes"] => ({
211
+ id: context.Name,
212
+ name: context.Name,
213
+ description: context.Metadata?.Description ?? "",
214
+ docker: context.Endpoints?.docker,
215
+ });