diff --git a/platform/src/components/aws/cluster.ts b/platform/src/components/aws/cluster.ts index 497bba587..12392a3f1 100644 --- a/platform/src/components/aws/cluster.ts +++ b/platform/src/components/aws/cluster.ts @@ -150,33 +150,33 @@ export interface ClusterArgs { * ``` */ vpc: - | Vpc - | Input<{ - /** - * The ID of the VPC. - */ - id: Input; - /** - * A list of subnet IDs in the VPC to place the load balancer in. - */ - loadBalancerSubnets: Input[]>; - /** - * A list of private subnet IDs in the VPC to place the services in. - */ - serviceSubnets: Input[]>; - /** - * A list of VPC security group IDs for the service. - */ - securityGroups: Input[]>; - /** - * The ID of the Cloud Map namespace to use for the service. - */ - cloudmapNamespaceId: Input; - /** - * The name of the Cloud Map namespace to use for the service. - */ - cloudmapNamespaceName: Input; - }>; + | Vpc + | Input<{ + /** + * The ID of the VPC. + */ + id: Input; + /** + * A list of subnet IDs in the VPC to place the load balancer in. + */ + loadBalancerSubnets: Input[]>; + /** + * A list of private subnet IDs in the VPC to place the services in. + */ + serviceSubnets: Input[]>; + /** + * A list of VPC security group IDs for the service. + */ + securityGroups: Input[]>; + /** + * The ID of the Cloud Map namespace to use for the service. + */ + cloudmapNamespaceId: Input; + /** + * The name of the Cloud Map namespace to use for the service. + */ + cloudmapNamespaceName: Input; + }>; /** * Force upgrade from `Cluster.v1` to the latest `Cluster` version. The only valid value * is `v2`, which is the version of the new `Cluster`. @@ -254,34 +254,38 @@ export interface ClusterServiceArgs { * Instead of deploying your service, this starts it locally. It's run * as a separate process in the `sst dev` multiplexer. Read more about * [`sst dev`](/docs/reference/cli/#dev). + * + * To disable dev mode, pass in `false`. */ - dev?: { - /** - * The `url` when this is running in dev mode. - * - * Since this component is not deployed in `sst dev`, there is no real URL. But if you are - * using this component's `url` or linking to this component's `url`, it can be useful to - * have a placeholder URL. It avoids having to handle it being `undefined`. - * @default `"http://url-unavailable-in-dev.mode"` - */ - url?: Input; - /** - * The command that `sst dev` runs to start this in dev mode. This is the command you run - * when you want to run your service locally. - */ - command?: Input; - /** - * Configure if you want to automatically start this when `sst dev` starts. You can still - * start it manually later. - * @default `true` - */ - autostart?: Input; - /** - * Change the directory from where the `command` is run. - * @default Uses the `image.dockerfile` path - */ - directory?: Input; - }; + dev?: + | false + | { + /** + * The `url` when this is running in dev mode. + * + * Since this component is not deployed in `sst dev`, there is no real URL. But if you are + * using this component's `url` or linking to this component's `url`, it can be useful to + * have a placeholder URL. It avoids having to handle it being `undefined`. + * @default `"http://url-unavailable-in-dev.mode"` + */ + url?: Input; + /** + * The command that `sst dev` runs to start this in dev mode. This is the command you run + * when you want to run your service locally. + */ + command?: Input; + /** + * Configure if you want to automatically start this when `sst dev` starts. You can still + * start it manually later. + * @default `true` + */ + autostart?: Input; + /** + * Change the directory from where the `command` is run. + * @default Uses the `image.dockerfile` path + */ + directory?: 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 @@ -340,105 +344,105 @@ export interface ClusterServiceArgs { domain?: Input< | string | { - /** - * The custom domain you want to use. - * - * @example - * ```js - * { - * domain: { - * name: "example.com" - * } - * } - * ``` - * - * Can also include subdomains based on the current stage. - * - * ```js - * { - * domain: { - * name: `${$app.stage}.example.com` - * } - * } - * ``` - */ - name: Input; - /** - * The ARN of an ACM (AWS Certificate Manager) certificate that proves ownership of the - * domain. By default, a certificate is created and validated automatically. - * - * :::tip - * You need to pass in a `cert` for domains that are not hosted on supported `dns` providers. - * ::: - * - * To manually set up a domain on an unsupported provider, you'll need to: - * - * 1. [Validate that you own the domain](https://docs.aws.amazon.com/acm/latest/userguide/domain-ownership-validation.html) by creating an ACM certificate. You can either validate it by setting a DNS record or by verifying an email sent to the domain owner. - * 2. Once validated, set the certificate ARN as the `cert` and set `dns` to `false`. - * 3. Add the DNS records in your provider to point to the load balancer endpoint. - * - * @example - * ```js - * { - * domain: { - * name: "example.com", - * dns: false, - * cert: "arn:aws:acm:us-east-1:112233445566:certificate/3a958790-8878-4cdc-a396-06d95064cf63" - * } - * } - * ``` - */ - cert?: Input; - /** - * The DNS provider to use for the domain. Defaults to the AWS. - * - * Takes an adapter that can create the DNS records on the provider. This can automate - * validating the domain and setting up the DNS routing. - * - * Supports Route 53, Cloudflare, and Vercel adapters. For other providers, you'll need - * to set `dns` to `false` and pass in a certificate validating ownership via `cert`. - * - * @default `sst.aws.dns` - * - * @example - * - * Specify the hosted zone ID for the Route 53 domain. - * - * ```js - * { - * domain: { - * name: "example.com", - * dns: sst.aws.dns({ - * zone: "Z2FDTNDATAQYW2" - * }) - * } - * } - * ``` - * - * Use a domain hosted on Cloudflare, needs the Cloudflare provider. - * - * ```js - * { - * domain: { - * name: "example.com", - * dns: sst.cloudflare.dns() - * } - * } - * ``` - * - * Use a domain hosted on Vercel, needs the Vercel provider. - * - * ```js - * { - * domain: { - * name: "example.com", - * dns: sst.vercel.dns() - * } - * } - * ``` - */ - dns?: Input; - } + /** + * The custom domain you want to use. + * + * @example + * ```js + * { + * domain: { + * name: "example.com" + * } + * } + * ``` + * + * Can also include subdomains based on the current stage. + * + * ```js + * { + * domain: { + * name: `${$app.stage}.example.com` + * } + * } + * ``` + */ + name: Input; + /** + * The ARN of an ACM (AWS Certificate Manager) certificate that proves ownership of the + * domain. By default, a certificate is created and validated automatically. + * + * :::tip + * You need to pass in a `cert` for domains that are not hosted on supported `dns` providers. + * ::: + * + * To manually set up a domain on an unsupported provider, you'll need to: + * + * 1. [Validate that you own the domain](https://docs.aws.amazon.com/acm/latest/userguide/domain-ownership-validation.html) by creating an ACM certificate. You can either validate it by setting a DNS record or by verifying an email sent to the domain owner. + * 2. Once validated, set the certificate ARN as the `cert` and set `dns` to `false`. + * 3. Add the DNS records in your provider to point to the load balancer endpoint. + * + * @example + * ```js + * { + * domain: { + * name: "example.com", + * dns: false, + * cert: "arn:aws:acm:us-east-1:112233445566:certificate/3a958790-8878-4cdc-a396-06d95064cf63" + * } + * } + * ``` + */ + cert?: Input; + /** + * The DNS provider to use for the domain. Defaults to the AWS. + * + * Takes an adapter that can create the DNS records on the provider. This can automate + * validating the domain and setting up the DNS routing. + * + * Supports Route 53, Cloudflare, and Vercel adapters. For other providers, you'll need + * to set `dns` to `false` and pass in a certificate validating ownership via `cert`. + * + * @default `sst.aws.dns` + * + * @example + * + * Specify the hosted zone ID for the Route 53 domain. + * + * ```js + * { + * domain: { + * name: "example.com", + * dns: sst.aws.dns({ + * zone: "Z2FDTNDATAQYW2" + * }) + * } + * } + * ``` + * + * Use a domain hosted on Cloudflare, needs the Cloudflare provider. + * + * ```js + * { + * domain: { + * name: "example.com", + * dns: sst.cloudflare.dns() + * } + * } + * ``` + * + * Use a domain hosted on Vercel, needs the Vercel provider. + * + * ```js + * { + * domain: { + * name: "example.com", + * dns: sst.vercel.dns() + * } + * } + * ``` + */ + dns?: Input; + } >; /** * Configure the mapping for the ports the public endpoint listens to and forwards to @@ -753,46 +757,46 @@ export interface ClusterServiceArgs { 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>>; - } + /** + * 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>>; + } >; /** * The command to override the default command in the container. @@ -913,21 +917,21 @@ export interface ClusterServiceArgs { image?: Input< | string | { - /** - * The path to the Docker build context. Same as the top-level - * [`image.context`](#image-context). - */ - context?: Input; - /** - * The path to the Dockerfile. Same as the top-level - * [`image.dockerfile`](#image-dockerfile). - */ - dockerfile?: Input; - /** - * Key-value pairs of build args. Same as the top-level [`image.args`](#image-args). - */ - args?: Input>>; - } + /** + * The path to the Docker build context. Same as the top-level + * [`image.context`](#image-context). + */ + context?: Input; + /** + * The path to the Dockerfile. Same as the top-level + * [`image.dockerfile`](#image-dockerfile). + */ + dockerfile?: Input; + /** + * Key-value pairs of build args. Same as the top-level [`image.args`](#image-args). + */ + args?: Input>>; + } >; /** * The command to override the default command in the container. Same as the top-level diff --git a/platform/src/components/aws/service.ts b/platform/src/components/aws/service.ts index 377b840af..5b62020a1 100644 --- a/platform/src/components/aws/service.ts +++ b/platform/src/components/aws/service.ts @@ -83,6 +83,7 @@ export class Service extends Component implements Link.Linkable { private readonly domain?: Output; private readonly _url?: Output; private readonly devUrl?: Output; + private readonly dev: boolean; constructor( name: string, @@ -93,6 +94,7 @@ export class Service extends Component implements Link.Linkable { const self = this; + const dev = normalizeDev(); const cluster = output(args.cluster); const { isSstVpc, vpc } = normalizeVpc(); const region = normalizeRegion(); @@ -106,11 +108,12 @@ export class Service extends Component implements Link.Linkable { const taskRole = createTaskRole(); + this.dev = !!dev; this.cloudmapNamespace = vpc.cloudmapNamespaceName; this.taskRole = taskRole; - if ($dev) { - this.devUrl = !pub ? undefined : output(args.dev?.url ?? URL_UNAVAILABLE); + if (dev) { + this.devUrl = !pub ? undefined : dev.url; registerReceiver(); return; } @@ -139,9 +142,18 @@ export class Service extends Component implements Link.Linkable { domain ? `https://${domain}/` : `http://${loadBalancer}`, ); - registerHint(); + this.registerOutputs({ _hint: this._url }); registerReceiver(); + function normalizeDev() { + if (!$dev) return undefined; + if (args.dev === false) return undefined; + + return { + url: output(args.dev?.url ?? URL_UNAVAILABLE), + }; + } + function normalizeVpc() { // "vpc" is a Vpc.v1 component if (args.vpc instanceof VpcV1) { @@ -519,7 +531,7 @@ export class Service extends Component implements Link.Linkable { args.transform?.taskRole, `${name}TaskRole`, { - assumeRolePolicy: !$dev + assumeRolePolicy: !dev ? iam.assumeRolePolicyForPrincipal({ Service: "ecs-tasks.amazonaws.com", }) @@ -820,10 +832,6 @@ export class Service extends Component implements Link.Linkable { }); } - function registerHint() { - self.registerOutputs({ _hint: self._url }); - } - function registerReceiver() { containers.apply((val) => { for (const container of val) { @@ -863,7 +871,7 @@ export class Service extends Component implements Link.Linkable { public get url() { const errorMessage = "Cannot access the URL because no public ports are exposed."; - if ($dev) { + if (this.dev) { if (!this.devUrl) throw new VisibleError(errorMessage); return this.devUrl; } @@ -876,8 +884,9 @@ export class Service extends Component implements Link.Linkable { * The name of the Cloud Map service. */ public get service() { - if ($dev) return interpolate`dev.${this.cloudmapNamespace}`; - return interpolate`${this.cloudmapService!.name}.${this.cloudmapNamespace}`; + return this.dev + ? interpolate`dev.${this.cloudmapNamespace}` + : interpolate`${this.cloudmapService!.name}.${this.cloudmapNamespace}`; } /** @@ -890,7 +899,7 @@ export class Service extends Component implements Link.Linkable { * The Amazon ECS Service. */ get service() { - if ($dev) + if (self.dev) throw new VisibleError("Cannot access `nodes.service` in dev mode."); return self.service!; }, @@ -904,7 +913,7 @@ export class Service extends Component implements Link.Linkable { * The Amazon ECS Task Definition. */ get taskDefinition() { - if ($dev) + if (self.dev) throw new VisibleError( "Cannot access `nodes.taskDefinition` in dev mode.", ); @@ -914,7 +923,7 @@ export class Service extends Component implements Link.Linkable { * The Amazon Elastic Load Balancer. */ get loadBalancer() { - if ($dev) + if (self.dev) throw new VisibleError( "Cannot access `nodes.loadBalancer` in dev mode.", ); @@ -931,7 +940,7 @@ export class Service extends Component implements Link.Linkable { public getSSTLink() { return { properties: { - url: $dev ? this.devUrl : this._url, + url: this.dev ? this.devUrl : this._url, service: this.service, }, };