Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@
"@cdktf/provider-aws": "^21.22.1",
"@cdktf/provider-azurerm": "^14.23.1",
"@cdktf/provider-cloudflare": "^13.9.1",
"@pulumi/aws": "^7.14.0",
"@pulumi/azure-native": "^3.11.0",
"@pulumi/cloudflare": "^6.11.0",
"@pulumi/command": "^1.1.3",
"@pulumi/pulumi": "^3.213.0",
"@pulumi/std": "^2.2.0",
"@types/lodash": "^4.17.21",
"@types/node": "^25.0.3",
"@types/uuid": "^11.0.0",
Expand Down
1,660 changes: 1,651 additions & 9 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions src/lib/cloudflare/common/constants.ts

This file was deleted.

83 changes: 13 additions & 70 deletions src/lib/cloudflare/common/construct.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { AwsProvider } from '@cdktf/provider-aws/lib/provider/index.js'
import { CloudflareProvider } from '@cdktf/provider-cloudflare/lib/provider/index.js'
import { AzurermProvider } from '@cdktf/provider-azurerm/lib/provider/index.js'
import { AzurermBackend, S3Backend, TerraformStack, TerraformVariable } from 'cdktf'
import { Construct } from 'constructs'
import { Provider as CloudflareProvider } from '@pulumi/cloudflare'
import { ComponentResource, ComponentResourceOptions, Config } from '@pulumi/pulumi'
import { isDevStage, isPrdStage, isTestStage, isUatStage } from '../../common/index.js'
import {
CloudflareAccessManager,
Expand All @@ -16,12 +13,13 @@ import {
CloudflareWorkerManager,
CloudflareZoneManager,
} from '../services/index.js'
import { RemoteBackend } from './constants.js'
import { CommonCloudflareStackProps } from './types.js'

export class CommonCloudflareConstruct extends TerraformStack {
export class CommonCloudflareConstruct extends ComponentResource {
declare props: CommonCloudflareStackProps
declare options?: ComponentResourceOptions
id: string
config: Config
fullyQualifiedDomainName: string
accessManager: CloudflareAccessManager
apiShieldManager: CloudflareApiShieldManager
Expand All @@ -33,15 +31,13 @@ export class CommonCloudflareConstruct extends TerraformStack {
ruleSetManager: CloudflareRuleSetManager
workerManager: CloudflareWorkerManager
zoneManager: CloudflareZoneManager
awsProvider: AwsProvider
s3Backend: S3Backend
azurermProvider: AzurermProvider
azurermBackend: AzurermBackend
provider: CloudflareProvider

constructor(scope: Construct, id: string, props: CommonCloudflareStackProps) {
super(scope, id)
constructor(name: string, props: CommonCloudflareStackProps, options?: ComponentResourceOptions) {
super(`custom:cloudflare:Construct:${name}`, name, props, options)
this.props = props
this.id = id
this.options = options
this.id = name

this.accessManager = new CloudflareAccessManager()
this.apiShieldManager = new CloudflareApiShieldManager()
Expand All @@ -54,11 +50,10 @@ export class CommonCloudflareConstruct extends TerraformStack {
this.workerManager = new CloudflareWorkerManager()
this.zoneManager = new CloudflareZoneManager()

/* initialise config */
this.config = new Config()
this.determineFullyQualifiedDomain()
this.determineAccountId()
this.determineApiToken()
this.determineRemoteBackend()
new CloudflareProvider(this, `${this.id}-provider`, this.props)
this.provider = new CloudflareProvider(`${this.id}-provider`, this.props, options)
}

/**
Expand All @@ -70,58 +65,6 @@ export class CommonCloudflareConstruct extends TerraformStack {
: this.props.domainName
}

/**
* @summary Determine the account id based on the cdktf.json context
*/
protected determineAccountId() {
this.props.accountId = new TerraformVariable(this, `accountId`, {}).stringValue
}

/**
* @summary Determine the api token based on the cdktf.json context
*/
protected determineApiToken() {
this.props.apiToken = new TerraformVariable(this, `apiToken`, {}).stringValue
}

protected determineRemoteBackend() {
const debug = this.node.tryGetContext('debug')

switch (this.props.remoteBackend?.type) {
case RemoteBackend.s3:
this.awsProvider = new AwsProvider(this, `${this.id}-aws-provider`, {
profile: process.env.AWS_PROFILE,
region: this.props.remoteBackend.region,
})
this.s3Backend = new S3Backend(this, {
bucket: this.props.remoteBackend.bucketName,
dynamodbTable: this.props.remoteBackend.tableName,
key: `${this.id}`,
profile: process.env.AWS_PROFILE,
region: this.props.remoteBackend.region,
})
break
case RemoteBackend.azurerm:
this.azurermProvider = new AzurermProvider(this, `${this.id}-azurerm-provider`, {
features: [{}],
subscriptionId: this.props.remoteBackend.subscriptionId,
})
this.azurermBackend = new AzurermBackend(this, {
storageAccountName: this.props.remoteBackend.storageAccountName,
containerName: this.props.remoteBackend.containerName,
key: `${this.id}`,
subscriptionId: this.props.remoteBackend.subscriptionId,
resourceGroupName: this.props.remoteBackend.resourceGroupName,
})
break
case RemoteBackend.local:
if (debug) console.debug(`Using local backend for ${this.id}`)
break
default:
break
}
}

/**
* @summary Utility method to determine if the initialisation is in development (dev) stage
* This is determined by the stage property injected via cdk context
Expand Down
1 change: 0 additions & 1 deletion src/lib/cloudflare/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './constants.js'
export * from './construct.js'
export * from './stack.js'
export * from './types.js'
127 changes: 58 additions & 69 deletions src/lib/cloudflare/common/stack.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import fs from 'fs'
import { CommonCloudflareConstruct } from './construct.js'
import { CommonCloudflareStackProps } from './types.js'

import { ComponentResource, ComponentResourceOptions, Config } from '@pulumi/pulumi'
import appRoot from 'app-root-path'
import { TerraformStack } from 'cdktf'
import { Construct } from 'constructs'
import fs from 'fs'
import _ from 'lodash'
import path from 'path'
import { isDevStage } from '../../common/index.js'
import { CommonCloudflareConstruct } from './construct.js'
import { CommonCloudflareStackProps } from './types.js'

/**
* @classdesc Common stack to use as a base for all higher level constructs.
Expand All @@ -21,123 +19,114 @@ import { isDevStage } from '../../common/index.js'
* }
* }
*/
export class CommonCloudflareStack extends TerraformStack {
export class CommonCloudflareStack extends ComponentResource {
construct: CommonCloudflareConstruct
props: CommonCloudflareStackProps
config: Config

constructor(parent: Construct, name: string, props: CommonCloudflareStackProps) {
super(parent, name)

/* determine extra cdk contexts */
this.determineExtraContexts()

/* determine extra cdk stage contexts */
this.determineStageContexts()
constructor(name: string, props: CommonCloudflareStackProps, options?: ComponentResourceOptions) {
super(`custom:cloudflare:Stack:${name}`, name, props, options)

/* initialise config */
this.config = new Config()
this.props = this.determineConstructProps(props)
}

/**
* @summary Method to determine the core CDK construct properties injected via context cdktf.json
* @summary Method to determine the core CDK construct properties injected via context
* @param props The stack properties
* @returns The stack properties
*/
protected determineConstructProps(props: CommonCloudflareStackProps) {
const stage = this.node.tryGetContext('stage')
let projectProps: CommonCloudflareStackProps = props
if (!projectProps) {
const projectPropsPath = path.join(appRoot.path, 'pulumi.json')
if (!fs.existsSync(projectPropsPath)) throw `Context properties unavailable in path:${projectPropsPath}`

const projectPropsBuffer = fs.readFileSync(projectPropsPath)
projectProps = JSON.parse(projectPropsBuffer.toString('utf-8'))
}

return {
accountId: this.node.tryGetContext('accountId'),
apiToken: this.node.tryGetContext('apiToken'),
domainName: this.node.tryGetContext('domainName'),
extraContexts: this.node.tryGetContext('extraContexts'),
features: this.node.tryGetContext('features'),
name: this.node.tryGetContext('resourceGroupName'),
skipStageForARecords: this.node.tryGetContext('skipStageForARecords'),
stage: stage,
subDomain: this.node.tryGetContext('subDomain'),
accountId: projectProps.accountId,
apiToken: projectProps.apiToken,
domainName: projectProps.domainName,
extraContexts: projectProps.extraContexts,
name: projectProps.resourceGroupName ?? projectProps.name,
skipStageForARecords: projectProps.skipStageForARecords,
stage: projectProps.stage,
stageContextPath: projectProps.stageContextPath,
subDomain: projectProps.subDomain,
...this.determineExtraContexts(props),
...this.determineStageContexts(props),
}
}

/**
* @summary Method to determine extra cdk contexts apart from the main cdktf.json
* @summary Method to determine extra cdk contexts apart from the main context
* - Sets the properties from the extra contexts into cdk node context
* - Primary use is to have layered config in separate files to enable easier maintenance and readability
*/
protected determineExtraContexts() {
const extraContexts = this.node.tryGetContext('extraContexts')
const debug = this.node.tryGetContext('debug')

if (!extraContexts) {
if (debug) console.debug(`No additional contexts provided. Using default context properties from cdktf.json`)
return
protected determineExtraContexts(props: CommonCloudflareStackProps) {
if (!props.extraContexts) {
if (props.debug) console.debug(`No additional contexts provided. Using default context properties`)
return {}
}

_.forEach(extraContexts, (context: string) => {
let extraContextProps: Record<string, any> = {}
_.forEach(props.extraContexts, (context: string) => {
const extraContextPath = path.join(appRoot.path, context)

/* scenario where extra context is configured in cdk.json but absent in file system */
if (!fs.existsSync(extraContextPath)) throw `Extra context properties unavailable in path:${extraContextPath}`

/* read the extra properties */
const extraContextPropsBuffer = fs.readFileSync(extraContextPath)
if (debug) console.debug(`Adding additional contexts provided in ${extraContextPath}`)
if (props.debug) console.debug(`Adding additional contexts provided in ${extraContextPath}`)

/* parse as JSON properties */
const extraContextProps = JSON.parse(extraContextPropsBuffer.toString('utf-8'))

/* set each of the property into the cdk node context */
_.keys(extraContextProps).forEach((propKey: any) => {
this.node.setContext(propKey, extraContextProps[propKey])
})
extraContextProps = {
...extraContextProps,
...JSON.parse(extraContextPropsBuffer.toString('utf-8')),
}
})
return extraContextProps
}

/**
* @summary Method to determine extra cdk stage contexts apart from the main cdktf.json
* @summary Method to determine extra cdk stage contexts apart from the main context
* - Sets the properties from the extra stage contexts into cdk node context
* - Primary use is to have layered config for each environment which is injected into the context
*/
protected determineStageContexts() {
const stage = process.env.STAGE ?? this.node.tryGetContext('stage')
const stageContextPath = this.node.tryGetContext('stageContextPath') || 'cdkEnv'
const stageContextFilePath = path.join(appRoot.path, stageContextPath, `${stage}.json`)
const debug = this.node.tryGetContext('debug')

if (isDevStage(stage)) {
if (debug) console.debug(`Development stage. Using default stage context properties`)
protected determineStageContexts(props: CommonCloudflareStackProps) {
const stageContextFilePath = path.join(appRoot.path, props.stageContextPath ?? 'cdkEnv', `${props.stage}.json`)

if (isDevStage(props.stage)) {
if (props.debug) console.debug(`Development stage. Using default stage context properties`)
}

/* alert default context usage when extra stage config is missing */
if (!fs.existsSync(stageContextFilePath)) {
if (debug) console.debug(`Stage specific context properties unavailable in path:${stageContextFilePath}`)
if (debug) console.debug(`Using default stage context properties for ${stage} stage`)
return
if (props.debug) console.debug(`Stage specific context properties unavailable in path:${stageContextFilePath}`)
if (props.debug) console.debug(`Using default stage context properties for ${props.stage} stage`)
return {}
}

/* read the extra properties */
const stageContextPropsBuffer = fs.readFileSync(stageContextFilePath)
if (debug) console.debug(`Adding additional stage contexts provided in ${stageContextFilePath}`)
if (props.debug) console.debug(`Adding additional stage contexts provided in ${stageContextFilePath}`)

/* parse as JSON properties */
const stageContextProps = JSON.parse(stageContextPropsBuffer.toString('utf-8'))

/* set each of the property into the cdk node context */
_.keys(stageContextProps).forEach((propKey: any) => {
/* handle object, array properties */
if (typeof stageContextProps[propKey] === 'object' && !Array.isArray(stageContextProps[propKey])) {
this.node.setContext(propKey, _.merge(this.node.tryGetContext(propKey), stageContextProps[propKey]))
} else {
/* handle all other primitive properties */
this.node.setContext(propKey, stageContextProps[propKey])
}
})
return JSON.parse(stageContextPropsBuffer.toString('utf-8'))
}

/**
* @summary Determine the fully qualified domain name based on domainName & subDomain
*/
protected fullyQualifiedDomain() {
const domainName = this.node.tryGetContext('domainName')
const subDomain = this.node.tryGetContext('subDomain')
const domainName = this.props.domainName
const subDomain = this.props.subDomain

return subDomain ? `${subDomain}.${domainName}` : domainName
}
}
17 changes: 2 additions & 15 deletions src/lib/cloudflare/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import { CloudflareProviderConfig } from '@cdktf/provider-cloudflare/lib/provider/index.js'
import { BaseProps } from '../../common/index.js'
import { RemoteBackend } from './constants.js'

export interface RemoteBackendProps {
bucketName: string
region: string
tableName: string
type: RemoteBackend
storageAccountName: string
containerName: string
subscriptionId: string
resourceGroupName: string
}

/**
*/
export interface CommonCloudflareStackProps extends BaseProps, CloudflareProviderConfig {
export interface CommonCloudflareStackProps extends BaseProps {
accountId: string
apiToken: string
remoteBackend?: RemoteBackendProps
resourceGroupName?: string
useExistingZone?: boolean
}
Loading