diff --git a/API.md b/API.md index 55d1600..1151541 100644 --- a/API.md +++ b/API.md @@ -2625,6 +2625,7 @@ const bashCDKPipelineOptions: BashCDKPipelineOptions = { ... } | featureStages | StageOptions | This specifies details for feature stages. | | independentStages | IndependentStage[] | This specifies details for independent stages. | | personalStage | StageOptions | This specifies details for a personal stage. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. | | pkgNamespace | string | This field determines the NPM namespace to be used when packaging CDK cloud assemblies. | | postSynthCommands | string[] | *No description.* | | postSynthSteps | PipelineStep[] | *No description.* | @@ -2725,6 +2726,19 @@ This specifies details for a personal stage. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* the project name if the project has a parent (monorepo subproject), otherwise no prefix + +A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. + +--- + ##### `pkgNamespace`Optional ```typescript @@ -2844,6 +2858,7 @@ const bashDriftDetectionWorkflowOptions: BashDriftDetectionWorkflowOptions = { . | --- | --- | --- | | stages | DriftDetectionStageOptions[] | Drift detection configurations for different environments. | | name | string | Name of the workflow. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. | | schedule | string | Cron schedule for drift detection. | | scriptPath | string | Path to the output script. | @@ -2874,6 +2889,19 @@ Name of the workflow. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* no prefix + +A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. + +--- + ##### `schedule`Optional ```typescript @@ -3030,6 +3058,7 @@ const cDKPipelineOptions: CDKPipelineOptions = { ... } | featureStages | StageOptions | This specifies details for feature stages. | | independentStages | IndependentStage[] | This specifies details for independent stages. | | personalStage | StageOptions | This specifies details for a personal stage. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. | | pkgNamespace | string | This field determines the NPM namespace to be used when packaging CDK cloud assemblies. | | postSynthCommands | string[] | *No description.* | | postSynthSteps | PipelineStep[] | *No description.* | @@ -3130,6 +3159,19 @@ This specifies details for a personal stage. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* the project name if the project has a parent (monorepo subproject), otherwise no prefix + +A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. + +--- + ##### `pkgNamespace`Optional ```typescript @@ -4063,6 +4105,7 @@ const driftDetectionWorkflowOptions: DriftDetectionWorkflowOptions = { ... } | --- | --- | --- | | stages | DriftDetectionStageOptions[] | Drift detection configurations for different environments. | | name | string | Name of the workflow. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. | | schedule | string | Cron schedule for drift detection. | --- @@ -4092,6 +4135,19 @@ Name of the workflow. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* no prefix + +A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. + +--- + ##### `schedule`Optional ```typescript @@ -4296,6 +4352,7 @@ const githubCDKPipelineOptions: GithubCDKPipelineOptions = { ... } | featureStages | StageOptions | This specifies details for feature stages. | | independentStages | IndependentStage[] | This specifies details for independent stages. | | personalStage | StageOptions | This specifies details for a personal stage. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. | | pkgNamespace | string | This field determines the NPM namespace to be used when packaging CDK cloud assemblies. | | postSynthCommands | string[] | *No description.* | | postSynthSteps | PipelineStep[] | *No description.* | @@ -4400,6 +4457,19 @@ This specifies details for a personal stage. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* the project name if the project has a parent (monorepo subproject), otherwise no prefix + +A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. + +--- + ##### `pkgNamespace`Optional ```typescript @@ -4577,6 +4647,7 @@ const gitHubDriftDetectionWorkflowOptions: GitHubDriftDetectionWorkflowOptions = | --- | --- | --- | | stages | DriftDetectionStageOptions[] | Drift detection configurations for different environments. | | name | string | Name of the workflow. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. | | schedule | string | Cron schedule for drift detection. | | createIssues | boolean | Whether to create issues on drift detection. | | permissions | {[ key: string ]: string} | Additional permissions for GitHub workflow. | @@ -4608,6 +4679,19 @@ Name of the workflow. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* no prefix + +A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. + +--- + ##### `schedule`Optional ```typescript @@ -4939,6 +5023,7 @@ const gitlabCDKPipelineOptions: GitlabCDKPipelineOptions = { ... } | featureStages | StageOptions | This specifies details for feature stages. | | independentStages | IndependentStage[] | This specifies details for independent stages. | | personalStage | StageOptions | This specifies details for a personal stage. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. | | pkgNamespace | string | This field determines the NPM namespace to be used when packaging CDK cloud assemblies. | | postSynthCommands | string[] | *No description.* | | postSynthSteps | PipelineStep[] | *No description.* | @@ -5041,6 +5126,19 @@ This specifies details for a personal stage. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* the project name if the project has a parent (monorepo subproject), otherwise no prefix + +A unique name for this pipeline, used as a prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. + +--- + ##### `pkgNamespace`Optional ```typescript @@ -5184,6 +5282,7 @@ const gitLabDriftDetectionWorkflowOptions: GitLabDriftDetectionWorkflowOptions = | --- | --- | --- | | stages | DriftDetectionStageOptions[] | Drift detection configurations for different environments. | | name | string | Name of the workflow. | +| pipelineName | string | A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. | | schedule | string | Cron schedule for drift detection. | | image | string | Docker image to use for drift detection. | | runnerTags | string[] | GitLab runner tags. | @@ -5215,6 +5314,19 @@ Name of the workflow. --- +##### `pipelineName`Optional + +```typescript +public readonly pipelineName: string; +``` + +- *Type:* string +- *Default:* no prefix + +A unique name for this pipeline, used as a prefix for workflow files and artifact names to prevent collisions in monorepos. + +--- + ##### `schedule`Optional ```typescript diff --git a/src/awscdk/base.ts b/src/awscdk/base.ts index edd8a37..9ca9cf4 100644 --- a/src/awscdk/base.ts +++ b/src/awscdk/base.ts @@ -122,6 +122,14 @@ export interface IamRoleConfig { */ export interface CDKPipelineOptions { + /** + * A unique name for this pipeline, used as a prefix for workflow files, + * concurrency groups, and artifact names to prevent collisions in monorepos. + * + * @default - the project name if the project has a parent (monorepo subproject), otherwise no prefix + */ + readonly pipelineName?: string; + /** * the name of the branch to deploy from * @default main @@ -203,6 +211,9 @@ export abstract class CDKPipeline extends Component { public readonly stackPrefix: string; public readonly branchName: string; + /** Prefix for workflow files, concurrency groups, and artifact names to prevent collisions in monorepos. */ + protected readonly namePrefix: string; + constructor(protected app: awscdk.AwsCdkTypeScriptApp, protected baseOptions: CDKPipelineOptions) { super(app); @@ -215,6 +226,8 @@ export abstract class CDKPipeline extends Component { // ); this.project.gitignore.exclude('/cdk-outputs-*.json'); + const pipelineName = baseOptions.pipelineName ?? (app.parent ? app.name : undefined); + this.namePrefix = pipelineName ? `${pipelineName}-` : ''; this.stackPrefix = baseOptions.stackPrefix ?? app.name; this.branchName = baseOptions.branchName ?? 'main'; // TODO use defaultReleaseBranch of NodeProject diff --git a/src/awscdk/github.ts b/src/awscdk/github.ts index 25e6bec..fffaf9c 100644 --- a/src/awscdk/github.ts +++ b/src/awscdk/github.ts @@ -83,7 +83,7 @@ export class GithubCDKPipeline extends CDKPipeline { }); // Initialize the deployment workflow on GitHub. - this.deploymentWorkflow = this.app.github!.addWorkflow('deploy'); + this.deploymentWorkflow = this.app.github!.addWorkflow(`${this.namePrefix}deploy`); this.deploymentWorkflow.on({ push: { branches: [this.branchName], @@ -146,7 +146,7 @@ export class GithubCDKPipeline extends CDKPipeline { * Creates a workflow for deploying feature branches when PRs are labeled with 'feature-deployment'. */ private createFeatureDeployWorkflow(): void { - const workflow = this.app.github!.addWorkflow('deploy-feature'); + const workflow = this.app.github!.addWorkflow(`${this.namePrefix}deploy-feature`); workflow.on({ pullRequestTarget: { @@ -161,8 +161,8 @@ export class GithubCDKPipeline extends CDKPipeline { this.provideDeployStep({ name: 'feature', env: this.baseOptions.featureStages!.env }), new CdkOutputsSummaryStep(this.project, { stageName: 'feature' }), new UploadArtifactStep(this.project, { - name: 'cdk-outputs-feature', - path: 'cdk-outputs-feature.json', + name: `${this.namePrefix}cdk-outputs-feature`, + path: `${this.namePrefix}cdk-outputs-feature.json`, }), ].map(s => s.toGithub()); @@ -176,7 +176,7 @@ export class GithubCDKPipeline extends CDKPipeline { idToken: JobPermission.WRITE, }, ...(steps.flatMap(s => s.permissions).filter(p => p != undefined) as JobPermissions[])), concurrency: { - 'group': 'deploy-feature-${{ github.event.pull_request.number }}', + 'group': `${this.namePrefix}deploy-feature-\${{ github.event.pull_request.number }}`, 'cancel-in-progress': false, }, env: { @@ -203,7 +203,7 @@ export class GithubCDKPipeline extends CDKPipeline { * Creates a workflow for destroying feature branches when PRs are closed or unlabeled. */ private createFeatureDestroyWorkflow(): void { - const workflow = this.app.github!.addWorkflow('destroy-feature'); + const workflow = this.app.github!.addWorkflow(`${this.namePrefix}destroy-feature`); workflow.on({ pullRequestTarget: { @@ -233,7 +233,7 @@ export class GithubCDKPipeline extends CDKPipeline { idToken: JobPermission.WRITE, }, ...(steps.flatMap(s => s.permissions).filter(p => p != undefined) as JobPermissions[])), concurrency: { - 'group': 'destroy-feature-${{ github.event.pull_request.number }}', + 'group': `${this.namePrefix}destroy-feature-\${{ github.event.pull_request.number }}`, 'cancel-in-progress': false, }, env: { @@ -265,7 +265,7 @@ export class GithubCDKPipeline extends CDKPipeline { steps.push(this.provideSynthStep()); steps.push(new UploadArtifactStep(this.project, { - name: 'cloud-assembly', + name: `${this.namePrefix}cloud-assembly`, path: `${this.app.cdkConfig.cdkout}/`, })); @@ -307,7 +307,7 @@ export class GithubCDKPipeline extends CDKPipeline { const steps = [ new SimpleCommandStep(this.project, ['git config --global user.name "github-actions" && git config --global user.email "github-actions@github.com"']), new DownloadArtifactStep(this.project, { - name: 'cloud-assembly', + name: `${this.namePrefix}cloud-assembly`, path: `${this.app.cdkConfig.cdkout}/`, }), this.provideInstallStep(), @@ -367,13 +367,13 @@ export class GithubCDKPipeline extends CDKPipeline { this.provideDeployStep(stage), new CdkOutputsSummaryStep(this.project, { stageName: stage.name }), new UploadArtifactStep(this.project, { - name: `cdk-outputs-${stage.name}`, + name: `${this.namePrefix}cdk-outputs-${stage.name}`, path: `cdk-outputs-${stage.name}.json`, }), ].map(s => s.toGithub()); // Create new workflow for deployment - const stageWorkflow = this.app.github!.addWorkflow(`release-${stage.name}`); + const stageWorkflow = this.app.github!.addWorkflow(`${this.namePrefix}release-${stage.name}`); stageWorkflow.on({ workflowDispatch: { inputs: { @@ -392,7 +392,7 @@ export class GithubCDKPipeline extends CDKPipeline { environment: stage.githubEnvironment ?? stage.name, }, concurrency: { - 'group': `deploy-${stage.name}`, + 'group': `${this.namePrefix}deploy-${stage.name}`, 'cancel-in-progress': false, }, env: { @@ -430,14 +430,14 @@ export class GithubCDKPipeline extends CDKPipeline { ) { const steps = [ new DownloadArtifactStep(this.project, { - name: 'cloud-assembly', + name: `${this.namePrefix}cloud-assembly`, path: `${this.app.cdkConfig.cdkout}/`, }), this.provideInstallStep(), this.provideDeployStep(stage), new CdkOutputsSummaryStep(this.project, { stageName: stage.name }), new UploadArtifactStep(this.project, { - name: `cdk-outputs-${stage.name}`, + name: `${this.namePrefix}cdk-outputs-${stage.name}`, path: `cdk-outputs-${stage.name}.json`, }), ].map(s => s.toGithub()); @@ -449,7 +449,7 @@ export class GithubCDKPipeline extends CDKPipeline { environment: stage.githubEnvironment ?? stage.name, }, concurrency: { - 'group': `deploy-${stage.name}`, + 'group': `${this.namePrefix}deploy-${stage.name}`, 'cancel-in-progress': false, }, needs: [`assetUpload${useGithubEnvironmentsForAssetUpload ? `-${stage.name}` : ''}`, ...steps.flatMap(s => s.needs), ...jobDependencies], @@ -491,13 +491,13 @@ export class GithubCDKPipeline extends CDKPipeline { this.provideDeployStep(stage), new CdkOutputsSummaryStep(this.project, { stageName: stage.name }), new UploadArtifactStep(this.project, { - name: `cdk-outputs-${stage.name}`, + name: `${this.namePrefix}cdk-outputs-${stage.name}`, path: `cdk-outputs-${stage.name}.json`, }), ].map(s => s.toGithub()); // Create new workflow for deployment - const stageWorkflow = this.app.github!.addWorkflow(`deploy-${stage.name}`); + const stageWorkflow = this.app.github!.addWorkflow(`${this.namePrefix}deploy-${stage.name}`); stageWorkflow.on({ workflowDispatch: {}, }); @@ -506,7 +506,7 @@ export class GithubCDKPipeline extends CDKPipeline { needs: steps.flatMap(s => s.needs), runsOn: this.options.runnerTags ?? DEFAULT_RUNNER_TAGS, concurrency: { - 'group': `deploy-${stage.name}`, + 'group': `${this.namePrefix}deploy-${stage.name}`, 'cancel-in-progress': false, }, env: { diff --git a/src/awscdk/gitlab.ts b/src/awscdk/gitlab.ts index 9758e60..dd77879 100644 --- a/src/awscdk/gitlab.ts +++ b/src/awscdk/gitlab.ts @@ -97,7 +97,7 @@ export class GitlabCDKPipeline extends CDKPipeline { */ protected setupSnippets() { this.config.addJobs({ - '.artifacts_cdk': { + [`.${this.namePrefix}artifacts_cdk`]: { artifacts: { when: gitlab.CacheWhen.ON_SUCCESS, expireIn: '30 days', @@ -106,7 +106,7 @@ export class GitlabCDKPipeline extends CDKPipeline { paths: ['cdk.out'], }, }, - '.artifacts_cdkdeploy': { + [`.${this.namePrefix}artifacts_cdkdeploy`]: { artifacts: { when: gitlab.CacheWhen.ON_SUCCESS, expireIn: '30 days', @@ -115,7 +115,7 @@ export class GitlabCDKPipeline extends CDKPipeline { paths: ['cdk-outputs-*.json'], }, }, - '.aws_base': { + [`.${this.namePrefix}aws_base`]: { image: { name: this.jobImage }, idTokens: { AWS_TOKEN: { @@ -150,8 +150,8 @@ export class GitlabCDKPipeline extends CDKPipeline { this.config.addStages('synth'); this.config.addJobs({ - synth: { - extends: ['.aws_base', '.artifacts_cdk', ...gitlabSteps.flatMap(s => s.extensions)], + [`${this.namePrefix}synth`]: { + extends: [`.${this.namePrefix}aws_base`, `.${this.namePrefix}artifacts_cdk`, ...gitlabSteps.flatMap(s => s.extensions)], needs: gitlabSteps.flatMap(s => s.needs), stage: 'synth', tags: this.options.runnerTags?.synth ?? this.options.runnerTags?.default, @@ -181,11 +181,11 @@ export class GitlabCDKPipeline extends CDKPipeline { this.config.addStages('publish_assets'); this.config.addJobs({ - publish_assets: { - extends: ['.aws_base', ...gitlabSteps.flatMap(s => s.extensions)], + [`${this.namePrefix}publish_assets`]: { + extends: [`.${this.namePrefix}aws_base`, ...gitlabSteps.flatMap(s => s.extensions)], stage: 'publish_assets', tags: this.options.runnerTags?.assetPublishing ?? this.options.runnerTags?.default, - needs: [{ job: 'synth', artifacts: true }, ...gitlabSteps.flatMap(s => s.needs)], + needs: [{ job: `${this.namePrefix}synth`, artifacts: true }, ...gitlabSteps.flatMap(s => s.needs)], script: gitlabSteps.flatMap(s => s.commands), variables: gitlabSteps.reduce((acc, step) => ({ ...acc, ...step.env }), {}), }, @@ -216,24 +216,24 @@ export class GitlabCDKPipeline extends CDKPipeline { this.config.addStages(stage.name); this.config.addJobs({ ...(stage.diffType !== CdkDiffType.NONE) && { - [`diff-${stage.name}`]: { - extends: ['.aws_base', ...diffSteps.flatMap(s => s.extensions)], + [`${this.namePrefix}diff-${stage.name}`]: { + extends: [`.${this.namePrefix}aws_base`, ...diffSteps.flatMap(s => s.extensions)], stage: stage.name, tags: this.options.runnerTags?.diff?.[stage.name] ?? this.options.runnerTags?.deployment?.[stage.name] ?? this.options.runnerTags?.default, only: { refs: [this.branchName], }, needs: [ - { job: 'synth', artifacts: true }, - { job: 'publish_assets' }, + { job: `${this.namePrefix}synth`, artifacts: true }, + { job: `${this.namePrefix}publish_assets` }, ...diffSteps.flatMap(s => s.needs), ], script: diffSteps.flatMap(s => s.commands), variables: diffSteps.reduce((acc, step) => ({ ...acc, ...step.env }), {}), }, }, - [`deploy-${stage.name}`]: { - extends: ['.aws_base', '.artifacts_cdkdeploy', ...deploySteps.flatMap(s => s.extensions)], + [`${this.namePrefix}deploy-${stage.name}`]: { + extends: [`.${this.namePrefix}aws_base`, `.${this.namePrefix}artifacts_cdkdeploy`, ...deploySteps.flatMap(s => s.extensions)], stage: stage.name, tags: this.options.runnerTags?.deployment?.[stage.name] ?? this.options.runnerTags?.default, ...stage.manualApproval && { @@ -243,9 +243,9 @@ export class GitlabCDKPipeline extends CDKPipeline { refs: [this.branchName], }, needs: [ - { job: 'synth', artifacts: true }, - { job: 'publish_assets' }, - ...(stage.diffType !== CdkDiffType.NONE) ? [{ job: `diff-${stage.name}` }] : [], + { job: `${this.namePrefix}synth`, artifacts: true }, + { job: `${this.namePrefix}publish_assets` }, + ...(stage.diffType !== CdkDiffType.NONE) ? [{ job: `${this.namePrefix}diff-${stage.name}` }] : [], ...deploySteps.flatMap(s => s.needs), ], script: deploySteps.flatMap(s => s.commands), @@ -271,8 +271,8 @@ export class GitlabCDKPipeline extends CDKPipeline { this.config.addStages(stage.name); this.config.addJobs({ - [`deploy-${stage.name}`]: { - extends: ['.aws_base', '.artifacts_cdkdeploy', ...steps.flatMap(s => s.extensions)], + [`${this.namePrefix}deploy-${stage.name}`]: { + extends: [`.${this.namePrefix}aws_base`, `.${this.namePrefix}artifacts_cdkdeploy`, ...steps.flatMap(s => s.extensions)], stage: stage.name, tags: this.options.runnerTags?.deployment?.[stage.name] ?? this.options.runnerTags?.default, ...stage.deployOnPush && { diff --git a/src/drift/base.ts b/src/drift/base.ts index 6c858d4..e3b877a 100644 --- a/src/drift/base.ts +++ b/src/drift/base.ts @@ -56,6 +56,14 @@ export interface DriftErrorHandler { } export interface DriftDetectionWorkflowOptions { + /** + * A unique name for this pipeline, used as a prefix for workflow files + * and artifact names to prevent collisions in monorepos. + * + * @default - no prefix + */ + readonly pipelineName?: string; + /** * Name of the workflow * @default "drift-detection" @@ -79,9 +87,13 @@ export abstract class DriftDetectionWorkflow extends Component { public readonly schedule: string; protected readonly stages: DriftDetectionStageOptions[]; + /** Prefix for workflow files and artifact names to prevent collisions in monorepos. */ + protected readonly namePrefix: string; + constructor(project: Project, options: DriftDetectionWorkflowOptions) { super(project); + this.namePrefix = options.pipelineName ? `${options.pipelineName}-` : ''; this.name = options.name ?? 'drift-detection'; this.schedule = options.schedule ?? '0 0 * * *'; this.stages = options.stages; diff --git a/src/drift/github.ts b/src/drift/github.ts index 787215e..1272b5e 100644 --- a/src/drift/github.ts +++ b/src/drift/github.ts @@ -27,7 +27,7 @@ export class GitHubDriftDetectionWorkflow extends DriftDetectionWorkflow { this.permissions = options.permissions; this.createIssues = options.createIssues ?? false; - this.workflow = (this.project as GitHubProject).github!.addWorkflow('drift-detection'); + this.workflow = (this.project as GitHubProject).github!.addWorkflow(`${this.namePrefix}drift-detection`); this.workflow.on({ schedule: [{ cron: this.schedule, @@ -82,8 +82,8 @@ export class GitHubDriftDetectionWorkflow extends DriftDetectionWorkflow { name: 'Upload results', uses: 'actions/upload-artifact@v4', with: { - name: `drift-results-${stage.name}`, - path: `drift-results-${stage.name}.json`, + name: `${this.namePrefix}drift-results-${stage.name}`, + path: `${this.namePrefix}drift-results-${stage.name}.json`, }, }, ...(this.createIssues ? [{ @@ -127,7 +127,7 @@ export class GitHubDriftDetectionWorkflow extends DriftDetectionWorkflow { private generateIssueCreationScript(stage: DriftDetectionStageOptions): string { return ` const fs = require('fs'); -const resultsFile = 'drift-results-${stage.name}.json'; +const resultsFile = '${this.namePrefix}drift-results-${stage.name}.json'; if (!fs.existsSync(resultsFile)) { console.log('No results file found'); diff --git a/src/drift/gitlab.ts b/src/drift/gitlab.ts index 21d4f1f..2123e0c 100644 --- a/src/drift/gitlab.ts +++ b/src/drift/gitlab.ts @@ -32,7 +32,7 @@ export class GitLabDriftDetectionWorkflow extends DriftDetectionWorkflow { this.config.addStages('drift-detection', 'summary'); this.config.addJobs({ - '.drift-detection': { + [`.${this.namePrefix}drift-detection`]: { stage: 'drift-detection', tags: this.runnerTags, image: { name: this.image }, @@ -60,14 +60,14 @@ export class GitLabDriftDetectionWorkflow extends DriftDetectionWorkflow { // Add job for each stage for (const stage of this.stages) { - const jobName = `drift:${stage.name}`; + const jobName = `${this.namePrefix}drift:${stage.name}`; const driftStep = new DriftDetectionStep(this.project, stage); const stepConfig = driftStep.toGitlab(); this.config.addJobs({ [jobName]: { - extends: ['.drift-detection'], + extends: [`.${this.namePrefix}drift-detection`], variables: { ...stepConfig.env, }, @@ -79,10 +79,10 @@ export class GitLabDriftDetectionWorkflow extends DriftDetectionWorkflow { // Add summary job this.config.addJobs({ - 'drift:summary': { + [`${this.namePrefix}drift:summary`]: { stage: 'summary', tags: this.runnerTags, - needs: this.stages.map(s => `drift:${s.name}`), + needs: this.stages.map(s => `${this.namePrefix}drift:${s.name}`), only: { refs: ['schedules'], variables: ['$CI_PIPELINE_SOURCE == "schedule"', '$DRIFT_DETECTION == "true"'], diff --git a/test/__snapshots__/github.test.ts.snap b/test/__snapshots__/github.test.ts.snap index 251950f..9ad3801 100644 --- a/test/__snapshots__/github.test.ts.snap +++ b/test/__snapshots__/github.test.ts.snap @@ -1666,6 +1666,425 @@ jobs: " `; +exports[`Github snapshot with explicit pipelineName 1`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +name: backend-deploy +on: + push: + branches: + - main + workflow_dispatch: {} +jobs: + synth: + name: Synth CDK application + needs: [] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + CI: "true" + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - run: npx projen install:ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: synthRole + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen build + - name: Upload Artifact + uses: actions/upload-artifact@v4.6.2 + with: + name: backend-cloud-assembly + path: cdk.out/ + assetUpload: + name: Publish assets to AWS + needs: synth + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + env: + CI: "true" + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - run: git config --global user.name "github-actions" && git config --global user.email "github-actions@github.com" + - name: Download Artifact + uses: actions/download-artifact@v5 + with: + name: backend-cloud-assembly + path: cdk.out/ + - run: npx projen install:ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: publishRole + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen publish:assets + - run: npx projen bump + - run: npx projen release:push-assembly + deploy-dev: + name: Deploy stage dev to AWS + needs: assetUpload + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + concurrency: + group: backend-deploy-dev + cancel-in-progress: false + env: + CI: "true" + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + - name: Download Artifact + uses: actions/download-artifact@v5 + with: + name: backend-cloud-assembly + path: cdk.out/ + - run: npx projen install:ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: devRole + role-session-name: GitHubAction + aws-region: eu-central-1 + - run: npx projen deploy:dev + - name: Add CDK outputs to summary - dev + run: |- + if [ -f "cdk-outputs-dev.json" ]; then + echo "## CDK Stack Outputs - dev" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check if the file has content + if [ -s "cdk-outputs-dev.json" ]; then + # Parse JSON and create a markdown table + echo "| Stack | Output Key | Output Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|------------|--------------|" >> $GITHUB_STEP_SUMMARY + + # Use jq to parse the JSON and format as table rows + jq -r 'to_entries | .[] | .key as $stack | .value | to_entries | .[] | "| \\($stack) | \\(.key) | \\(.value) |"' "cdk-outputs-dev.json" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + else + echo "No outputs found for this deployment." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + else + echo "## CDK Stack Outputs - dev" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Outputs file not found: cdk-outputs-dev.json" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + - name: Upload Artifact + uses: actions/upload-artifact@v4.6.2 + with: + name: backend-cdk-outputs-dev + path: cdk-outputs-dev.json +" +`; + +exports[`Github snapshot with explicit pipelineName 2`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +name: backend-deploy-feature +on: + pull_request_target: + types: + - synchronize + - labeled + - opened + - reopened + workflow_dispatch: {} +jobs: + synth-and-deploy: + name: Synth and deploy CDK application to feature stage + needs: [] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + concurrency: + group: backend-deploy-feature-\${{ github.event.pull_request.number }} + cancel-in-progress: false + env: + CI: "true" + BRANCH: \${{ github.head_ref }} + if: contains(join(github.event.pull_request.labels.*.name, ','), 'feature-deployment') + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + - run: npx projen install:ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: synthRole + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen build + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen deploy:feature + - name: Add CDK outputs to summary - feature + run: |- + if [ -f "cdk-outputs-feature.json" ]; then + echo "## CDK Stack Outputs - feature" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check if the file has content + if [ -s "cdk-outputs-feature.json" ]; then + # Parse JSON and create a markdown table + echo "| Stack | Output Key | Output Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|------------|--------------|" >> $GITHUB_STEP_SUMMARY + + # Use jq to parse the JSON and format as table rows + jq -r 'to_entries | .[] | .key as $stack | .value | to_entries | .[] | "| \\($stack) | \\(.key) | \\(.value) |"' "cdk-outputs-feature.json" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + else + echo "No outputs found for this deployment." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + else + echo "## CDK Stack Outputs - feature" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Outputs file not found: cdk-outputs-feature.json" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + - name: Upload Artifact + uses: actions/upload-artifact@v4.6.2 + with: + name: backend-cdk-outputs-feature + path: backend-cdk-outputs-feature.json +" +`; + +exports[`Github snapshot with explicit pipelineName 3`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +name: backend-destroy-feature +on: + pull_request_target: + types: + - closed + - unlabeled + workflow_dispatch: {} +jobs: + destroy-feature: + name: Destroy CDK feature stage + needs: [] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + concurrency: + group: backend-destroy-feature-\${{ github.event.pull_request.number }} + cancel-in-progress: false + env: + CI: "true" + BRANCH: \${{ github.head_ref }} + if: github.event.action == 'closed' || (github.event.action == 'unlabeled' && github.event.label.name == 'feature-deployment') + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + - run: npx projen install:ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: synthRole + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen build + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen destroy:feature +" +`; + +exports[`Github snapshot with explicit pipelineName 4`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +name: backend-release-prod +on: + workflow_dispatch: + inputs: + version: + description: Package version + required: true +jobs: + deploy: + name: Release stage prod to AWS + needs: [] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + concurrency: + group: backend-deploy-prod + cancel-in-progress: false + env: + CI: "true" + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + - run: npx projen install:ci + - run: yarn add @assembly/testapp@\${{github.event.inputs.version}} + - run: mv ./node_modules/@assembly/testapp cdk.out + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: prodRole + role-session-name: GitHubAction + aws-region: eu-central-1 + - run: npx projen deploy:prod + - name: Add CDK outputs to summary - prod + run: |- + if [ -f "cdk-outputs-prod.json" ]; then + echo "## CDK Stack Outputs - prod" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check if the file has content + if [ -s "cdk-outputs-prod.json" ]; then + # Parse JSON and create a markdown table + echo "| Stack | Output Key | Output Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|------------|--------------|" >> $GITHUB_STEP_SUMMARY + + # Use jq to parse the JSON and format as table rows + jq -r 'to_entries | .[] | .key as $stack | .value | to_entries | .[] | "| \\($stack) | \\(.key) | \\(.value) |"' "cdk-outputs-prod.json" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + else + echo "No outputs found for this deployment." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + else + echo "## CDK Stack Outputs - prod" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Outputs file not found: cdk-outputs-prod.json" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + - name: Upload Artifact + uses: actions/upload-artifact@v4.6.2 + with: + name: backend-cdk-outputs-prod + path: cdk-outputs-prod.json +" +`; + +exports[`Github snapshot with explicit pipelineName 5`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +name: backend-deploy-sandbox +on: + workflow_dispatch: {} +jobs: + deploy: + name: Release stage sandbox to AWS + needs: [] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + concurrency: + group: backend-deploy-sandbox + cancel-in-progress: false + env: + CI: "true" + steps: + - uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Checkout + uses: actions/checkout@v5 + - run: npx projen install:ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: synthRole + role-session-name: GitHubAction + aws-region: us-east-1 + - run: npx projen build + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-session-name: GitHubAction + aws-region: eu-central-1 + - run: npx projen diff:sandbox + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-session-name: GitHubAction + aws-region: eu-central-1 + - run: npx projen deploy:sandbox + - name: Add CDK outputs to summary - sandbox + run: |- + if [ -f "cdk-outputs-sandbox.json" ]; then + echo "## CDK Stack Outputs - sandbox" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check if the file has content + if [ -s "cdk-outputs-sandbox.json" ]; then + # Parse JSON and create a markdown table + echo "| Stack | Output Key | Output Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|------------|--------------|" >> $GITHUB_STEP_SUMMARY + + # Use jq to parse the JSON and format as table rows + jq -r 'to_entries | .[] | .key as $stack | .value | to_entries | .[] | "| \\($stack) | \\(.key) | \\(.value) |"' "cdk-outputs-sandbox.json" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + else + echo "No outputs found for this deployment." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + else + echo "## CDK Stack Outputs - sandbox" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Outputs file not found: cdk-outputs-sandbox.json" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + - name: Upload Artifact + uses: actions/upload-artifact@v4.6.2 + with: + name: backend-cdk-outputs-sandbox + path: cdk-outputs-sandbox.json +" +`; + exports[`Github snapshot with feature stages 1`] = ` "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". diff --git a/test/__snapshots__/gitlab.test.ts.snap b/test/__snapshots__/gitlab.test.ts.snap index f71c73d..350409d 100644 --- a/test/__snapshots__/gitlab.test.ts.snap +++ b/test/__snapshots__/gitlab.test.ts.snap @@ -270,6 +270,202 @@ exports[`Gitlab snapshot 2`] = ` } `; +exports[`Gitlab snapshot with explicit pipelineName 1`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +stages: + - synth + - publish_assets + - dev + - prod + - sandbox +.backend-artifacts_cdk: + artifacts: + when: on_success + expire_in: 30 days + name: CDK Assembly - $CI_JOB_NAME-$CI_COMMIT_REF_SLUG + untracked: false + paths: + - cdk.out +.backend-artifacts_cdkdeploy: + artifacts: + when: on_success + expire_in: 30 days + name: CDK Outputs - $CI_JOB_NAME-$CI_COMMIT_REF_SLUG + untracked: false + paths: + - cdk-outputs-*.json +.backend-aws_base: + image: + name: "image: jsii/superchain:1-buster-slim-node18" + id_tokens: + AWS_TOKEN: + aud: https://sts.amazonaws.com + variables: + CI: "true" +backend-synth: + extends: + - .backend-aws_base + - .backend-artifacts_cdk + needs: [] + stage: synth + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "synthRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen build + variables: {} +backend-publish_assets: + extends: + - .backend-aws_base + stage: publish_assets + needs: + - job: backend-synth + artifacts: true + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "publishRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen publish:assets + variables: {} +backend-diff-dev: + extends: + - .backend-aws_base + stage: dev + only: + refs: + - main + needs: + - job: backend-synth + artifacts: true + - job: backend-publish_assets + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "devRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen fastdiff:dev + variables: + AWS_REGION: eu-central-1 +backend-deploy-dev: + extends: + - .backend-aws_base + - .backend-artifacts_cdkdeploy + stage: dev + only: + refs: + - main + needs: + - job: backend-synth + artifacts: true + - job: backend-publish_assets + - job: backend-diff-dev + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "devRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen deploy:dev + - if [ -f "cdk-outputs-dev.json" ]; then + - ' echo -e "\\e[0Ksection_start:\`date +%s\`:cdk_outputs_dev[collapsed=false]\\r\\e[0K📦 CDK Stack Outputs - dev"' + - ' if [ -s "cdk-outputs-dev.json" ]; then' + - ' echo ""' + - ' echo "Stack Outputs:"' + - ' echo "=============="' + - " jq -r 'to_entries | .[] | \\"\\\\n📚 Stack: \\\\(.key)\\\\n\\" + (.value | to_entries | .[] | \\" • \\\\(.key): \\\\(.value)\\")' \\"cdk-outputs-dev.json\\"" + - ' echo ""' + - " else" + - ' echo "No outputs found for this deployment."' + - " fi" + - ' echo -e "\\e[0Ksection_end:\`date +%s\`:cdk_outputs_dev\\r\\e[0K"' + - else + - ' echo "⚠️ CDK outputs file not found: cdk-outputs-dev.json"' + - fi + variables: + AWS_REGION: eu-central-1 +backend-diff-prod: + extends: + - .backend-aws_base + stage: prod + only: + refs: + - main + needs: + - job: backend-synth + artifacts: true + - job: backend-publish_assets + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "prodRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen diff:prod + variables: + AWS_REGION: eu-central-1 +backend-deploy-prod: + extends: + - .backend-aws_base + - .backend-artifacts_cdkdeploy + stage: prod + when: manual + only: + refs: + - main + needs: + - job: backend-synth + artifacts: true + - job: backend-publish_assets + - job: backend-diff-prod + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "prodRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen deploy:prod + - if [ -f "cdk-outputs-prod.json" ]; then + - ' echo -e "\\e[0Ksection_start:\`date +%s\`:cdk_outputs_prod[collapsed=false]\\r\\e[0K📦 CDK Stack Outputs - prod"' + - ' if [ -s "cdk-outputs-prod.json" ]; then' + - ' echo ""' + - ' echo "Stack Outputs:"' + - ' echo "=============="' + - " jq -r 'to_entries | .[] | \\"\\\\n📚 Stack: \\\\(.key)\\\\n\\" + (.value | to_entries | .[] | \\" • \\\\(.key): \\\\(.value)\\")' \\"cdk-outputs-prod.json\\"" + - ' echo ""' + - " else" + - ' echo "No outputs found for this deployment."' + - " fi" + - ' echo -e "\\e[0Ksection_end:\`date +%s\`:cdk_outputs_prod\\r\\e[0K"' + - else + - ' echo "⚠️ CDK outputs file not found: cdk-outputs-prod.json"' + - fi + variables: + AWS_REGION: eu-central-1 +backend-deploy-sandbox: + extends: + - .backend-aws_base + - .backend-artifacts_cdkdeploy + stage: sandbox + only: + refs: + - main + needs: [] + script: + - npx projen install:ci + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "synthRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen build + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "undefined" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen diff:sandbox + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "undefined" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - npx projen deploy:sandbox + - if [ -f "cdk-outputs-sandbox.json" ]; then + - ' echo -e "\\e[0Ksection_start:\`date +%s\`:cdk_outputs_sandbox[collapsed=false]\\r\\e[0K📦 CDK Stack Outputs - sandbox"' + - ' if [ -s "cdk-outputs-sandbox.json" ]; then' + - ' echo ""' + - ' echo "Stack Outputs:"' + - ' echo "=============="' + - " jq -r 'to_entries | .[] | \\"\\\\n📚 Stack: \\\\(.key)\\\\n\\" + (.value | to_entries | .[] | \\" • \\\\(.key): \\\\(.value)\\")' \\"cdk-outputs-sandbox.json\\"" + - ' echo ""' + - " else" + - ' echo "No outputs found for this deployment."' + - " fi" + - ' echo -e "\\e[0Ksection_end:\`date +%s\`:cdk_outputs_sandbox\\r\\e[0K"' + - else + - ' echo "⚠️ CDK outputs file not found: cdk-outputs-sandbox.json"' + - fi + variables: + AWS_REGION: eu-central-1 +" +`; + exports[`Gitlab snapshot with independent stage 1`] = ` "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". diff --git a/test/drift/__snapshots__/drift-detection.test.ts.snap b/test/drift/__snapshots__/drift-detection.test.ts.snap index cf1a443..fb14ee4 100644 --- a/test/drift/__snapshots__/drift-detection.test.ts.snap +++ b/test/drift/__snapshots__/drift-detection.test.ts.snap @@ -576,6 +576,116 @@ jobs: " `; +exports[`GitHubDriftDetectionWorkflow should prefix workflow and artifact names with pipelineName 1`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +name: backend-drift-detection +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: + inputs: + stage: + description: Stage to check for drift (leave empty for all) + required: false + type: choice + options: + - production +jobs: + drift-production: + name: Drift Detection - production + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + AWS_DEFAULT_REGION: us-east-1 + DRIFT_DETECTION_OUTPUT: drift-results-production.json + AWS_REGION: us-east-1 + STAGE_NAME: production + if: \${{ github.event_name == 'schedule' || github.event.inputs.stage == '' || github.event.inputs.stage == 'production' }} + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: "20" + - name: Install dependencies + run: npm ci + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: arn:aws:iam::123456789012:role/ProdRole + role-session-name: GitHubAction + aws-region: us-east-1 + - run: detect-drift --region us-east-1 + - name: Upload results + uses: actions/upload-artifact@v4 + with: + name: backend-drift-results-production + path: backend-drift-results-production.json + drift-summary: + name: Drift Detection Summary + needs: drift-production + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download all artifacts + uses: actions/download-artifact@v5 + with: + path: drift-results + - name: Generate summary + run: | + + #!/bin/bash + echo "## Drift Detection Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + total_stacks=0 + total_drifted=0 + total_errors=0 + + for file in drift-results-*.json; do + if [[ -f "$file" ]]; then + stage=$(basename $(dirname "$file")) + echo "### Stage: $stage" >> $GITHUB_STEP_SUMMARY + + # Parse JSON and generate summary + jq -r ' + . as $results | + "- Total stacks: " + ($results | length | tostring) + "\\n" + + "- Drifted: " + ([$results[] | select(.driftStatus == "DRIFTED")] | length | tostring) + "\\n" + + "- Errors: " + ([$results[] | select(.error)] | length | tostring) + "\\n" + + ([$results[] | select(.driftStatus == "DRIFTED")] | + if length > 0 then + "\\n**Drifted stacks:**\\n" + + (map(" - " + .stackName + " (" + ((.driftedResources // []) | length | tostring) + " resources)") | join("\\n")) + else "" end) + ' "$file" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + + # Count totals + total_stacks=$((total_stacks + $(jq 'length' "$file"))) + total_drifted=$((total_drifted + $(jq '[.[] | select(.driftStatus == "DRIFTED")] | length' "$file"))) + total_errors=$((total_errors + $(jq '[.[] | select(.error)] | length' "$file"))) + fi + done + + echo "### Overall Summary" >> $GITHUB_STEP_SUMMARY + echo "- Total stacks checked: $total_stacks" >> $GITHUB_STEP_SUMMARY + echo "- Total drifted stacks: $total_drifted" >> $GITHUB_STEP_SUMMARY + echo "- Total errors: $total_errors" >> $GITHUB_STEP_SUMMARY + + if [[ $total_drifted -gt 0 ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "⚠️ **Action required:** Drift detected in $total_drifted stacks" >> $GITHUB_STEP_SUMMARY + fi +" +`; + exports[`GitHubDriftDetectionWorkflow should support custom schedule 1`] = ` "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". @@ -786,4 +896,115 @@ exports[`GitLabDriftDetectionWorkflow should add GitLab runner tags when specifi exports[`GitLabDriftDetectionWorkflow should create GitLab pipeline 1`] = `undefined`; +exports[`GitLabDriftDetectionWorkflow should prefix job names with pipelineName 1`] = ` +"# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". + +stages: + - drift-detection + - summary +.backend-drift-detection: + stage: drift-detection + tags: [] + image: + name: node:18 + id_tokens: + AWS_TOKEN: + aud: https://sts.amazonaws.com + only: + refs: + - schedules + variables: + - $CI_PIPELINE_SOURCE == "schedule" + - $DRIFT_DETECTION == "true" + before_script: + - apt-get update && apt-get install -y python3 python3-pip + - pip3 install awscli + - npm ci + artifacts: + paths: + - drift-results-*.json + expire_in: 1 week + when: always +backend-drift:production: + extends: + - .backend-drift-detection + variables: + AWS_REGION: us-east-1 + AWS_DEFAULT_REGION: us-east-1 + DRIFT_DETECTION_OUTPUT: drift-results-production.json + STAGE_NAME: production + script: + - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role-with-web-identity --role-arn "arn:aws:iam::123456789012:role/ProdRole" --role-session-name "GitLabRunner-\${CI_PROJECT_ID}-\${CI_PIPELINE_ID}}" --web-identity-token \${AWS_TOKEN} --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) + - detect-drift --region us-east-1 + allow_failure: true +backend-drift:summary: + stage: summary + tags: [] + needs: + - backend-drift:production + only: + refs: + - schedules + variables: + - $CI_PIPELINE_SOURCE == "schedule" + - $DRIFT_DETECTION == "true" + script: + - echo "## Drift Detection Summary" + - echo "" + - | + + total_stacks=0 + total_drifted=0 + total_errors=0 + + for file in drift-results-*.json; do + if [[ -f "$file" ]]; then + stage=$(echo $file | sed 's/drift-results-//;s/.json//') + echo "### Stage: $stage" + + # Count results + stacks=$(jq 'length' "$file") + drifted=$(jq '[.[] | select(.driftStatus == "DRIFTED")] | length' "$file") + errors=$(jq '[.[] | select(.error)] | length' "$file") + + echo "- Total stacks: $stacks" + echo "- Drifted: $drifted" + echo "- Errors: $errors" + + # Show drifted stacks + if [[ $drifted -gt 0 ]]; then + echo "" + echo "**Drifted stacks:**" + jq -r '.[] | select(.driftStatus == "DRIFTED") | " - " + .stackName + " (" + ((.driftedResources // []) | length | tostring) + " resources)"' "$file" + fi + + echo "" + + # Accumulate totals + total_stacks=$((total_stacks + stacks)) + total_drifted=$((total_drifted + drifted)) + total_errors=$((total_errors + errors)) + fi + done + + echo "### Overall Summary" + echo "- Total stacks checked: $total_stacks" + echo "- Total drifted stacks: $total_drifted" + echo "- Total errors: $total_errors" + + if [[ $total_drifted -gt 0 ]]; then + echo "" + echo "⚠️ **Action required:** Drift detected in $total_drifted stacks" + + # Send notification if webhook is configured + if [[ -n "$DRIFT_NOTIFICATION_WEBHOOK" ]]; then + curl -X POST "$DRIFT_NOTIFICATION_WEBHOOK" \\ + -H "Content-Type: application/json" \\ + -d "{\\"text\\": \\"Drift detected in $total_drifted stacks. Check pipeline $CI_PIPELINE_URL for details.\\"}" || true + fi + fi + when: always +" +`; + exports[`GitLabDriftDetectionWorkflow should support custom docker image 1`] = `undefined`; diff --git a/test/drift/drift-detection.test.ts b/test/drift/drift-detection.test.ts index fdb119a..02b5b6a 100644 --- a/test/drift/drift-detection.test.ts +++ b/test/drift/drift-detection.test.ts @@ -139,6 +139,31 @@ describe('GitHubDriftDetectionWorkflow', () => { expect(snapshot['.github/workflows/drift-detection.yml']).toMatchSnapshot(); }); + it('should prefix workflow and artifact names with pipelineName', () => { + new GitHubDriftDetectionWorkflow(project, { + pipelineName: 'backend', + stages: [ + { + name: 'production', + region: 'us-east-1', + roleArn: 'arn:aws:iam::123456789012:role/ProdRole', + }, + ], + }); + + const snapshot = synthSnapshot(project); + + // Workflow file should be prefixed + expect(snapshot['.github/workflows/backend-drift-detection.yml']).toBeDefined(); + expect(snapshot['.github/workflows/drift-detection.yml']).toBeUndefined(); + + // Artifact names should be prefixed + const workflowYml = snapshot['.github/workflows/backend-drift-detection.yml']; + expect(workflowYml).toContain('backend-drift-results-production'); + + expect(workflowYml).toMatchSnapshot(); + }); + it('should support disabling issue creation', () => { new GitHubDriftDetectionWorkflow(project, { createIssues: false, @@ -194,6 +219,33 @@ describe('GitLabDriftDetectionWorkflow', () => { expect(snapshot['.gitlab/drift-detection.yml']).toMatchSnapshot(); }); + it('should prefix job names with pipelineName', () => { + new GitLabDriftDetectionWorkflow(project, { + pipelineName: 'backend', + stages: [ + { + name: 'production', + region: 'us-east-1', + roleArn: 'arn:aws:iam::123456789012:role/ProdRole', + }, + ], + }); + + const snapshot = synthSnapshot(project); + + // Find the gitlab ci file (could be .gitlab-ci.yml or .gitlab/drift-detection.yml) + const gitlabCiKey = Object.keys(snapshot).find(k => k.includes('gitlab')); + expect(gitlabCiKey).toBeDefined(); + const gitlabCi = snapshot[gitlabCiKey!]; + + // Job names should be prefixed, hidden jobs preserve dot prefix + expect(gitlabCi).toContain('.backend-drift-detection'); + expect(gitlabCi).toContain('backend-drift:production'); + expect(gitlabCi).toContain('backend-drift:summary'); + + expect(gitlabCi).toMatchSnapshot(); + }); + it('should support custom docker image', () => { new GitLabDriftDetectionWorkflow(project, { image: 'custom/node:18-aws', diff --git a/test/github.test.ts b/test/github.test.ts index 9c577bf..15585a7 100644 --- a/test/github.test.ts +++ b/test/github.test.ts @@ -699,6 +699,126 @@ test('Github snapshot with jump roles', () => { expect(snapshot['src/app.ts']).toContain('StringParameter'); }); +test('Github snapshot with explicit pipelineName', () => { + const p = new AwsCdkTypeScriptApp({ + cdkVersion: '2.132.0', + defaultReleaseBranch: 'main', + name: 'testapp', + }); + + new GithubCDKPipeline(p, { + pipelineName: 'backend', + iamRoleArns: { + synth: 'synthRole', + assetPublishing: 'publishRole', + deployment: { + dev: 'devRole', + prod: 'prodRole', + }, + }, + pkgNamespace: '@assembly', + stages: [{ + name: 'dev', + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }, { + name: 'prod', + manualApproval: true, + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }], + featureStages: { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }, + independentStages: [{ + name: 'sandbox', + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }], + }); + + const snapshot = synthSnapshot(p); + + // Workflow files should be prefixed + expect(snapshot['.github/workflows/backend-deploy.yml']).toBeDefined(); + expect(snapshot['.github/workflows/backend-deploy-feature.yml']).toBeDefined(); + expect(snapshot['.github/workflows/backend-destroy-feature.yml']).toBeDefined(); + expect(snapshot['.github/workflows/backend-release-prod.yml']).toBeDefined(); + expect(snapshot['.github/workflows/backend-deploy-sandbox.yml']).toBeDefined(); + + // Old unprefixed files should not exist + expect(snapshot['.github/workflows/deploy.yml']).toBeUndefined(); + expect(snapshot['.github/workflows/deploy-feature.yml']).toBeUndefined(); + expect(snapshot['.github/workflows/destroy-feature.yml']).toBeUndefined(); + expect(snapshot['.github/workflows/release-prod.yml']).toBeUndefined(); + expect(snapshot['.github/workflows/deploy-sandbox.yml']).toBeUndefined(); + + // Verify artifact names and concurrency groups are prefixed + const deployYml = snapshot['.github/workflows/backend-deploy.yml']; + expect(deployYml).toContain('backend-cloud-assembly'); + expect(deployYml).toContain('backend-cdk-outputs-dev'); + expect(deployYml).toContain('backend-deploy-dev'); + + const featureDeployYml = snapshot['.github/workflows/backend-deploy-feature.yml']; + expect(featureDeployYml).toContain('backend-cdk-outputs-feature'); + expect(featureDeployYml).toContain('backend-deploy-feature-'); + + const featureDestroyYml = snapshot['.github/workflows/backend-destroy-feature.yml']; + expect(featureDestroyYml).toContain('backend-destroy-feature-'); + + const releaseYml = snapshot['.github/workflows/backend-release-prod.yml']; + expect(releaseYml).toContain('backend-cdk-outputs-prod'); + expect(releaseYml).toContain('backend-deploy-prod'); + + const sandboxYml = snapshot['.github/workflows/backend-deploy-sandbox.yml']; + expect(sandboxYml).toContain('backend-cdk-outputs-sandbox'); + expect(sandboxYml).toContain('backend-deploy-sandbox'); + + expect(deployYml).toMatchSnapshot(); + expect(featureDeployYml).toMatchSnapshot(); + expect(featureDestroyYml).toMatchSnapshot(); + expect(releaseYml).toMatchSnapshot(); + expect(sandboxYml).toMatchSnapshot(); +}); + +test('Github snapshot with no pipelineName on standalone project', () => { + const p = new AwsCdkTypeScriptApp({ + cdkVersion: '2.132.0', + defaultReleaseBranch: 'main', + name: 'testapp', + }); + + new GithubCDKPipeline(p, { + iamRoleArns: { + synth: 'synthRole', + assetPublishing: 'publishRole', + }, + stages: [{ + name: 'dev', + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }], + }); + + const snapshot = synthSnapshot(p); + + // Standalone project without parent should have no prefix + expect(snapshot['.github/workflows/deploy.yml']).toBeDefined(); + expect(snapshot['.github/workflows/deploy.yml']).toContain('cloud-assembly'); + expect(snapshot['.github/workflows/deploy.yml']).not.toContain('testapp-cloud-assembly'); +}); + test('Github snapshot with pnpm package manager', () => { const p = new AwsCdkTypeScriptApp({ cdkVersion: '2.132.0', diff --git a/test/gitlab.test.ts b/test/gitlab.test.ts index 691a233..4feb03a 100644 --- a/test/gitlab.test.ts +++ b/test/gitlab.test.ts @@ -276,4 +276,102 @@ test('Gitlab snapshot with versioning enabled', () => { expect(snapshot['src/app.ts']).toContain('addVersioningToStack'); expect(snapshot['src/app.ts']).toContain('CfnOutput'); expect(snapshot['src/app.ts']).toContain('StringParameter'); +}); + +test('Gitlab snapshot with explicit pipelineName', () => { + const p = new AwsCdkTypeScriptApp({ + cdkVersion: '2.132.0', + defaultReleaseBranch: 'main', + name: 'testapp', + }); + + new GitlabCDKPipeline(p, { + pipelineName: 'backend', + iamRoleArns: { + synth: 'synthRole', + assetPublishing: 'publishRole', + deployment: { + dev: 'devRole', + prod: 'prodRole', + }, + }, + stages: [{ + name: 'dev', + diffType: CdkDiffType.FAST, + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }, { + name: 'prod', + manualApproval: true, + diffType: CdkDiffType.FULL, + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }], + independentStages: [{ + name: 'sandbox', + env: { + account: '123456789012', + region: 'eu-central-1', + }, + deployOnPush: true, + }], + }); + + const snapshot = synthSnapshot(p); + const gitlabCi = snapshot['.gitlab-ci.yml']; + + // Verify job names are prefixed + expect(gitlabCi).toContain('backend-synth'); + expect(gitlabCi).toContain('backend-publish_assets'); + expect(gitlabCi).toContain('backend-diff-dev'); + expect(gitlabCi).toContain('backend-deploy-dev'); + expect(gitlabCi).toContain('backend-deploy-prod'); + expect(gitlabCi).toContain('backend-deploy-sandbox'); + + // Verify hidden job names preserve dot prefix + expect(gitlabCi).toContain('.backend-aws_base'); + expect(gitlabCi).toContain('.backend-artifacts_cdk'); + expect(gitlabCi).toContain('.backend-artifacts_cdkdeploy'); + + // Verify extends references use the prefixed hidden names + expect(gitlabCi).not.toContain('extends:\n - .aws_base'); + + expect(gitlabCi).toMatchSnapshot(); +}); + +test('Gitlab snapshot with no pipelineName on standalone project', () => { + const p = new AwsCdkTypeScriptApp({ + cdkVersion: '2.132.0', + defaultReleaseBranch: 'main', + name: 'testapp', + }); + + new GitlabCDKPipeline(p, { + iamRoleArns: { + synth: 'synthRole', + assetPublishing: 'publishRole', + deployment: { + dev: 'devRole', + }, + }, + stages: [{ + name: 'dev', + env: { + account: '123456789012', + region: 'eu-central-1', + }, + }], + }); + + const snapshot = synthSnapshot(p); + const gitlabCi = snapshot['.gitlab-ci.yml']; + + // Standalone project without parent should have no prefix + expect(gitlabCi).toContain('.aws_base'); + expect(gitlabCi).not.toContain('testapp-synth'); + expect(gitlabCi).not.toContain('.testapp-aws_base'); }); \ No newline at end of file