From a4c9cb64e6fecd5b524a5dc9e5248791d3484933 Mon Sep 17 00:00:00 2001 From: Rico Hermans Date: Wed, 11 Dec 2024 10:52:56 +0100 Subject: [PATCH] fix: bump default superchain images (#1799) * fix: bump default superchain images delivlib has some places where it defaults to a particular superchain image, which may grow outdated over time. This came up specifically in the context of ECR mirroring recently. Bump the Superchain image we're using to a recent one everywhere (`bookworm` instead of `bullseye`, a more recent Node because Node 18 is about to be deprecated). Also make sure that all CodeBuild projects have reasonable descriptions, because we have hundreds in our account now and we need some way to tell them apart. * chore: self mutation Signed-off-by: github-actions * Duplicate superchain information into tests * Unused import --------- Signed-off-by: github-actions Co-authored-by: github-actions --- .../registry-sync/ecr-mirror.test.ts | 2 +- lib/__tests__/signing.test.ts | 2 +- lib/build-env.ts | 3 +- lib/constants.ts | 4 ++ lib/pipeline.ts | 14 ++++ lib/publishing.ts | 69 ++++++++++++++++++- lib/pull-request/merge-back.ts | 7 ++ lib/registry-sync/ecr-mirror.ts | 19 ++++- lib/signing.ts | 3 +- 9 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 lib/constants.ts diff --git a/lib/__tests__/registry-sync/ecr-mirror.test.ts b/lib/__tests__/registry-sync/ecr-mirror.test.ts index 6e87229f1..8325bb14a 100644 --- a/lib/__tests__/registry-sync/ecr-mirror.test.ts +++ b/lib/__tests__/registry-sync/ecr-mirror.test.ts @@ -36,7 +36,7 @@ describe('EcrMirror', () => { Value: '123aass:password-key:AWSCURRENT', }, ], - Image: 'public.ecr.aws/jsii/superchain:1-bullseye-slim-node18', + Image: 'public.ecr.aws/jsii/superchain:1-bookworm-slim-node22', }, Source: { BuildSpec: { diff --git a/lib/__tests__/signing.test.ts b/lib/__tests__/signing.test.ts index fb177020e..acfdf79c0 100644 --- a/lib/__tests__/signing.test.ts +++ b/lib/__tests__/signing.test.ts @@ -103,7 +103,7 @@ describe('with standard pipeline', () => { }, }, ], - Image: 'public.ecr.aws/jsii/superchain:1-bullseye-slim-node18', + Image: 'public.ecr.aws/jsii/superchain:1-bookworm-slim-node22', ImagePullCredentialsType: 'SERVICE_ROLE', PrivilegedMode: false, Type: 'LINUX_CONTAINER', diff --git a/lib/build-env.ts b/lib/build-env.ts index e1cd54dba..eb5decb4f 100644 --- a/lib/build-env.ts +++ b/lib/build-env.ts @@ -1,4 +1,5 @@ import { aws_codebuild as cbuild } from 'aws-cdk-lib'; +import { DEFAULT_SUPERCHAIN_IMAGE } from './constants'; export interface BuildEnvironmentProps { computeType?: cbuild.ComputeType; @@ -14,7 +15,7 @@ export function createBuildEnvironment(props: BuildEnvironmentProps) { computeType: props.computeType || cbuild.ComputeType.SMALL, privileged: props.privileged, environmentVariables: renderEnvironmentVariables({ ...props.environment, ...props.env }), - buildImage: props.buildImage || cbuild.LinuxBuildImage.fromDockerRegistry('public.ecr.aws/jsii/superchain:1-bullseye-slim-node18'), + buildImage: props.buildImage || cbuild.LinuxBuildImage.fromDockerRegistry(DEFAULT_SUPERCHAIN_IMAGE), }; return environment; diff --git a/lib/constants.ts b/lib/constants.ts new file mode 100644 index 000000000..04f48fd27 --- /dev/null +++ b/lib/constants.ts @@ -0,0 +1,4 @@ +/** + * The default superchain image that will be used all across delivlib if no override is supplied. + */ +export const DEFAULT_SUPERCHAIN_IMAGE = 'public.ecr.aws/jsii/superchain:1-bookworm-slim-node22'; \ No newline at end of file diff --git a/lib/pipeline.ts b/lib/pipeline.ts index b920cd4d9..c6f2b09c1 100644 --- a/lib/pipeline.ts +++ b/lib/pipeline.ts @@ -217,6 +217,7 @@ export class Pipeline extends Construct { private readonly buildEnvironment: cbuild.BuildEnvironment; private readonly buildSpec?: cbuild.BuildSpec; private firstPublishStageName?: string; + private readonly descrPipelineName: string; constructor(parent: Construct, name: string, props: PipelineProps) { super(parent, name); @@ -229,6 +230,9 @@ export class Pipeline extends Construct { pipelineName: props.pipelineName, restartExecutionOnUpdate: props.restartExecutionOnUpdate === undefined ? true : props.restartExecutionOnUpdate, }); + // We will use the pipeline name if given, but we can't use the Ref if not given + // because that would create cyclic references. Fall back to construct path if anonymous. + this.descrPipelineName = props.pipelineName ?? this.node.path; this.branch = props.branch || 'master'; this.sourceArtifact = props.repo.createSourceStage(this.pipeline, this.branch); @@ -241,6 +245,7 @@ export class Pipeline extends Construct { buildProjectName = `${props.pipelineName}-Build`; } this.buildProject = new cbuild.PipelineProject(this, 'BuildProject', { + description: `Pipeline ${this.descrPipelineName}: build step`, projectName: buildProjectName, environment: this.buildEnvironment, buildSpec: this.buildSpec, @@ -388,6 +393,7 @@ export class Pipeline extends Construct { public publishToNpm(options: publishing.PublishToNpmProjectProps & AddPublishOptions) { this.addPublish(new publishing.PublishToNpmProject(this, 'Npm', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to NPM`, dryRun: this.dryRun, ...options, }), options); @@ -395,6 +401,7 @@ export class Pipeline extends Construct { public publishToMaven(options: publishing.PublishToMavenProjectProps & AddPublishOptions) { this.addPublish(new publishing.PublishToMavenProject(this, 'Maven', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to Maven`, dryRun: this.dryRun, ...options, }), options); @@ -402,6 +409,7 @@ export class Pipeline extends Construct { public publishToNuGet(options: publishing.PublishToNuGetProjectProps & AddPublishOptions) { this.addPublish(new publishing.PublishToNuGetProject(this, 'NuGet', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to NuGet`, dryRun: this.dryRun, ...options, }), options); @@ -409,6 +417,7 @@ export class Pipeline extends Construct { public publishToGitHubPages(options: publishing.PublishDocsToGitHubProjectProps & AddPublishOptions) { this.addPublish(new publishing.PublishDocsToGitHubProject(this, 'GitHubPages', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to GitHub Pages`, dryRun: this.dryRun, ...options, }), options); @@ -416,6 +425,7 @@ export class Pipeline extends Construct { public publishToGitHub(options: publishing.PublishToGitHubProps & AddPublishOptions) { this.addPublish(new publishing.PublishToGitHub(this, 'GitHub', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to GitHub`, dryRun: this.dryRun, ...options, }), options); @@ -423,6 +433,7 @@ export class Pipeline extends Construct { public publishToPyPI(options: publishing.PublishToPyPiProps & AddPublishOptions) { this.addPublish(new publishing.PublishToPyPi(this, 'PyPI', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to PyPI`, dryRun: this.dryRun, ...options, }), options); @@ -430,6 +441,7 @@ export class Pipeline extends Construct { public publishToS3(id: string, options: publishing.PublishToS3Props & AddPublishOptions) { this.addPublish(new publishing.PublishToS3(this, id, { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish to S3 (${options.bucket.bucketName})`, dryRun: this.dryRun, ...options, }), options); @@ -440,6 +452,7 @@ export class Pipeline extends Construct { */ public publishToGolang(options: publishing.PublishToGolangProps) { this.addPublish(new publishing.PublishToGolang(this, 'Golang', { + description: options.description ?? `Pipeline ${this.descrPipelineName}: publish Golang`, dryRun: this.dryRun, ...options, })); @@ -474,6 +487,7 @@ export class Pipeline extends Construct { const mergeBack = new AutoMergeBack(this, 'MergeBack', { repo: this.repo, ...options, + projectDescription: options?.projectDescription ?? `Pipeline ${this.descrPipelineName}: merge-back step`, }); if (options?.stage) { diff --git a/lib/publishing.ts b/lib/publishing.ts index b8fca7e12..d90e6b59b 100644 --- a/lib/publishing.ts +++ b/lib/publishing.ts @@ -9,6 +9,7 @@ import { } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { ICodeSigningCertificate } from './code-signing'; +import { DEFAULT_SUPERCHAIN_IMAGE } from './constants'; import { OpenPGPKeyPair } from './open-pgp-key-pair'; import * as permissions from './permissions'; import { AddToPipelineOptions, IPublisher } from './pipeline'; @@ -77,6 +78,13 @@ export interface PublishToMavenProjectProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } /** @@ -92,7 +100,8 @@ export class PublishToMavenProject extends Construct implements IPublisher { const forReal = props.dryRun === undefined ? 'false' : (!props.dryRun).toString(); const shellable = new Shellable(this, 'Default', { - platform: new LinuxPlatform(props.buildImage ?? cbuild.LinuxBuildImage.fromDockerRegistry('public.ecr.aws/jsii/superchain:1-bookworm-slim-node20')), + description: props.description, + platform: new LinuxPlatform(props.buildImage ?? cbuild.LinuxBuildImage.fromDockerRegistry(DEFAULT_SUPERCHAIN_IMAGE)), scriptDirectory: path.join(__dirname, 'publishing', 'maven'), entrypoint: 'publish.sh', environment: noUndefined({ @@ -165,6 +174,13 @@ export interface PublishToNpmProjectProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } /** @@ -182,6 +198,7 @@ export class PublishToNpmProject extends Construct implements IPublisher { const access = props.access ?? NpmAccess.PUBLIC; const shellable = new Shellable(this, 'Default', { + description: props.description, platform: new LinuxPlatform(cbuild.LinuxBuildImage.STANDARD_7_0), scriptDirectory: path.join(__dirname, 'publishing', 'npm'), entrypoint: 'publish.sh', @@ -249,6 +266,13 @@ export interface PublishToNuGetProjectProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } /** @@ -281,7 +305,8 @@ export class PublishToNuGetProject extends Construct implements IPublisher { } const shellable = new Shellable(this, 'Default', { - platform: new LinuxPlatform(props.buildImage ?? cbuild.LinuxBuildImage.fromDockerRegistry('public.ecr.aws/jsii/superchain:1-bookworm-slim-node20')), + description: props.description, + platform: new LinuxPlatform(props.buildImage ?? cbuild.LinuxBuildImage.fromDockerRegistry(DEFAULT_SUPERCHAIN_IMAGE)), scriptDirectory: path.join(__dirname, 'publishing', 'nuget'), entrypoint: 'publish.sh', environment, @@ -353,6 +378,13 @@ export interface PublishDocsToGitHubProjectProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } /** @@ -368,6 +400,7 @@ export class PublishDocsToGitHubProject extends Construct implements IPublisher const forReal = props.dryRun === undefined ? 'false' : (!props.dryRun).toString(); const shellable = new Shellable(this, 'Default', { + description: props.description, platform: new LinuxPlatform(cbuild.LinuxBuildImage.STANDARD_7_0), scriptDirectory: path.join(__dirname, 'publishing', 'docs'), entrypoint: 'publish.sh', @@ -466,6 +499,13 @@ export interface PublishToGitHubProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } export class PublishToGitHub extends Construct implements IPublisher { @@ -485,6 +525,7 @@ export class PublishToGitHub extends Construct implements IPublisher { } const shellable = new Shellable(this, 'Default', { + description: props.description, platform: new LinuxPlatform(cbuild.LinuxBuildImage.STANDARD_7_0), scriptDirectory: path.join(__dirname, 'publishing', 'github'), entrypoint: 'publish.sh', @@ -542,6 +583,13 @@ export interface PublishToS3Props { * @default true */ dryRun?: boolean; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } export class PublishToS3 extends Construct implements IPublisher { @@ -554,6 +602,7 @@ export class PublishToS3 extends Construct implements IPublisher { const forReal = props.dryRun === undefined ? 'false' : (!props.dryRun).toString(); const shellable = new Shellable(this, 'Default', { + description: props.description, platform: new LinuxPlatform(cbuild.LinuxBuildImage.STANDARD_7_0), scriptDirectory: path.join(__dirname, 'publishing', 's3'), entrypoint: 'publish.sh', @@ -604,6 +653,13 @@ export interface PublishToPyPiProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } export class PublishToPyPi extends Construct { @@ -617,6 +673,7 @@ export class PublishToPyPi extends Construct { const forReal = props.dryRun === undefined ? 'false' : (!props.dryRun).toString(); const shellable = new Shellable(this, 'Default', { + description: props.description, platform: new LinuxPlatform(cbuild.LinuxBuildImage.STANDARD_7_0), scriptDirectory: path.join(__dirname, 'publishing', 'pypi'), entrypoint: 'publish.sh', @@ -705,6 +762,13 @@ export interface PublishToGolangProps { * @default - no SSM parameters */ ssmPrefix?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + description?: string; } /** @@ -720,6 +784,7 @@ export class PublishToGolang extends Construct { const dryRun = props.dryRun ?? false; const shellable = new Shellable(this, 'Default', { + description: props.description, platform: new LinuxPlatform(cbuild.LinuxBuildImage.STANDARD_7_0), scriptDirectory: path.join(__dirname, 'publishing', 'golang'), entrypoint: 'publish.sh', diff --git a/lib/pull-request/merge-back.ts b/lib/pull-request/merge-back.ts index 6afd5901b..5b0654b74 100644 --- a/lib/pull-request/merge-back.ts +++ b/lib/pull-request/merge-back.ts @@ -81,6 +81,13 @@ export interface AutoMergeBackOptions extends pr.AutoPullRequestOptions { * @default - no condition */ condition?: string; + + /** + * Description for the CodeBuild project + * + * @default - No description + */ + projectDescription?: string; } export interface AutoMergeBackPipelineOptions extends AutoMergeBackOptions { diff --git a/lib/registry-sync/ecr-mirror.ts b/lib/registry-sync/ecr-mirror.ts index 243cc107f..93d2bbc05 100644 --- a/lib/registry-sync/ecr-mirror.ts +++ b/lib/registry-sync/ecr-mirror.ts @@ -8,9 +8,11 @@ import { aws_s3_assets as s3Assets, aws_secretsmanager as sm, custom_resources as cr, + Annotations, } from 'aws-cdk-lib'; import { Construct, IConstruct } from 'constructs'; import { MirrorSource } from './mirror-source'; +import { DEFAULT_SUPERCHAIN_IMAGE } from '../constants'; /** * Authentication details for DockerHub. @@ -56,6 +58,15 @@ export interface EcrMirrorProps { */ readonly dockerHubCredentials: DockerHubCredentials; + /** + * The image used to run the mirror step itself. + * + * Prefer to supply the image yourself here. + * + * @default - Some superchain image that may grow outdated. + */ + readonly buildImage?: codebuild.IBuildImage; + /** * Sync job runs on a schedule. * Throws an error if neither this nor `autoStart` are specified. @@ -101,10 +112,15 @@ export class EcrMirror extends Construct { const username = codeBuildSecretValue(props.dockerHubCredentials.usernameKey, props.dockerHubCredentials); const password = codeBuildSecretValue(props.dockerHubCredentials.passwordKey, props.dockerHubCredentials); + if (!props.buildImage) { + Annotations.of(this).addWarningV2('aws-delivlib:EcrMirror.missingBuildImage', 'Prefer supplying an explicit build image to relying on the default superchain.'); + } + this.project = new codebuild.Project(this, 'EcrPushImages', { + description: Lazy.string({ produce: () => `Synchronize ${props.sources.length} images from DockerHub to local ECR` }), environment: { privileged: true, - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('public.ecr.aws/jsii/superchain:1-bullseye-slim-node18'), + buildImage: props.buildImage ?? codebuild.LinuxBuildImage.fromDockerRegistry(DEFAULT_SUPERCHAIN_IMAGE), }, environmentVariables: { // DockerHub credentials to avoid throttling @@ -204,6 +220,7 @@ export class EcrMirror extends Construct { if (props.schedule) { new events.Rule(this, 'ScheduledTrigger', { + description: 'Trigger ECR mirror job', schedule: props.schedule, targets: [new targets.CodeBuildProject(this.project)], }); diff --git a/lib/signing.ts b/lib/signing.ts index 53aff3629..cbb9be3c8 100644 --- a/lib/signing.ts +++ b/lib/signing.ts @@ -7,6 +7,7 @@ import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { IBucket } from 'aws-cdk-lib/aws-s3'; import { Construct, IConstruct } from 'constructs'; import { BuildSpec } from './build-spec'; +import { DEFAULT_SUPERCHAIN_IMAGE } from './constants'; import { AddToPipelineOptions } from './pipeline'; import { LinuxPlatform, Shellable } from './shellable'; @@ -100,7 +101,7 @@ export class SignNuGetWithSigner extends Construct implements ISigner { } const shellable = new Shellable(this, 'Default', { - platform: new LinuxPlatform(props.buildImage ?? LinuxBuildImage.fromDockerRegistry('public.ecr.aws/jsii/superchain:1-bullseye-slim-node18')), + platform: new LinuxPlatform(props.buildImage ?? LinuxBuildImage.fromDockerRegistry(DEFAULT_SUPERCHAIN_IMAGE)), scriptDirectory: path.join(__dirname, 'signing', 'nuget'), entrypoint: 'sign.sh', serviceRole: props.serviceRole,