diff --git a/src/constructs/autoscaling/user-data.test.ts b/src/constructs/autoscaling/user-data.test.ts index daa2d8126c..0bc3ba9960 100644 --- a/src/constructs/autoscaling/user-data.test.ts +++ b/src/constructs/autoscaling/user-data.test.ts @@ -22,7 +22,7 @@ describe("GuUserData", () => { const props: GuUserDataProps = { app, distributable: { - bucket: new GuDistributionBucketParameter(stack), + bucket: GuDistributionBucketParameter.getInstance(stack), fileName: "my-app.deb", executionStatement: `dpkg -i /${app}/my-app.deb`, }, @@ -73,7 +73,7 @@ describe("GuUserData", () => { const props: GuUserDataProps = { app, distributable: { - bucket: new GuDistributionBucketParameter(stack), + bucket: GuDistributionBucketParameter.getInstance(stack), fileName: "my-app.deb", executionStatement: `dpkg -i /${app}/my-app.deb`, }, diff --git a/src/constructs/core/parameters/s3.ts b/src/constructs/core/parameters/s3.ts index c313e16dc4..4f31896ee1 100644 --- a/src/constructs/core/parameters/s3.ts +++ b/src/constructs/core/parameters/s3.ts @@ -2,15 +2,28 @@ import type { GuStack } from "../stack"; import { GuStringParameter } from "./base"; export class GuDistributionBucketParameter extends GuStringParameter { - public static parameterName = "DistributionBucketName"; + private static instance: GuDistributionBucketParameter | undefined; - constructor(scope: GuStack, id: string = GuDistributionBucketParameter.parameterName) { - super(scope, id, { + // eslint-disable-next-line custom-rules/valid-constructors -- TODO be better + private constructor(scope: GuStack) { + super(scope, "DistributionBucketName", { description: "SSM parameter containing the S3 bucket name holding distribution artifacts", default: "/account/services/artifact.bucket", fromSSM: true, }); } + + public static getInstance(stack: GuStack): GuDistributionBucketParameter { + // Resources can only live in the same App so return a new instance where necessary. + // See https://github.com/aws/aws-cdk/blob/0ea4b19afd639541e5f1d7c1783032ee480c307e/packages/%40aws-cdk/core/lib/private/refs.ts#L47-L50 + const isSameStack = this.instance?.node.root === stack.node.root; + + if (!this.instance || !isSameStack) { + this.instance = new GuDistributionBucketParameter(stack); + } + + return this.instance; + } } export class GuPrivateConfigBucketParameter extends GuStringParameter { diff --git a/src/constructs/iam/policies/s3-get-object.test.ts b/src/constructs/iam/policies/s3-get-object.test.ts index 8c1a749c59..60be3bac52 100644 --- a/src/constructs/iam/policies/s3-get-object.test.ts +++ b/src/constructs/iam/policies/s3-get-object.test.ts @@ -82,7 +82,7 @@ describe("The GuGetDistributablePolicy construct", () => { const json = SynthUtils.toCloudFormation(stack) as SynthedStack; const parameterKeys = Object.keys(json.Parameters); - const expectedKeys = ["Stage", GuDistributionBucketParameter.parameterName]; + const expectedKeys = ["Stage", GuDistributionBucketParameter.getInstance(stack).id]; expect(parameterKeys).toEqual(expectedKeys); expect(json.Parameters.DistributionBucketName).toEqual({ diff --git a/src/constructs/iam/policies/s3-get-object.ts b/src/constructs/iam/policies/s3-get-object.ts index f5d70c5769..ec6a4f17ee 100644 --- a/src/constructs/iam/policies/s3-get-object.ts +++ b/src/constructs/iam/policies/s3-get-object.ts @@ -24,7 +24,7 @@ export class GuGetDistributablePolicy extends GuGetS3ObjectsPolicy { const path = [scope.stack, scope.stage, props.app, "*"].join("/"); super(scope, AppIdentity.suffixText(props, "GetDistributablePolicy"), { ...props, - bucketName: new GuDistributionBucketParameter(scope).valueAsString, + bucketName: GuDistributionBucketParameter.getInstance(scope).valueAsString, paths: [path], }); AppIdentity.taggedConstruct(props, this);