AWS Service Catalog - Passing Parameters to a Stack within a ProductStack

659 Views Asked by At

I have a Service Catalog portfolio that has a product deployed to it. The product is created from a basic AWS CDK Construct (S3 Bucket). I want to extend the product's usefulness by adding a parameter that a user can populate when consuming the product. This seems easy when I'm using a Construct in the ProductStack:

export class ScConstructDemoStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);

        const portfolio = new Portfolio(this, 'myportfolio', {
            displayName: 'Test Portfolio',
            providerName: 'Acme Corp'
        });

        const bucketConstructCFProduct = new CloudFormationProduct(this, 'SCProduct_construct_bucket', {
            productName: 'S3FromConstructWithParam',
            owner: 'me',
            productVersions: [{
                cloudFormationTemplate: CloudFormationTemplate.fromProductStack(new myS3ConstructProduct(this, 'S3FromConstructWithParam')),
                productVersionName: 'v1',
                description: 'Quick S3 Bucket'
            }]
        });

        portfolio.addProduct(bucketConstructCFProduct);

    }
}

class myS3ConstructProduct extends ProductStack {
    constructor(scope: Construct, id: string) {
        super(scope, id);

        const uploadBucketName = new CfnParameter(this, 'bucketNameParam', {
            type: 'String',
            description: 'Name of S3 Bucket',
        });

        const bucket = new Bucket(this, 'construct-bucket', {
            bucketName: uploadBucketName.valueAsString
        });
    }
}

This works.

However, I also have a CDK Stack that I want to add to Service Catalog. This stack is defined with a CfnParameter that I would expect to be available when provisioning the Product.

export class ScCdkDemoStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const portfolio = new Portfolio(this, 'myportfolio', {
      displayName: 'Test Portfolio',
      providerName: 'Acme Corp'
    });

    const bucketCFProduct = new CloudFormationProduct(this, 'SCProduct_bucket', {
      productName: 'S3WithParam',
      owner: 'me',
      productVersions: [{
        cloudFormationTemplate: CloudFormationTemplate.fromProductStack(new myS3StackProduct(this, 'S3WithParam')),
        productVersionName: 'v1',
        description: 'Quick S3 Bucket'
      }]
    });

    portfolio.addProduct(bucketCFProduct);

  }
}

interface bucketProps extends StackProps {
  readonly bucketName: string
}

class myS3Stack extends Stack {
  constructor(scope: Construct, id: string, props: bucketProps) {
    super(scope, id, props);
    const bucket = new Bucket(this, 'mybucket', {
      bucketName: props.bucketName
    });
  }
}

class myS3StackProduct extends ProductStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);
    const uploadBucketName = new CfnParameter(this, 'bucketNameParam', {
      type: 'String',
      description: 'Name of S3 Bucket',
    });

    const deployStack = new myS3Stack(this, 's3StackProduct', {
      bucketName: uploadBucketName.valueAsString
    })
  }
}

When I synth this CDK code I get an exception which I read as something (probably the CfnParameter) is missing / undefined when the Stack is created.

Error: Artifact StackDemoStackS3WithParams3StackProduct88DBCA37 depends on non-existing artifact StackDemoStackS3WithParamDFB09CA4
    at /Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/cx-api/lib/cloud-artifact.js:1:1310
    at Array.map (<anonymous>)
    at CloudFormationStackArtifact.get dependencies [as dependencies] (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/cx-api/lib/cloud-artifact.js:1:1244)
    at CloudAssembly.validateDeps (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/cx-api/lib/cloud-assembly.js:1:3861)
    at new CloudAssembly (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/cx-api/lib/cloud-assembly.js:1:1219)
    at CloudAssemblyBuilder.buildAssembly (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/cx-api/lib/cloud-assembly.js:1:6098)
    at Object.synthesize (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:1:800)
    at App.synth (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/core/lib/stage.js:1:1866)
    at process.<anonymous> (/Users/ben/code/sc_cdk_demo/node_modules/aws-cdk-lib/core/lib/app.js:1:1164)
    at Object.onceWrapper (node:events:642:26)

Subprocess exited with error 1

Can anyone tell me why I can't pass a Parameter to a Stack like this or point out my daft mistake?

Many Thanks

nb. I'm new to TypeScript, the CDK repo this code lives in is here

1

There are 1 best solutions below

1
Drēm Darios On

The error looks like there is an issue with dependencies between stacks. Your myS3StackProduct stack doesn't really need to be dependent on the myS3Stack. Instead, you can define the S3 bucket and parameter in the product stack like this:

class myS3StackProduct extends ProductStack {
    def __init__(self, scope: Construct, id: str):
        super().__init__(scope, id)

        upload_bucket_name = CfnParameter(self, "uploadBucketName", type="String",
                                          description="The name of the Amazon S3 bucket where uploaded files will be stored.")
        bucket = s3.Bucket(self, "myBucket",
                           bucket_name=upload_bucket_name.value_as_string)
}

This will remove myS3StackProduct's dependency on myS3Stack.