pulumi/pulumi-cdk

Use CDK AppStagingSynthesizer for bootstrap resources

Opened this issue ยท 0 comments

Hello!

  • Vote on this issue by adding a ๐Ÿ‘ reaction
  • If you want to implement this feature, comment to let us know (we'll work with you on design, scheduling, etc.)

Issue details

Currently in order to use pulumi-cdk you have to first bootstrap your AWS account using the CDK bootstrap process. This adds an additional dependency and makes it harder for pulumi users to adopt CDK constructs.

There is a new App Staging Synthesizer that does not use the bootstrap process and instead will create the bootstrap resources as needed as part of the deployment.

This would probably entail creating a custom staging stack and might look something like this

export class CustomStagingStack extends pulumiCdk.Stack implements IStagingResources {
    public static factory(options: StagingStackOptions): IStagingResourcesFactory {
        const appId = options.appId
            .toLocaleLowerCase()
            .replace(/[^a-z0-9-]/g, '-')
            .slice(0, 20);
        return {
            obtainStagingResources(stack, context) {
                const app = App.of(stack);
                if (!App.isApp(app)) {
                    throw new Error(`Stack ${stack.stackName} must be part of an App`);
                }
                const stackPrefix = options.stagingStackNamePrefix ?? 'StagingStack';
                const stackName = `${stackPrefix}-${appId}`;
                const stackId = `${stackName}-${context.environmentString}`;
                return new CustomStagingStack(stackId, app, {
                    appId,
                });
            },
        };
    }

    public readonly stagingRepos: { [name: string]: Repository } = {};
    constructor(id: string, app: App, private readonly props: StagingStackProps) {
        console.error('creating CustomStagingStack: ', id);
        super(id, app, {
            ...props,
            props: {
                ...props.props,
                synthesizer: new BootstraplessSynthesizer(),
            },
        });
    }

    public getCreateRepo(asset: DockerImageAssetSource): string {
        if (!asset.assetName) {
            throw new Error("Assets synthesized with AppScopedStagingSynthesizer must include an 'assetName'");
        }
        const repoName = `${this.props.appId}/${asset.assetName}`.toLocaleLowerCase().replace('.', '-');
        if (this.stagingRepos[asset.assetName] === undefined) {
            this.stagingRepos[asset.assetName] = new Repository(repoName, {
                repositoryName: repoName,
                imageTagMutability: 'IMMUTABLE',
            });
        }
        return repoName;
    }
    private bucket?: Bucket;

    public getCreateBucket(): string {
        const stagingBucketName = `pulumi-cdk-${this.props.appId}-staging-${this.account}-${this.region}`;
        if (!this.bucket) {
            this.bucket = new Bucket('PulumiCdkStagingBucket', {
                bucketName: stagingBucketName,
                bucketEncryption: {
                    serverSideEncryptionConfiguration: [
                        {
                            serverSideEncryptionByDefault: {
                                sSEAlgorithm: 'AES256',
                            },
                        },
                    ],
                },
            });
        }
        return stagingBucketName;
    }
    addFile(asset: FileAssetSource): FileStagingLocation {
        const bucketName = this.getCreateBucket();
        return {
            prefix: asset.deployTime ? DEPLOY_TIME_PREFIX : undefined,
            bucketName,
            dependencyStack: this,
        };
    }

    addDockerImage(asset: DockerImageAssetSource): ImageStagingLocation {
        const repoName = this.getCreateRepo(asset);
        return {
            repoName,
            dependencyStack: this,
        };
    }
}

I think the only tricky piece would be figuring out how to make sure all resources that have assets depend on these bootstrap resources.

Affected area/feature