Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
cluster: support pre-built image
Browse files Browse the repository at this point in the history
  • Loading branch information
fwang committed Sep 20, 2024
1 parent 71cbc94 commit 7e69488
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 112 deletions.
102 changes: 58 additions & 44 deletions platform/src/components/aws/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,16 @@ export interface ClusterServiceArgs {
directory?: Input<string>;
};
/**
* 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: {
Expand All @@ -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<string>;
/**
* 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<string>;
/**
* 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<Record<string, Input<string>>>;
}>;
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<string>;
/**
* 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<string>;
/**
* 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<Record<string, Input<string>>>;
}
>;
/**
* 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
Expand Down
136 changes: 68 additions & 68 deletions platform/src/components/aws/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
})),
Expand Down

0 comments on commit 7e69488

Please sign in to comment.