@alchemy.run/docker 0.0.0 → 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 +46 -4
- 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 +58 -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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Dockerfile content for container platforms.
|
|
3
|
+
*
|
|
4
|
+
* Container platform props accept `dockerfile: string | { content: Input<string> }`:
|
|
5
|
+
* a plain string is always a **path** (relative to `context`); an object with a
|
|
6
|
+
* `content` key is **inline Dockerfile content**. `Dockerfile.inline` is the
|
|
7
|
+
* ergonomic way to produce the latter — a tagged template whose interpolations
|
|
8
|
+
* ride the normal {@link Output} machinery, so referencing another resource's
|
|
9
|
+
* attributes (e.g. an {@link ECR.Image}'s `imageUri`) creates a real dependency
|
|
10
|
+
* edge and re-resolves on change.
|
|
11
|
+
*
|
|
12
|
+
* The returned value is a plain JSON-serializable object (no symbols or
|
|
13
|
+
* brands) so it round-trips through persisted state unchanged; the structural
|
|
14
|
+
* `content` key is the discriminant.
|
|
15
|
+
*
|
|
16
|
+
* @example Base plus system packages, still bundling your Effect program
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import * as Dockerfile from "@alchemy.run/docker/Dockerfile";
|
|
19
|
+
*
|
|
20
|
+
* const worker = yield* AWS.ECS.Task(
|
|
21
|
+
* "Transcoder",
|
|
22
|
+
* {
|
|
23
|
+
* cluster,
|
|
24
|
+
* main: import.meta.url,
|
|
25
|
+
* dockerfile: Dockerfile.inline`
|
|
26
|
+
* FROM oven/bun:1
|
|
27
|
+
* RUN apt-get update && apt-get install -y ffmpeg
|
|
28
|
+
* `,
|
|
29
|
+
* },
|
|
30
|
+
* impl,
|
|
31
|
+
* );
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example Deriving from another resource's image (dependency edge for free)
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const base = yield* AWS.ECR.Image("MlBase", { context: "./ml-base" });
|
|
37
|
+
*
|
|
38
|
+
* const trainer = yield* AWS.ECS.Task(
|
|
39
|
+
* "Trainer",
|
|
40
|
+
* {
|
|
41
|
+
* cluster,
|
|
42
|
+
* main: import.meta.url,
|
|
43
|
+
* dockerfile: Dockerfile.inline`
|
|
44
|
+
* FROM ${base.imageUri}
|
|
45
|
+
* RUN pip install -r /opt/requirements.txt
|
|
46
|
+
* `,
|
|
47
|
+
* },
|
|
48
|
+
* impl,
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
import type { Input } from "alchemy/Input";
|
|
53
|
+
import * as Output from "alchemy/Output";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Inline Dockerfile content. A plain, state-serializable object; the
|
|
57
|
+
* structural `content` key distinguishes it from a path string wherever
|
|
58
|
+
* `dockerfile` props accept `string | InlineDockerfile`.
|
|
59
|
+
*
|
|
60
|
+
* Never interpolate secrets: anything in Dockerfile content is baked into
|
|
61
|
+
* image layers. `Input<string>` excludes `Redacted` values at the type level.
|
|
62
|
+
*/
|
|
63
|
+
export interface InlineDockerfile {
|
|
64
|
+
readonly content: Input<string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Structural guard for {@link InlineDockerfile} vs a path string. */
|
|
68
|
+
export const isInlineDockerfile = (value: unknown): value is InlineDockerfile =>
|
|
69
|
+
typeof value === "object" && value !== null && "content" in value;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tagged template producing {@link InlineDockerfile}. Interpolations may be
|
|
73
|
+
* plain strings or `Output<string>`s (resolved at deploy time via
|
|
74
|
+
* `Output.interpolate`); with no interpolations the content is a plain string.
|
|
75
|
+
*/
|
|
76
|
+
export const inline = (
|
|
77
|
+
template: TemplateStringsArray,
|
|
78
|
+
...args: Array<Input<string>>
|
|
79
|
+
): InlineDockerfile => ({
|
|
80
|
+
content: args.length === 0 ? template.raw.join("") : Output.interpolate(template, ...args),
|
|
81
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a healthcheck `cmd` as the single string Docker's `--health-cmd`
|
|
3
|
+
* flag takes. The flag always runs its value through the container's shell, so
|
|
4
|
+
* the Docker API's exec form has to be flattened:
|
|
5
|
+
*
|
|
6
|
+
* - `"cmd"`: used as is.
|
|
7
|
+
* - `["CMD-SHELL", "cmd"]`: the shell command, without the marker.
|
|
8
|
+
* - `["CMD", "pg_isready", "-U", "app"]`: the arguments, each quoted so it
|
|
9
|
+
* reaches the program as one argument.
|
|
10
|
+
* - `["NONE"]`: no command (the caller disables the healthcheck).
|
|
11
|
+
* - any other array: joined with spaces and run in the shell, as before, so
|
|
12
|
+
* `["curl -f localhost || exit 1"]` keeps working.
|
|
13
|
+
*/
|
|
14
|
+
export const healthcheckCommand = (cmd: string[] | string): string | undefined => {
|
|
15
|
+
if (typeof cmd === "string") return cmd;
|
|
16
|
+
const [head, ...rest] = cmd;
|
|
17
|
+
switch (head) {
|
|
18
|
+
case "NONE":
|
|
19
|
+
return undefined;
|
|
20
|
+
case "CMD-SHELL":
|
|
21
|
+
return rest.join(" ");
|
|
22
|
+
case "CMD":
|
|
23
|
+
return rest.map(shellQuote).join(" ");
|
|
24
|
+
default:
|
|
25
|
+
return cmd.join(" ");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** True when `cmd` is Docker's `["NONE"]` form, which disables the healthcheck. */
|
|
30
|
+
export const isHealthcheckDisabled = (cmd: string[] | string): boolean =>
|
|
31
|
+
Array.isArray(cmd) && cmd[0] === "NONE";
|
|
32
|
+
|
|
33
|
+
/** Single-quotes an argument unless it is made only of shell-safe characters. */
|
|
34
|
+
const shellQuote = (arg: string): string =>
|
|
35
|
+
/^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
|
package/src/Image.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import * as Artifacts from "alchemy/Artifacts";
|
|
2
|
+
import { isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as FileSystem from "effect/FileSystem";
|
|
7
|
+
import * as Path from "effect/Path";
|
|
8
|
+
import { Docker, dockerContextName, dockerPhysicalName } from "./Docker.ts";
|
|
9
|
+
import type { Providers } from "./Providers.ts";
|
|
10
|
+
import {
|
|
11
|
+
type ImageRegistry,
|
|
12
|
+
parseCreatedAt,
|
|
13
|
+
publishedRepoDigest,
|
|
14
|
+
repositoryFromImageRef,
|
|
15
|
+
withRegistryHost,
|
|
16
|
+
} from "./Registry.ts";
|
|
17
|
+
|
|
18
|
+
export interface DockerBuildOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Build context directory.
|
|
21
|
+
*
|
|
22
|
+
* @default Current working directory.
|
|
23
|
+
*/
|
|
24
|
+
context?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Dockerfile path, relative to the context unless absolute.
|
|
27
|
+
*
|
|
28
|
+
* @default "Dockerfile"
|
|
29
|
+
*/
|
|
30
|
+
dockerfile?: string;
|
|
31
|
+
/** Target platform, e.g. `"linux/amd64"`. */
|
|
32
|
+
platform?: string;
|
|
33
|
+
/** Docker build arguments. */
|
|
34
|
+
args?: Record<string, string>;
|
|
35
|
+
/** Multi-stage build target. */
|
|
36
|
+
target?: string;
|
|
37
|
+
/** Cache sources passed as `--cache-from`. */
|
|
38
|
+
cacheFrom?: string[];
|
|
39
|
+
/** Cache destinations passed as `--cache-to`. */
|
|
40
|
+
cacheTo?: string[];
|
|
41
|
+
/** Additional Docker build options. */
|
|
42
|
+
options?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ImageProps {
|
|
46
|
+
/**
|
|
47
|
+
* Repository/name for the built image.
|
|
48
|
+
*
|
|
49
|
+
* @default Generated from stack, stage, logical id, and instance id.
|
|
50
|
+
*/
|
|
51
|
+
name?: string;
|
|
52
|
+
/** Image tag. @default "latest" */
|
|
53
|
+
tag?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Registry credentials. The image is pushed to this registry (unless
|
|
56
|
+
* `skipPush`), and the build itself authenticates with them, so private
|
|
57
|
+
* base images and `type=registry` caches hosted there resolve without a
|
|
58
|
+
* host `docker login`. Credentials already in `DOCKER_AUTH_CONFIG` for
|
|
59
|
+
* other registries are kept. Build-time authentication needs Buildx 0.26+
|
|
60
|
+
* (Docker Desktop 4.44+) or the legacy builder; older Buildx plugins ignore
|
|
61
|
+
* it, so a private base image there still needs `docker login`.
|
|
62
|
+
*/
|
|
63
|
+
registry?: ImageRegistry;
|
|
64
|
+
/** Skip registry push even when `registry` is set. @default false */
|
|
65
|
+
skipPush?: boolean;
|
|
66
|
+
/** Docker context name or context resource. */
|
|
67
|
+
context?: Docker.ContextRef;
|
|
68
|
+
/** Docker build configuration. */
|
|
69
|
+
build: DockerBuildOptions;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface Image extends Resource<
|
|
73
|
+
"Docker.Image",
|
|
74
|
+
ImageProps,
|
|
75
|
+
{
|
|
76
|
+
/** Image repository/name without tag. */
|
|
77
|
+
name: string;
|
|
78
|
+
/** Final image reference. Includes registry host when pushed there. */
|
|
79
|
+
imageRef: string;
|
|
80
|
+
/** Local image id after build/tag. */
|
|
81
|
+
imageId: string;
|
|
82
|
+
/** Registry digest after push when available. */
|
|
83
|
+
repoDigest?: string;
|
|
84
|
+
/** Tag used for the local image. */
|
|
85
|
+
tag: string;
|
|
86
|
+
/** Build timestamp in milliseconds since epoch. */
|
|
87
|
+
builtAt: number;
|
|
88
|
+
},
|
|
89
|
+
never,
|
|
90
|
+
Providers
|
|
91
|
+
> {}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Builds, tags, and optionally pushes Docker images through the active Docker
|
|
95
|
+
* context.
|
|
96
|
+
*
|
|
97
|
+
* This resource uses the Docker CLI and whatever daemon or remote context the
|
|
98
|
+
* CLI is configured to target. It is separate from `Cloudflare.Container`;
|
|
99
|
+
* registry image references are the boundary between Docker-managed images and
|
|
100
|
+
* cloud container platforms.
|
|
101
|
+
*
|
|
102
|
+
* `Image` always builds from a Dockerfile. To pull (and optionally re-tag and
|
|
103
|
+
* push) an existing registry image, use `Docker.RemoteImage`.
|
|
104
|
+
*
|
|
105
|
+
*
|
|
106
|
+
* ### Building Images
|
|
107
|
+
* **Example:** Build from a Dockerfile
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const image = yield* Docker.Image("app", {
|
|
110
|
+
* name: "my-app",
|
|
111
|
+
* tag: "latest",
|
|
112
|
+
* build: {
|
|
113
|
+
* context: "./app",
|
|
114
|
+
* dockerfile: "Dockerfile",
|
|
115
|
+
* args: { NODE_ENV: "production" },
|
|
116
|
+
* },
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* ### Registry Push
|
|
121
|
+
* **Example:** Push with Redacted credentials
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const image = yield* Docker.Image("app", {
|
|
124
|
+
* name: "my-app",
|
|
125
|
+
* build: { context: "./app" },
|
|
126
|
+
* registry: {
|
|
127
|
+
* server: "ghcr.io",
|
|
128
|
+
* username: "octocat",
|
|
129
|
+
* password: Config.Redacted("GITHUB_TOKEN"),
|
|
130
|
+
* },
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* ### Private Base Images and Registry Caches
|
|
135
|
+
* **Example:** Build FROM a private base image with a registry cache
|
|
136
|
+
* ```typescript
|
|
137
|
+
* // Dockerfile: FROM registry.example.com/base:v1
|
|
138
|
+
* const image = yield* Docker.Image("app", {
|
|
139
|
+
* name: "registry.example.com/app",
|
|
140
|
+
* build: {
|
|
141
|
+
* context: "./app",
|
|
142
|
+
* cacheFrom: ["type=registry,ref=registry.example.com/app:buildcache"],
|
|
143
|
+
* cacheTo: ["type=registry,ref=registry.example.com/app:buildcache,mode=max"],
|
|
144
|
+
* },
|
|
145
|
+
* // Authenticates the base-image pull and cache import/export, then the push.
|
|
146
|
+
* registry: {
|
|
147
|
+
* server: "registry.example.com",
|
|
148
|
+
* username: "deploy",
|
|
149
|
+
* password: Config.Redacted("REGISTRY_PASSWORD"),
|
|
150
|
+
* },
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* ### Docker Context
|
|
155
|
+
* **Example:** Build in a named Docker context
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const image = yield* Docker.Image("app", {
|
|
158
|
+
* name: "my-app",
|
|
159
|
+
* context: "remote-build",
|
|
160
|
+
* build: { context: "./app" },
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @resource
|
|
165
|
+
* @product Image
|
|
166
|
+
*/
|
|
167
|
+
export const Image = Resource<Image>("Docker.Image");
|
|
168
|
+
|
|
169
|
+
export const ImageProvider = () =>
|
|
170
|
+
Provider.effect(
|
|
171
|
+
Image,
|
|
172
|
+
Effect.gen(function* () {
|
|
173
|
+
const fs = yield* FileSystem.FileSystem;
|
|
174
|
+
const path = yield* Path.Path;
|
|
175
|
+
const docker = yield* Docker;
|
|
176
|
+
|
|
177
|
+
const buildAndInspectImage = Effect.fn(function* (
|
|
178
|
+
id: string,
|
|
179
|
+
props: ImageProps,
|
|
180
|
+
instanceId: string,
|
|
181
|
+
) {
|
|
182
|
+
const name = yield* dockerPhysicalName(id, props, instanceId);
|
|
183
|
+
const engineContext = dockerContextName(props.context);
|
|
184
|
+
const tag = props.tag ?? "latest";
|
|
185
|
+
const ref = `${name}:${tag}`;
|
|
186
|
+
|
|
187
|
+
const paths = yield* resolveBuildPaths(props.build);
|
|
188
|
+
yield* docker.image.build({
|
|
189
|
+
tag: ref,
|
|
190
|
+
context: paths.context,
|
|
191
|
+
file: paths.dockerfile,
|
|
192
|
+
platform: props.build.platform,
|
|
193
|
+
target: props.build.target,
|
|
194
|
+
"build-arg": props.build.args,
|
|
195
|
+
"cache-from": props.build.cacheFrom,
|
|
196
|
+
"cache-to": props.build.cacheTo,
|
|
197
|
+
args: props.build.options,
|
|
198
|
+
engineContext,
|
|
199
|
+
// Base images and registry caches may live in the same registry;
|
|
200
|
+
// the push itself stays in `reconcile`.
|
|
201
|
+
credentials: props.registry,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Read the freshly built image's id and creation time straight from
|
|
205
|
+
// Docker rather than synthesizing a wall-clock timestamp.
|
|
206
|
+
return {
|
|
207
|
+
name,
|
|
208
|
+
tag,
|
|
209
|
+
image: yield* docker.image.inspect(ref, engineContext),
|
|
210
|
+
ref,
|
|
211
|
+
};
|
|
212
|
+
}, Artifacts.cached("build"));
|
|
213
|
+
|
|
214
|
+
const resolveBuildPaths = Effect.fn(function* (build: DockerBuildOptions) {
|
|
215
|
+
const cwd = yield* Effect.sync(() => process.cwd());
|
|
216
|
+
const context = path.resolve(build.context ?? cwd);
|
|
217
|
+
const dockerfile = build.dockerfile
|
|
218
|
+
? path.isAbsolute(build.dockerfile)
|
|
219
|
+
? build.dockerfile
|
|
220
|
+
: path.resolve(context, build.dockerfile)
|
|
221
|
+
: path.resolve(context, "Dockerfile");
|
|
222
|
+
if (!(yield* fs.exists(context))) {
|
|
223
|
+
return yield* Effect.die(`Docker build context does not exist: ${context}`);
|
|
224
|
+
}
|
|
225
|
+
if (!(yield* fs.exists(dockerfile))) {
|
|
226
|
+
return yield* Effect.die(`Dockerfile does not exist: ${dockerfile}`);
|
|
227
|
+
}
|
|
228
|
+
return { context, dockerfile };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return Image.Provider.of({
|
|
232
|
+
list: () => Effect.succeed([]),
|
|
233
|
+
read: Effect.fn(function* ({ id, instanceId, olds, output }) {
|
|
234
|
+
const context = dockerContextName(olds.context);
|
|
235
|
+
const ref =
|
|
236
|
+
output?.imageRef ??
|
|
237
|
+
(yield* dockerPhysicalName(id, olds, instanceId).pipe(
|
|
238
|
+
Effect.map((name) => `${name}:${olds.tag ?? "latest"}`),
|
|
239
|
+
));
|
|
240
|
+
const image = yield* docker.image
|
|
241
|
+
.inspect(ref, context)
|
|
242
|
+
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined));
|
|
243
|
+
if (!image) return undefined;
|
|
244
|
+
return {
|
|
245
|
+
name: output?.name ?? repositoryFromImageRef(ref),
|
|
246
|
+
imageRef: ref,
|
|
247
|
+
imageId: image.Id,
|
|
248
|
+
repoDigest: output?.repoDigest,
|
|
249
|
+
tag: output?.tag ?? olds.tag ?? "latest",
|
|
250
|
+
builtAt: output?.builtAt ?? parseCreatedAt(image.Created),
|
|
251
|
+
};
|
|
252
|
+
}),
|
|
253
|
+
diff: Effect.fn(function* ({ id, instanceId, news, output, olds }) {
|
|
254
|
+
if (!isResolved(news) || !output) return undefined;
|
|
255
|
+
if (dockerContextName(olds.context) !== dockerContextName(news.context)) {
|
|
256
|
+
return { action: "update" };
|
|
257
|
+
}
|
|
258
|
+
const { image } = yield* buildAndInspectImage(id, news, instanceId);
|
|
259
|
+
if (output?.imageId !== image.Id) {
|
|
260
|
+
return { action: "update" };
|
|
261
|
+
}
|
|
262
|
+
}),
|
|
263
|
+
reconcile: Effect.fn(function* ({ id, instanceId, news, session }) {
|
|
264
|
+
const context = dockerContextName(news.context);
|
|
265
|
+
const { name, tag, image, ref } = yield* buildAndInspectImage(id, news, instanceId);
|
|
266
|
+
|
|
267
|
+
let repoDigest: string | undefined;
|
|
268
|
+
let targetImageRef: string = ref;
|
|
269
|
+
if (news.registry && !news.skipPush) {
|
|
270
|
+
yield* session.note(`Pushing image to registry "${news.registry.server}"`);
|
|
271
|
+
targetImageRef = withRegistryHost(ref, news.registry);
|
|
272
|
+
const pushed = yield* docker.image.push(ref, news.registry, undefined, context);
|
|
273
|
+
const published = yield* docker.image.inspect(ref, context);
|
|
274
|
+
repoDigest = publishedRepoDigest(ref, pushed, published.RepoDigests, targetImageRef);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
name,
|
|
279
|
+
imageRef: targetImageRef,
|
|
280
|
+
imageId: image.Id,
|
|
281
|
+
repoDigest,
|
|
282
|
+
tag,
|
|
283
|
+
builtAt: parseCreatedAt(image.Created),
|
|
284
|
+
};
|
|
285
|
+
}),
|
|
286
|
+
delete: Effect.fn(({ olds, output }) =>
|
|
287
|
+
docker.image
|
|
288
|
+
.remove(output.imageRef, undefined, dockerContextName(olds.context))
|
|
289
|
+
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.void)),
|
|
290
|
+
),
|
|
291
|
+
});
|
|
292
|
+
}),
|
|
293
|
+
);
|