From 7e69488d1ced9507acde33e06c084f5dee56b20a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 20 Sep 2024 18:20:43 -0400 Subject: [PATCH] cluster: support pre-built image --- platform/src/components/aws/cluster.ts | 102 +++++++++++-------- platform/src/components/aws/service.ts | 136 ++++++++++++------------- 2 files changed, 126 insertions(+), 112 deletions(-) diff --git a/platform/src/components/aws/cluster.ts b/platform/src/components/aws/cluster.ts index 32ea47a66..ea6592281 100644 --- a/platform/src/components/aws/cluster.ts +++ b/platform/src/components/aws/cluster.ts @@ -283,13 +283,16 @@ export interface ClusterServiceArgs { directory?: Input; }; /** - * Configure the docker build command for building the image. + * Configure the docker build command for building the image or specify a pre-built image. + * + * @default Build a docker image from the Dockerfile in the root directory. + * @example + * + * Building a docker image. * * Prior to building the image, SST will automatically add the `.sst` directory * to the `.dockerignore` if not already present. * - * @default `{}` - * @example * ```js * { * image: { @@ -301,48 +304,59 @@ export interface ClusterServiceArgs { * } * } * ``` + * + * Alternatively, you can pass in a pre-built image. + * + * ```js + * { + * image: "nginxdemos/hello:plain-text" + * } + * ``` */ - image?: Input<{ - /** - * The path to the [Docker build context](https://docs.docker.com/build/building/context/#local-context). The path is relative to your project's `sst.config.ts`. - * @default `"."` - * @example - * - * To change where the docker build context is located. - * - * ```js - * { - * context: "./app" - * } - * ``` - */ - context?: Input; - /** - * The path to the [Dockerfile](https://docs.docker.com/reference/cli/docker/image/build/#file). - * The path is relative to the build `context`. - * @default `"Dockerfile"` - * @example - * To use a different Dockerfile. - * ```js - * { - * dockerfile: "Dockerfile.prod" - * } - * ``` - */ - dockerfile?: Input; - /** - * Key-value pairs of [build args](https://docs.docker.com/build/guide/build-args/) to pass to the docker build command. - * @example - * ```js - * { - * args: { - * MY_VAR: "value" - * } - * } - * ``` - */ - args?: Input>>; - }>; + image?: Input< + | string + | { + /** + * The path to the [Docker build context](https://docs.docker.com/build/building/context/#local-context). The path is relative to your project's `sst.config.ts`. + * @default `"."` + * @example + * + * To change where the docker build context is located. + * + * ```js + * { + * context: "./app" + * } + * ``` + */ + context?: Input; + /** + * The path to the [Dockerfile](https://docs.docker.com/reference/cli/docker/image/build/#file). + * The path is relative to the build `context`. + * @default `"Dockerfile"` + * @example + * To use a different Dockerfile. + * ```js + * { + * dockerfile: "Dockerfile.prod" + * } + * ``` + */ + dockerfile?: Input; + /** + * Key-value pairs of [build args](https://docs.docker.com/build/guide/build-args/) to pass to the docker build command. + * @example + * ```js + * { + * args: { + * MY_VAR: "value" + * } + * } + * ``` + */ + args?: Input>>; + } + >; /** * Configure a public endpoint for the service. When configured, a load balancer * will be created to route traffic to the containers. By default, the endpoint is an diff --git a/platform/src/components/aws/service.ts b/platform/src/components/aws/service.ts index f4be4965b..d64bb88b6 100644 --- a/platform/src/components/aws/service.ts +++ b/platform/src/components/aws/service.ts @@ -180,16 +180,18 @@ export class Service extends Component implements Link.Linkable { } function normalizeImage() { - return all([args.image ?? {}, architecture]).apply( - ([image, architecture]) => ({ + return all([args.image, architecture]).apply(([image, architecture]) => { + if (typeof image === "string") return image; + + return { ...image, - context: image.context ?? ".", + context: image?.context ?? ".", platform: architecture === "arm64" ? Platform.Linux_arm64 : Platform.Linux_amd64, - }), - ); + }; + }); } function normalizeCpu() { @@ -315,68 +317,61 @@ export class Service extends Component implements Link.Linkable { } function createImage() { - // Edit .dockerignore file - const imageArgsNew = imageArgs.apply((imageArgs) => { - const context = path.join($cli.paths.root, imageArgs.context); - const dockerfile = imageArgs.dockerfile ?? "Dockerfile"; + return imageArgs.apply((imageArgs) => { + if (typeof imageArgs === "string") return output(imageArgs); - // get .dockerignore file - const file = (() => { - let filePath = path.join(context, `${dockerfile}.dockerignore`); - if (fs.existsSync(filePath)) return filePath; - filePath = path.join(context, ".dockerignore"); - if (fs.existsSync(filePath)) return filePath; - })(); + const contextPath = path.join($cli.paths.root, imageArgs.context); + const dockerfile = imageArgs.dockerfile ?? "Dockerfile"; + const dockerfilePath = imageArgs.dockerfile + ? path.join(contextPath, imageArgs.dockerfile) + : path.join(contextPath, imageArgs.context, "Dockerfile"); + const dockerIgnorePath = fs.existsSync( + path.join(contextPath, `${dockerfile}.dockerignore`), + ) + ? path.join(contextPath, `${dockerfile}.dockerignore`) + : path.join(contextPath, ".dockerignore"); // add .sst to .dockerignore if not exist - const content = file ? fs.readFileSync(file).toString() : ""; - const lines = content.split("\n"); + const lines = fs.existsSync(dockerIgnorePath) + ? fs.readFileSync(dockerIgnorePath).toString().split("\n") + : []; if (!lines.find((line) => line === ".sst")) { fs.writeFileSync( - file ?? path.join(context, ".dockerignore"), + dockerIgnorePath, [...lines, "", "# sst", ".sst"].join("\n"), ); } - return imageArgs; - }); - // Build image - return new Image( - ...transform( - args.transform?.image, - `${name}Image`, - { - context: { - location: imageArgsNew.apply((v) => - path.join($cli.paths.root, v.context), - ), + // Build image + const image = new Image( + ...transform( + args.transform?.image, + `${name}Image`, + { + context: { location: contextPath }, + dockerfile: { location: dockerfilePath }, + buildArgs: imageArgs.args ?? {}, + platforms: [imageArgs.platform], + tags: [interpolate`${bootstrapData.assetEcrUrl}:${name}`], + registries: [ + ecr + .getAuthorizationTokenOutput({ + registryId: bootstrapData.assetEcrRegistryId, + }) + .apply((authToken) => ({ + address: authToken.proxyEndpoint, + password: secret(authToken.password), + username: authToken.userName, + })), + ], + push: true, }, - dockerfile: { - location: imageArgsNew.apply((v) => - v.dockerfile - ? path.join($cli.paths.root, v.dockerfile) - : path.join($cli.paths.root, v.context, "Dockerfile"), - ), - }, - buildArgs: imageArgsNew.apply((v) => v.args ?? {}), - platforms: [imageArgs.platform], - tags: [interpolate`${bootstrapData.assetEcrUrl}:${name}`], - registries: [ - ecr - .getAuthorizationTokenOutput({ - registryId: bootstrapData.assetEcrRegistryId, - }) - .apply((authToken) => ({ - address: authToken.proxyEndpoint, - password: secret(authToken.password), - username: authToken.userName, - })), - ], - push: true, - }, - { parent: self }, - ), - ); + { parent: self }, + ), + ); + + return interpolate`${bootstrapData.assetEcrUrl}@${image.digest}`; + }); } function createLoadBalancer() { @@ -615,7 +610,7 @@ export class Service extends Component implements Link.Linkable { containerDefinitions: $jsonStringify([ { name, - image: interpolate`${bootstrapData.assetEcrUrl}@${image.digest}`, + image, pseudoTerminal: true, portMappings: [{ containerPortRange: "1-65535" }], logConfiguration: { @@ -785,11 +780,14 @@ export class Service extends Component implements Link.Linkable { function registerReceiver() { self.registerOutputs({ _receiver: imageArgs.apply((imageArgs) => ({ - directory: path.join( - imageArgs.dockerfile - ? path.dirname(imageArgs.dockerfile) - : imageArgs.context, - ), + directory: + typeof imageArgs === "string" + ? undefined + : path.join( + imageArgs.dockerfile + ? path.dirname(imageArgs.dockerfile) + : imageArgs.context, + ), links: linkData.apply((input) => input.map((item) => item.name)), environment: { ...args.environment, @@ -812,11 +810,13 @@ export class Service extends Component implements Link.Linkable { directory: output(args.dev?.directory).apply( (dir) => dir || - path.join( - imageArgs.dockerfile - ? path.dirname(imageArgs.dockerfile) - : imageArgs.context, - ), + (typeof imageArgs === "string" + ? undefined + : path.join( + imageArgs.dockerfile + ? path.dirname(imageArgs.dockerfile) + : imageArgs.context, + )), ), command: args.dev?.command, })),