Creating Modular Structure in Terraform CDK: How Can I Reuse the AwsProvider in Multiple Modules?

How can I structure my Terraform CDK code in TypeScript to create a simple VPC using multiple files and classes for each building block? I am looking for a way to organize my code in a more modular and maintainable way.

This is my main code:

import { App } from 'cdktf'; // import the App class from the cdktf library
import { RegionStack } from './lib/region-stack'; // import the RegionStack class from the './lib/region-stack' module

// Create a new App object
const app = new App();

// Create a new RegionStack object and pass it the app object, a string identifier, and a configuration object
new RegionStack(app, `aws`, {
  environment: 'dev', // field for the environment in which the region stack will be created
  region: 'eu-south-1', // field for the AWS region in which the region stack will be created
});

// Synthesize the CloudFormation template for the AWS resources specified in the code
app.synth();

I will call the RegionStack class:

import { Construct } from 'constructs';
import { TerraformStack } from 'cdktf';
import { VpcStack } from './vpc-stack';
import { AwsProvider } from '@cdktf/provider-aws/lib/provider';

// Interface for the RegionStackConfig object
interface RegionStackConfig {
  environment: string; // required field for the configuration object
  region?: string; // optional field for the configuration object
}

// The RegionStack class represents a stack of AWS resources for creating resources in a region
export class RegionStack extends TerraformStack {
  // Constructor for the RegionStack class
  // - scope: the Construct object that represents the scope for the region stack
  // - id: a string that is used to identify the region stack
  // - config: a RegionStackConfig object that contains configuration information for the region stack
  constructor(scope: Construct, id: string, config: RegionStackConfig) {
    // Call the parent constructor
    super(scope, id);

    // Create a new AwsProvider object and pass it the region field of the config object as the region field in its configuration object
    new AwsProvider(this, 'AWS', { region: config.region });

    // Create a new VpcStack object and pass it a configuration object with the name, cidr_block, and environment fields
    new VpcStack(this, 'test', {
      name: 'test',
      cidr_block: '10.0.0.0/16',
      environment: config.environment,
    });
  }
}

and then the VpcStack class:

import { Construct } from 'constructs';
import { TerraformStack } from 'cdktf';
import { Vpc } from '@cdktf/provider-aws/lib/vpc';

// The VpcStack class represents a stack of AWS resources for creating a VPC
export class VpcStack extends TerraformStack {
  // property to hold the VPC object
  vpc: any;
  // property to hold the Internet Gateway object
  InetGw: any;

  // Constructor for the VpcStack class
  // - scope: the Construct object that represents the scope for the VPC stack
  // - id: a string that is used to identify the VPC stack
  // - config: an object that contains configuration information for the VPC stack, including the cidr_block and name fields
  constructor(scope: Construct, id: string, config: any) {
    // Call the parent constructor
    super(scope, id);

    // Create a new Vpc object and assign it to the vpc property of the VpcStack object
    // The Vpc object is created with the cidr_block and enable_dns_hostnames fields from the config object
    this.vpc = new Vpc(this, `vpc${config.name}`, {
      cidrBlock: config.cidr_block,
      enableDnsHostnames: true,
    });
  }
}

and this is the error what I get:

ubuntu@ip-172-31-16-130:~/CloudServiceTree$ cdktf deploy

⠴  Synthesizing
[2023-01-01T15:41:54.338] [ERROR] default - /home/ubuntu/CloudServiceTree/node_modules/cdktf/lib/terraform-stack.ts:342
      throw new Error(
            ^

[2023-01-01T15:41:54.340] [ERROR] default - Error: Validation failed with the following errors:
  [aws/test] Found resources without a matching provider construct. Please make sure to add provider constructs [e.g. new RandomProvider(...)] to your stack 'test' for the following providers: aws
    at RegionStack.runAllValidations (/home/ubuntu/CloudServiceTree/node_modules/cdktf/lib/terraform-stack.ts:342:13)
    at StackSynthesizer.synthesize (/home/ubuntu/CloudServiceTree/node_modules/cdktf/lib/synthesize/synthesizer.ts:30:18)
    at /home/ubuntu/CloudServiceTree/node_modules/cdktf/lib/app.ts:129:49
    at Array.forEach (<anonymous>)
    at App.synth (/home/ubuntu/CloudServiceTree/node_modules/cdktf/lib/app.ts:129:12)
    at Object.<anonymous> (/home/ubuntu/CloudServiceTree/main.ts:14:5)
    at Module._compile (node:internal/modules/cjs/loader:1120:14)
    at Module.m._compile (/home/ubuntu/CloudServiceTree/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
ERROR: cdktf encountered an error while synthesizing

Synth command: npx ts-node main.ts
Error:         non-zero exit code 1

Question: I am using the Terraform CDK with TypeScript and I am trying to pass the AwsProvider to my sub-classes. However, if I create an AwsProvider object in each of my sub-classes, I will end up with multiple stacks. How can I avoid this and pass the AwsProvider object to all of my sub-classes while still maintaining a clean and modular structure?

If you extend Construct rather than TerraformStack you should get the desired effect.
You can directly add an instance of your RegionConstruct to a vanilla TerraformStack instance, but it’s also possible to still have it as a RegionStack and only change the VpcStack.

1 Like

thanks! Simple mistake but now I understand the concept a little bit more :slight_smile: