packages/aws-cdk-lib/aws-codebuild/lib/project.ts (1,201 lines of code) (raw):
import { Construct, IConstruct } from 'constructs';
import { IArtifacts } from './artifacts';
import { BuildSpec } from './build-spec';
import { Cache } from './cache';
import { CodeBuildMetrics } from './codebuild-canned-metrics.generated';
import { CfnProject } from './codebuild.generated';
import { CodePipelineArtifacts } from './codepipeline-artifacts';
import { ComputeType } from './compute-type';
import { EnvironmentType } from './environment-type';
import { IFileSystemLocation } from './file-location';
import { IFleet } from './fleet';
import { LinuxArmLambdaBuildImage } from './linux-arm-lambda-build-image';
import { LinuxLambdaBuildImage } from './linux-lambda-build-image';
import { NoArtifacts } from './no-artifacts';
import { NoSource } from './no-source';
import { runScriptLinuxBuildSpec, S3_BUCKET_ENV, S3_KEY_ENV } from './private/run-script-linux-build-spec';
import { LoggingOptions } from './project-logs';
import { renderReportGroupArn } from './report-group-utils';
import { ISource } from './source';
import { CODEPIPELINE_SOURCE_ARTIFACTS_TYPE, NO_SOURCE_TYPE } from './source-types';
import * as cloudwatch from '../../aws-cloudwatch';
import * as notifications from '../../aws-codestarnotifications';
import * as ec2 from '../../aws-ec2';
import * as ecr from '../../aws-ecr';
import { DockerImageAsset, DockerImageAssetProps } from '../../aws-ecr-assets';
import * as events from '../../aws-events';
import * as iam from '../../aws-iam';
import * as kms from '../../aws-kms';
import * as s3 from '../../aws-s3';
import * as secretsmanager from '../../aws-secretsmanager';
import { ArnFormat, Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization, UnscopedValidationError, ValidationError } from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
const VPC_POLICY_SYM = Symbol.for('@aws-cdk/aws-codebuild.roleVpcPolicy');
/**
* The type returned from `IProject#enableBatchBuilds`.
*/
export interface BatchBuildConfig {
/** The IAM batch service Role of this Project. */
readonly role: iam.IRole;
}
/**
* Location of a PEM certificate on S3
*/
export interface BuildEnvironmentCertificate {
/**
* The bucket where the certificate is
*/
readonly bucket: s3.IBucket;
/**
* The full path and name of the key file
*/
readonly objectKey: string;
}
/**
* Additional options to pass to the notification rule.
*/
export interface ProjectNotifyOnOptions extends notifications.NotificationRuleOptions {
/**
* A list of event types associated with this notification rule for CodeBuild Project.
* For a complete list of event types and IDs, see Notification concepts in the Developer Tools Console User Guide.
* @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#concepts-api
*/
readonly events: ProjectNotificationEvents[];
}
export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable, notifications.INotificationRuleSource {
/**
* The ARN of this Project.
* @attribute
*/
readonly projectArn: string;
/**
* The human-visible name of this Project.
* @attribute
*/
readonly projectName: string;
/** The IAM service Role of this Project. Undefined for imported Projects. */
readonly role?: iam.IRole;
/**
* Enable batch builds.
*
* Returns an object contining the batch service role if batch builds
* could be enabled.
*/
enableBatchBuilds(): BatchBuildConfig | undefined;
addToRolePolicy(policyStatement: iam.PolicyStatement): void;
/**
* Defines a CloudWatch event rule triggered when something happens with this project.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
onEvent(id: string, options?: events.OnEventOptions): events.Rule;
/**
* Defines a CloudWatch event rule triggered when the build project state
* changes. You can filter specific build status events using an event
* pattern filter on the `build-status` detail field:
*
* const rule = project.onStateChange('OnBuildStarted', { target });
* rule.addEventPattern({
* detail: {
* 'build-status': [
* "IN_PROGRESS",
* "SUCCEEDED",
* "FAILED",
* "STOPPED"
* ]
* }
* });
*
* You can also use the methods `onBuildFailed` and `onBuildSucceeded` to define rules for
* these specific state changes.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
onStateChange(id: string, options?: events.OnEventOptions): events.Rule;
/**
* Defines a CloudWatch event rule that triggers upon phase change of this
* build project.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
onPhaseChange(id: string, options?: events.OnEventOptions): events.Rule;
/**
* Defines an event rule which triggers when a build starts.
*/
onBuildStarted(id: string, options?: events.OnEventOptions): events.Rule;
/**
* Defines an event rule which triggers when a build fails.
*/
onBuildFailed(id: string, options?: events.OnEventOptions): events.Rule;
/**
* Defines an event rule which triggers when a build completes successfully.
*/
onBuildSucceeded(id: string, options?: events.OnEventOptions): events.Rule;
/**
* @returns a CloudWatch metric associated with this build project.
* @param metricName The name of the metric
* @param props Customization properties
*/
metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric;
/**
* Measures the number of builds triggered.
*
* Units: Count
*
* Valid CloudWatch statistics: Sum
*
* @default sum over 5 minutes
*/
metricBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
/**
* Measures the duration of all builds over time.
*
* Units: Seconds
*
* Valid CloudWatch statistics: Average (recommended), Maximum, Minimum
*
* @default average over 5 minutes
*/
metricDuration(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
/**
* Measures the number of successful builds.
*
* Units: Count
*
* Valid CloudWatch statistics: Sum
*
* @default sum over 5 minutes
*/
metricSucceededBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
/**
* Measures the number of builds that failed because of client error or
* because of a timeout.
*
* Units: Count
*
* Valid CloudWatch statistics: Sum
*
* @default sum over 5 minutes
*/
metricFailedBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
/**
* Defines a CodeStar Notification rule triggered when the project
* events emitted by you specified, it very similar to `onEvent` API.
*
* You can also use the methods `notifyOnBuildSucceeded` and
* `notifyOnBuildFailed` to define rules for these specific event emitted.
*
* @param id The logical identifier of the CodeStar Notifications rule that will be created
* @param target The target to register for the CodeStar Notifications destination.
* @param options Customization options for CodeStar Notifications rule
* @returns CodeStar Notifications rule associated with this build project.
*/
notifyOn(
id: string,
target: notifications.INotificationRuleTarget,
options: ProjectNotifyOnOptions,
): notifications.INotificationRule;
/**
* Defines a CodeStar notification rule which triggers when a build completes successfully.
*/
notifyOnBuildSucceeded(
id: string,
target: notifications.INotificationRuleTarget,
options?: notifications.NotificationRuleOptions,
): notifications.INotificationRule;
/**
* Defines a CodeStar notification rule which triggers when a build fails.
*/
notifyOnBuildFailed(
id: string,
target: notifications.INotificationRuleTarget,
options?: notifications.NotificationRuleOptions,
): notifications.INotificationRule;
}
/**
* Represents a reference to a CodeBuild Project.
*
* If you're managing the Project alongside the rest of your CDK resources,
* use the `Project` class.
*
* If you want to reference an already existing Project
* (or one defined in a different CDK Stack),
* use the `import` method.
*/
abstract class ProjectBase extends Resource implements IProject {
public abstract readonly grantPrincipal: iam.IPrincipal;
/** The ARN of this Project. */
public abstract readonly projectArn: string;
/** The human-visible name of this Project. */
public abstract readonly projectName: string;
/** The IAM service Role of this Project. */
public abstract readonly role?: iam.IRole;
/**
* Actual connections object for this Project.
* May be unset, in which case this Project is not configured to use a VPC.
* @internal
*/
protected _connections: ec2.Connections | undefined;
/**
* Access the Connections object.
* Will fail if this Project does not have a VPC set.
*/
public get connections(): ec2.Connections {
if (!this._connections) {
throw new ValidationError('Only VPC-associated Projects have security groups to manage. Supply the "vpc" parameter when creating the Project', this);
}
return this._connections;
}
public enableBatchBuilds(): BatchBuildConfig | undefined {
return undefined;
}
/**
* Add a permission only if there's a policy attached.
* @param statement The permissions statement to add
*/
public addToRolePolicy(statement: iam.PolicyStatement) {
if (this.role) {
this.role.addToPrincipalPolicy(statement);
}
}
/**
* Defines a CloudWatch event rule triggered when something happens with this project.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
public onEvent(id: string, options: events.OnEventOptions = {}): events.Rule {
const rule = new events.Rule(this, id, options);
rule.addTarget(options.target);
rule.addEventPattern({
source: ['aws.codebuild'],
detail: {
'project-name': [this.projectName],
},
});
return rule;
}
/**
* Defines a CloudWatch event rule triggered when the build project state
* changes. You can filter specific build status events using an event
* pattern filter on the `build-status` detail field:
*
* const rule = project.onStateChange('OnBuildStarted', { target });
* rule.addEventPattern({
* detail: {
* 'build-status': [
* "IN_PROGRESS",
* "SUCCEEDED",
* "FAILED",
* "STOPPED"
* ]
* }
* });
*
* You can also use the methods `onBuildFailed` and `onBuildSucceeded` to define rules for
* these specific state changes.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
public onStateChange(id: string, options: events.OnEventOptions = {}) {
const rule = this.onEvent(id, options);
rule.addEventPattern({
detailType: ['CodeBuild Build State Change'],
});
return rule;
}
/**
* Defines a CloudWatch event rule that triggers upon phase change of this
* build project.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
*/
public onPhaseChange(id: string, options: events.OnEventOptions = {}) {
const rule = this.onEvent(id, options);
rule.addEventPattern({
detailType: ['CodeBuild Build Phase Change'],
});
return rule;
}
/**
* Defines an event rule which triggers when a build starts.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*/
public onBuildStarted(id: string, options: events.OnEventOptions = {}) {
const rule = this.onStateChange(id, options);
rule.addEventPattern({
detail: {
'build-status': ['IN_PROGRESS'],
},
});
return rule;
}
/**
* Defines an event rule which triggers when a build fails.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*/
public onBuildFailed(id: string, options: events.OnEventOptions = {}) {
const rule = this.onStateChange(id, options);
rule.addEventPattern({
detail: {
'build-status': ['FAILED'],
},
});
return rule;
}
/**
* Defines an event rule which triggers when a build completes successfully.
*
* To access fields from the event in the event target input,
* use the static fields on the `StateChangeEvent` class.
*/
public onBuildSucceeded(id: string, options: events.OnEventOptions = {}) {
const rule = this.onStateChange(id, options);
rule.addEventPattern({
detail: {
'build-status': ['SUCCEEDED'],
},
});
return rule;
}
/**
* @returns a CloudWatch metric associated with this build project.
* @param metricName The name of the metric
* @param props Customization properties
*/
public metric(metricName: string, props?: cloudwatch.MetricOptions) {
return new cloudwatch.Metric({
namespace: 'AWS/CodeBuild',
metricName,
dimensionsMap: { ProjectName: this.projectName },
...props,
}).attachTo(this);
}
/**
* Measures the number of builds triggered.
*
* Units: Count
*
* Valid CloudWatch statistics: Sum
*
* @default sum over 5 minutes
*/
public metricBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return this.cannedMetric(CodeBuildMetrics.buildsSum, props);
}
/**
* Measures the duration of all builds over time.
*
* Units: Seconds
*
* Valid CloudWatch statistics: Average (recommended), Maximum, Minimum
*
* @default average over 5 minutes
*/
public metricDuration(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return this.cannedMetric(CodeBuildMetrics.durationAverage, props);
}
/**
* Measures the number of successful builds.
*
* Units: Count
*
* Valid CloudWatch statistics: Sum
*
* @default sum over 5 minutes
*/
public metricSucceededBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return this.cannedMetric(CodeBuildMetrics.succeededBuildsSum, props);
}
/**
* Measures the number of builds that failed because of client error or
* because of a timeout.
*
* Units: Count
*
* Valid CloudWatch statistics: Sum
*
* @default sum over 5 minutes
*/
public metricFailedBuilds(props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return this.cannedMetric(CodeBuildMetrics.failedBuildsSum, props);
}
public notifyOn(
id: string,
target: notifications.INotificationRuleTarget,
options: ProjectNotifyOnOptions,
): notifications.INotificationRule {
return new notifications.NotificationRule(this, id, {
...options,
source: this,
targets: [target],
});
}
public notifyOnBuildSucceeded(
id: string,
target: notifications.INotificationRuleTarget,
options?: notifications.NotificationRuleOptions,
): notifications.INotificationRule {
return this.notifyOn(id, target, {
...options,
events: [ProjectNotificationEvents.BUILD_SUCCEEDED],
});
}
public notifyOnBuildFailed(
id: string,
target: notifications.INotificationRuleTarget,
options?: notifications.NotificationRuleOptions,
): notifications.INotificationRule {
return this.notifyOn(id, target, {
...options,
events: [ProjectNotificationEvents.BUILD_FAILED],
});
}
public bindAsNotificationRuleSource(_scope: Construct): notifications.NotificationRuleSourceConfig {
return {
sourceArn: this.projectArn,
};
}
private cannedMetric(
fn: (dims: { ProjectName: string }) => cloudwatch.MetricProps,
props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return new cloudwatch.Metric({
...fn({ ProjectName: this.projectName }),
...props,
}).attachTo(this);
}
}
export interface CommonProjectProps {
/**
* A description of the project. Use the description to identify the purpose
* of the project.
*
* @default - No description.
*/
readonly description?: string;
/**
* Filename or contents of buildspec in JSON format.
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec-ref-example
*
* @default - Empty buildspec.
*/
readonly buildSpec?: BuildSpec;
/**
* Service Role to assume while running the build.
*
* @default - A role will be created.
*/
readonly role?: iam.IRole;
/**
* Encryption key to use to read and write artifacts.
*
* @default - The AWS-managed CMK for Amazon Simple Storage Service (Amazon S3) is used.
*/
readonly encryptionKey?: kms.IKey;
/**
* Caching strategy to use.
*
* @default Cache.none
*/
readonly cache?: Cache;
/**
* Build environment to use for the build.
*
* @default BuildEnvironment.LinuxBuildImage.STANDARD_7_0
*/
readonly environment?: BuildEnvironment;
/**
* Indicates whether AWS CodeBuild generates a publicly accessible URL for
* your project's build badge. For more information, see Build Badges Sample
* in the AWS CodeBuild User Guide.
*
* @default false
*/
readonly badge?: boolean;
/**
* The number of minutes after which AWS CodeBuild stops the build if it's
* not complete. For valid values, see the timeoutInMinutes field in the AWS
* CodeBuild User Guide.
*
* @default Duration.hours(1)
*/
readonly timeout?: Duration;
/**
* Additional environment variables to add to the build environment.
*
* @default - No additional environment variables are specified.
*/
readonly environmentVariables?: { [name: string]: BuildEnvironmentVariable };
/**
* Whether to check for the presence of any secrets in the environment variables of the default type, BuildEnvironmentVariableType.PLAINTEXT.
* Since using a secret for the value of that kind of variable would result in it being displayed in plain text in the AWS Console,
* the construct will throw an exception if it detects a secret was passed there.
* Pass this property as false if you want to skip this validation,
* and keep using a secret in a plain text environment variable.
*
* @default true
*/
readonly checkSecretsInPlainTextEnvVariables?: boolean;
/**
* The physical, human-readable name of the CodeBuild Project.
*
* @default - Name is automatically generated.
*/
readonly projectName?: string;
/**
* VPC network to place codebuild network interfaces
*
* Specify this if the codebuild project needs to access resources in a VPC.
*
* @default - No VPC is specified.
*/
readonly vpc?: ec2.IVpc;
/**
* Where to place the network interfaces within the VPC.
*
* To access AWS services, your CodeBuild project needs to be in one of the following types of subnets:
*
* 1. Subnets with access to the internet (of type PRIVATE_WITH_EGRESS).
* 2. Private subnets unconnected to the internet, but with [VPC endpoints](https://docs.aws.amazon.com/codebuild/latest/userguide/use-vpc-endpoints-with-codebuild.html) for the necessary services.
*
* If you don't specify a subnet selection, the default behavior is to use PRIVATE_WITH_EGRESS subnets first if they exist,
* then PRIVATE_WITHOUT_EGRESS, and finally PUBLIC subnets. If your VPC doesn't have PRIVATE_WITH_EGRESS subnets but you need
* AWS service access, add VPC Endpoints to your private subnets.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/vpc-support.html
*
* @default - private subnets if available else public subnets
*/
readonly subnetSelection?: ec2.SubnetSelection;
/**
* What security group to associate with the codebuild project's network interfaces.
* If no security group is identified, one will be created automatically.
*
* Only used if 'vpc' is supplied.
*
* @default - Security group will be automatically created.
*
*/
readonly securityGroups?: ec2.ISecurityGroup[];
/**
* Whether to allow the CodeBuild to send all network traffic
*
* If set to false, you must individually add traffic rules to allow the
* CodeBuild project to connect to network targets.
*
* Only used if 'vpc' is supplied.
*
* @default true
*/
readonly allowAllOutbound?: boolean;
/**
* An ProjectFileSystemLocation objects for a CodeBuild build project.
*
* A ProjectFileSystemLocation object specifies the identifier, location, mountOptions, mountPoint,
* and type of a file system created using Amazon Elastic File System.
*
* @default - no file system locations
*/
readonly fileSystemLocations?: IFileSystemLocation[];
/**
* Add permissions to this project's role to create and use test report groups with name starting with the name of this project.
*
* That is the standard report group that gets created when a simple name
* (in contrast to an ARN)
* is used in the 'reports' section of the buildspec of this project.
* This is usually harmless, but you can turn these off if you don't plan on using test
* reports in this project.
*
* @default true
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/test-report-group-naming.html
*/
readonly grantReportGroupPermissions?: boolean;
/**
* Information about logs for the build project. A project can create logs in Amazon CloudWatch Logs, an S3 bucket, or both.
*
* @default - no log configuration is set
*/
readonly logging?: LoggingOptions;
/**
* The number of minutes after which AWS CodeBuild stops the build if it's
* still in queue. For valid values, see the timeoutInMinutes field in the AWS
* CodeBuild User Guide.
*
* @default - no queue timeout is set
*/
readonly queuedTimeout?: Duration;
/**
* Maximum number of concurrent builds. Minimum value is 1 and maximum is account build limit.
*
* @default - no explicit limit is set
*/
readonly concurrentBuildLimit?: number;
/**
* Add the permissions necessary for debugging builds with SSM Session Manager
*
* If the following prerequisites have been met:
*
* - The necessary permissions have been added by setting this flag to true.
* - The build image has the SSM agent installed (true for default CodeBuild images).
* - The build is started with [debugSessionEnabled](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_StartBuild.html#CodeBuild-StartBuild-request-debugSessionEnabled) set to true.
*
* Then the build container can be paused and inspected using Session Manager
* by invoking the `codebuild-breakpoint` command somewhere during the build.
*
* `codebuild-breakpoint` commands will be ignored if the build is not started
* with `debugSessionEnabled=true`.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/session-manager.html
* @default false
*/
readonly ssmSessionPermissions?: boolean;
/**
* Specifies the visibility of the project's builds.
*
* @default - no visibility is set
*/
readonly visibility?: ProjectVisibility;
/**
* CodeBuild will automatically call retry build using the project's service role up to the auto-retry limit.
* `autoRetryLimit` must be between 0 and 10.
*
* @default - no retry
*/
readonly autoRetryLimit?: number;
}
export interface ProjectProps extends CommonProjectProps {
/**
* The source of the build.
* *Note*: if `NoSource` is given as the source,
* then you need to provide an explicit `buildSpec`.
*
* @default - NoSource
*/
readonly source?: ISource;
/**
* Defines where build artifacts will be stored.
* Could be: PipelineBuildArtifacts, NoArtifacts and S3Artifacts.
*
* @default NoArtifacts
*/
readonly artifacts?: IArtifacts;
/**
* The secondary sources for the Project.
* Can be also added after the Project has been created by using the `Project#addSecondarySource` method.
*
* @default - No secondary sources.
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-multi-in-out.html
*/
readonly secondarySources?: ISource[];
/**
* The secondary artifacts for the Project.
* Can also be added after the Project has been created by using the `Project#addSecondaryArtifact` method.
*
* @default - No secondary artifacts.
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-multi-in-out.html
*/
readonly secondaryArtifacts?: IArtifacts[];
}
/**
* The extra options passed to the `IProject.bindToCodePipeline` method.
*/
export interface BindToCodePipelineOptions {
/**
* The artifact bucket that will be used by the action that invokes this project.
*/
readonly artifactBucket: s3.IBucket;
}
/**
* A representation of a CodeBuild Project.
*/
export class Project extends ProjectBase {
public static fromProjectArn(scope: Construct, id: string, projectArn: string): IProject {
const parsedArn = Stack.of(scope).splitArn(projectArn, ArnFormat.SLASH_RESOURCE_NAME);
class Import extends ProjectBase {
public readonly grantPrincipal: iam.IPrincipal;
public readonly projectArn = projectArn;
public readonly projectName = parsedArn.resourceName!;
public readonly role?: iam.Role = undefined;
constructor(s: Construct, i: string) {
super(s, i, {
account: parsedArn.account,
region: parsedArn.region,
});
this.grantPrincipal = new iam.UnknownPrincipal({ resource: this });
}
}
return new Import(scope, id);
}
/**
* Import a Project defined either outside the CDK,
* or in a different CDK Stack
* (and exported using the `export` method).
*
* @note if you're importing a CodeBuild Project for use
* in a CodePipeline, make sure the existing Project
* has permissions to access the S3 Bucket of that Pipeline -
* otherwise, builds in that Pipeline will always fail.
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param projectName the name of the project to import
* @returns a reference to the existing Project
*/
public static fromProjectName(scope: Construct, id: string, projectName: string): IProject {
class Import extends ProjectBase {
public readonly grantPrincipal: iam.IPrincipal;
public readonly projectArn: string;
public readonly projectName: string;
public readonly role?: iam.Role = undefined;
constructor(s: Construct, i: string) {
super(s, i);
this.projectArn = Stack.of(this).formatArn({
service: 'codebuild',
resource: 'project',
resourceName: projectName,
});
this.grantPrincipal = new iam.UnknownPrincipal({ resource: this });
this.projectName = projectName;
}
}
return new Import(scope, id);
}
/**
* Convert the environment variables map of string to `BuildEnvironmentVariable`,
* which is the customer-facing type, to a list of `CfnProject.EnvironmentVariableProperty`,
* which is the representation of environment variables in CloudFormation.
*
* @param environmentVariables the map of string to environment variables
* @param validateNoPlainTextSecrets whether to throw an exception
* if any of the plain text environment variables contain secrets, defaults to 'false'
* @returns an array of `CfnProject.EnvironmentVariableProperty` instances
*/
public static serializeEnvVariables(environmentVariables: { [name: string]: BuildEnvironmentVariable },
validateNoPlainTextSecrets: boolean = false, principal?: iam.IGrantable): CfnProject.EnvironmentVariableProperty[] {
const ret = new Array<CfnProject.EnvironmentVariableProperty>();
const ssmIamResources = new Array<string>();
const secretsManagerIamResources = new Set<string>();
const kmsIamResources = new Set<string>();
for (const [name, envVariable] of Object.entries(environmentVariables)) {
const envVariableValue = envVariable.value?.toString();
const cfnEnvVariable: CfnProject.EnvironmentVariableProperty = {
name,
type: envVariable.type || BuildEnvironmentVariableType.PLAINTEXT,
value: envVariableValue,
};
ret.push(cfnEnvVariable);
// validate that the plain-text environment variables don't contain any secrets in them
if (validateNoPlainTextSecrets && cfnEnvVariable.type === BuildEnvironmentVariableType.PLAINTEXT) {
const fragments = Tokenization.reverseString(cfnEnvVariable.value);
for (const token of fragments.tokens) {
if (token instanceof SecretValue) {
throw new UnscopedValidationError(`Plaintext environment variable '${name}' contains a secret value! ` +
'This means the value of this variable will be visible in plain text in the AWS Console. ' +
"Please consider using CodeBuild's SecretsManager environment variables feature instead. " +
"If you'd like to continue with having this secret in the plaintext environment variables, " +
'please set the checkSecretsInPlainTextEnvVariables property to false');
}
}
}
if (principal) {
const stack = Stack.of(principal as unknown as IConstruct);
// save the SSM env variables
if (envVariable.type === BuildEnvironmentVariableType.PARAMETER_STORE) {
ssmIamResources.push(stack.formatArn({
service: 'ssm',
resource: 'parameter',
// If the parameter name starts with / the resource name is not separated with a double '/'
// arn:aws:ssm:region:1111111111:parameter/PARAM_NAME
resourceName: envVariableValue.startsWith('/')
? envVariableValue.slice(1)
: envVariableValue,
}));
}
// save SecretsManager env variables
if (envVariable.type === BuildEnvironmentVariableType.SECRETS_MANAGER) {
// We have 3 basic cases here of what envVariableValue can be:
// 1. A string that starts with 'arn:' (and might contain Token fragments).
// 2. A Token.
// 3. A simple value, like 'secret-id'.
if (envVariableValue.startsWith('arn:')) {
const parsedArn = stack.splitArn(envVariableValue, ArnFormat.COLON_RESOURCE_NAME);
if (!parsedArn.resourceName) {
throw new UnscopedValidationError('SecretManager ARN is missing the name of the secret: ' + envVariableValue);
}
// the value of the property can be a complex string, separated by ':';
// see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager
const secretName = parsedArn.resourceName.split(':')[0];
secretsManagerIamResources.add(stack.formatArn({
service: 'secretsmanager',
resource: 'secret',
// since we don't know whether the ARN was full, or partial
// (CodeBuild supports both),
// stick a "*" at the end, which makes it work for both
resourceName: `${secretName}*`,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
partition: parsedArn.partition,
account: parsedArn.account,
region: parsedArn.region,
}));
// if secret comes from another account, SecretsManager will need to access
// KMS on the other account as well to be able to get the secret
if (parsedArn.account && Token.compareStrings(parsedArn.account, stack.account) === TokenComparison.DIFFERENT) {
kmsIamResources.add(stack.formatArn({
service: 'kms',
resource: 'key',
// We do not know the ID of the key, but since this is a cross-account access,
// the key policies have to allow this access, so a wildcard is safe here
resourceName: '*',
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
partition: parsedArn.partition,
account: parsedArn.account,
region: parsedArn.region,
}));
}
} else if (Token.isUnresolved(envVariableValue)) {
// the value of the property can be a complex string, separated by ':';
// see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager
let secretArn = envVariableValue.split(':')[0];
// parse the Token, and see if it represents a single resource
// (we will assume it's a Secret from SecretsManager)
const fragments = Tokenization.reverseString(envVariableValue);
if (fragments.tokens.length === 1) {
const resolvable = fragments.tokens[0];
if (Reference.isReference(resolvable)) {
// check the Stack the resource owning the reference belongs to
const resourceStack = Stack.of(resolvable.target);
if (Token.compareStrings(stack.account, resourceStack.account) === TokenComparison.DIFFERENT) {
// since this is a cross-account access,
// add the appropriate KMS permissions
kmsIamResources.add(stack.formatArn({
service: 'kms',
resource: 'key',
// We do not know the ID of the key, but since this is a cross-account access,
// the key policies have to allow this access, so a wildcard is safe here
resourceName: '*',
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
partition: resourceStack.partition,
account: resourceStack.account,
region: resourceStack.region,
}));
// Work around a bug in SecretsManager -
// when the access is cross-environment,
// Secret.secretArn returns a partial ARN!
// So add a "*" at the end, so that the permissions work
secretArn = `${secretArn}-??????`;
}
}
}
// if we are passed a Token, we should assume it's the ARN of the Secret
// (as the name would not work anyway, because it would be the full name, which CodeBuild does not support)
secretsManagerIamResources.add(secretArn);
} else {
// the value of the property can be a complex string, separated by ':';
// see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager
const secretName = envVariableValue.split(':')[0];
secretsManagerIamResources.add(stack.formatArn({
service: 'secretsmanager',
resource: 'secret',
resourceName: `${secretName}-??????`,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
}));
}
}
}
}
if (ssmIamResources.length !== 0) {
principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['ssm:GetParameters'],
resources: ssmIamResources,
}));
}
if (secretsManagerIamResources.size !== 0) {
principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['secretsmanager:GetSecretValue'],
resources: Array.from(secretsManagerIamResources),
}));
}
if (kmsIamResources.size !== 0) {
principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['kms:Decrypt'],
resources: Array.from(kmsIamResources),
}));
}
return ret;
}
public readonly grantPrincipal: iam.IPrincipal;
/**
* The IAM role for this project.
*/
public readonly role?: iam.IRole;
/**
* The ARN of the project.
*/
public readonly projectArn: string;
/**
* The name of the project.
*/
public readonly projectName: string;
private readonly source: ISource;
private readonly buildImage: IBuildImage;
private readonly _secondarySources: CfnProject.SourceProperty[];
private readonly _secondarySourceVersions: CfnProject.ProjectSourceVersionProperty[];
private readonly _secondaryArtifacts: CfnProject.ArtifactsProperty[];
private _encryptionKey?: kms.IKey;
private readonly _fileSystemLocations: CfnProject.ProjectFileSystemLocationProperty[];
private _batchServiceRole?: iam.Role;
constructor(scope: Construct, id: string, props: ProjectProps) {
super(scope, id, {
physicalName: props.projectName,
});
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
this.role = props.role || new iam.Role(this, 'Role', {
roleName: PhysicalName.GENERATE_IF_NEEDED,
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
});
this.grantPrincipal = this.role;
this.buildImage = (props.environment && props.environment.buildImage) || LinuxBuildImage.STANDARD_7_0;
// let source "bind" to the project. this usually involves granting permissions
// for the code build role to interact with the source.
this.source = props.source || new NoSource();
const sourceConfig = this.source.bind(this, this);
if (props.badge && !this.source.badgeSupported) {
throw new ValidationError(`Badge is not supported for source type ${this.source.type}`, this);
}
const artifacts = props.artifacts
? props.artifacts
: (this.source.type === CODEPIPELINE_SOURCE_ARTIFACTS_TYPE
? new CodePipelineArtifacts()
: new NoArtifacts());
const artifactsConfig = artifacts.bind(this, this);
const cache = props.cache || Cache.none();
// give the caching strategy the option to grant permissions to any required resources
cache._bind(this);
// Inject download commands for asset if requested
const environmentVariables = props.environmentVariables || {};
const buildSpec = props.buildSpec;
if (this.source.type === NO_SOURCE_TYPE && (buildSpec === undefined || !buildSpec.isImmediate)) {
throw new ValidationError("If the Project's source is NoSource, you need to provide a concrete buildSpec", this);
}
this._secondarySources = [];
this._secondarySourceVersions = [];
this._fileSystemLocations = [];
for (const secondarySource of props.secondarySources || []) {
this.addSecondarySource(secondarySource);
}
this._secondaryArtifacts = [];
for (const secondaryArtifact of props.secondaryArtifacts || []) {
this.addSecondaryArtifact(secondaryArtifact);
}
this.validateCodePipelineSettings(artifacts);
for (const fileSystemLocation of props.fileSystemLocations || []) {
this.addFileSystemLocation(fileSystemLocation);
}
if (!Token.isUnresolved(props.autoRetryLimit) && (props.autoRetryLimit !== undefined)) {
if (props.autoRetryLimit < 0 || props.autoRetryLimit > 10) {
throw new ValidationError(`autoRetryLimit must be a value between 0 and 10, got ${props.autoRetryLimit}.`, this);
}
}
const resource = new CfnProject(this, 'Resource', {
description: props.description,
source: {
...sourceConfig.sourceProperty,
buildSpec: buildSpec && buildSpec.toBuildSpec(this),
},
artifacts: artifactsConfig.artifactsProperty,
serviceRole: this.role.roleArn,
environment: this.renderEnvironment(props, environmentVariables),
fileSystemLocations: Lazy.any({ produce: () => this.renderFileSystemLocations() }),
// lazy, because we have a setter for it in setEncryptionKey
// The 'alias/aws/s3' default is necessary because leaving the `encryptionKey` field
// empty will not remove existing encryptionKeys during an update (ref. t/D17810523)
encryptionKey: Lazy.string({ produce: () => this._encryptionKey ? this._encryptionKey.keyArn : 'alias/aws/s3' }),
badgeEnabled: props.badge,
cache: cache._toCloudFormation(),
name: this.physicalName,
timeoutInMinutes: props.timeout && props.timeout.toMinutes(),
queuedTimeoutInMinutes: props.queuedTimeout && props.queuedTimeout.toMinutes(),
concurrentBuildLimit: props.concurrentBuildLimit,
secondarySources: Lazy.any({ produce: () => this.renderSecondarySources() }),
secondarySourceVersions: Lazy.any({ produce: () => this.renderSecondarySourceVersions() }),
secondaryArtifacts: Lazy.any({ produce: () => this.renderSecondaryArtifacts() }),
triggers: sourceConfig.buildTriggers,
sourceVersion: sourceConfig.sourceVersion,
vpcConfig: this.configureVpc(props),
visibility: props.visibility,
logsConfig: this.renderLoggingConfiguration(props.logging),
buildBatchConfig: Lazy.any({
produce: () => {
const config: CfnProject.ProjectBuildBatchConfigProperty | undefined = this._batchServiceRole ? {
serviceRole: this._batchServiceRole.roleArn,
} : undefined;
return config;
},
}),
autoRetryLimit: props.autoRetryLimit,
});
this.addVpcRequiredPermissions(props, resource);
this.projectArn = this.getResourceArnAttribute(resource.attrArn, {
service: 'codebuild',
resource: 'project',
resourceName: this.physicalName,
});
this.projectName = this.getResourceNameAttribute(resource.ref);
this.addToRolePolicy(this.createLoggingPermission());
// add permissions to create and use test report groups
// with names starting with the project's name,
// unless the customer explicitly opts out of it
if (props.grantReportGroupPermissions !== false) {
this.addToRolePolicy(new iam.PolicyStatement({
actions: [
'codebuild:CreateReportGroup',
'codebuild:CreateReport',
'codebuild:UpdateReport',
'codebuild:BatchPutTestCases',
'codebuild:BatchPutCodeCoverages',
],
resources: [renderReportGroupArn(this, `${this.projectName}-*`)],
}));
}
// https://docs.aws.amazon.com/codebuild/latest/userguide/session-manager.html
if (props.ssmSessionPermissions) {
this.addToRolePolicy(new iam.PolicyStatement({
actions: [
// For the SSM channel
'ssmmessages:CreateControlChannel',
'ssmmessages:CreateDataChannel',
'ssmmessages:OpenControlChannel',
'ssmmessages:OpenDataChannel',
// In case the SSM session is set up to log commands to CloudWatch
'logs:DescribeLogGroups',
'logs:CreateLogStream',
'logs:PutLogEvents',
// In case the SSM session is set up to log commands to S3.
's3:GetEncryptionConfiguration',
's3:PutObject',
],
resources: ['*'],
}));
}
if (props.encryptionKey) {
this.encryptionKey = props.encryptionKey;
}
// bind
if (isBindableBuildImage(this.buildImage)) {
this.buildImage.bind(this, this, {});
}
this.node.addValidation({ validate: () => this.validateProject() });
}
@MethodMetadata()
public enableBatchBuilds(): BatchBuildConfig | undefined {
if (!this._batchServiceRole) {
this._batchServiceRole = new iam.Role(this, 'BatchServiceRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
});
this._batchServiceRole.addToPrincipalPolicy(new iam.PolicyStatement({
resources: [Lazy.string({
produce: () => this.projectArn,
})],
actions: [
'codebuild:StartBuild',
'codebuild:StopBuild',
'codebuild:RetryBuild',
],
}));
}
return {
role: this._batchServiceRole,
};
}
/**
* Adds a secondary source to the Project.
*
* @param secondarySource the source to add as a secondary source
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-multi-in-out.html
*/
@MethodMetadata()
public addSecondarySource(secondarySource: ISource): void {
if (!secondarySource.identifier) {
throw new ValidationError('The identifier attribute is mandatory for secondary sources', this);
}
const secondarySourceConfig = secondarySource.bind(this, this);
this._secondarySources.push(secondarySourceConfig.sourceProperty);
if (secondarySourceConfig.sourceVersion) {
this._secondarySourceVersions.push({
sourceIdentifier: secondarySource.identifier,
sourceVersion: secondarySourceConfig.sourceVersion,
});
}
}
/**
* Adds a fileSystemLocation to the Project.
*
* @param fileSystemLocation the fileSystemLocation to add
*/
@MethodMetadata()
public addFileSystemLocation(fileSystemLocation: IFileSystemLocation): void {
const fileSystemConfig = fileSystemLocation.bind(this, this);
this._fileSystemLocations.push(fileSystemConfig.location);
}
/**
* Adds a secondary artifact to the Project.
*
* @param secondaryArtifact the artifact to add as a secondary artifact
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-multi-in-out.html
*/
@MethodMetadata()
public addSecondaryArtifact(secondaryArtifact: IArtifacts): void {
if (!secondaryArtifact.identifier) {
throw new ValidationError('The identifier attribute is mandatory for secondary artifacts', this);
}
this._secondaryArtifacts.push(secondaryArtifact.bind(this, this).artifactsProperty);
}
/**
* A callback invoked when the given project is added to a CodePipeline.
*
* @param _scope the construct the binding is taking place in
* @param options additional options for the binding
*/
@MethodMetadata()
public bindToCodePipeline(_scope: Construct, options: BindToCodePipelineOptions): void {
// work around a bug in CodeBuild: it ignores the KMS key set on the pipeline,
// and always uses its own, project-level key
if (options.artifactBucket.encryptionKey && !this._encryptionKey) {
// we cannot safely do this assignment if the key is of type kms.Key,
// and belongs to a stack in a different account or region than the project
// (that would cause an illegal reference, as KMS keys don't have physical names)
const keyStack = Stack.of(options.artifactBucket.encryptionKey);
const projectStack = Stack.of(this);
if (!(options.artifactBucket.encryptionKey instanceof kms.Key &&
(keyStack.account !== projectStack.account || keyStack.region !== projectStack.region))) {
this.encryptionKey = options.artifactBucket.encryptionKey;
}
}
}
private validateProject(): string[] {
const ret = new Array<string>();
if (this.source.type === CODEPIPELINE_SOURCE_ARTIFACTS_TYPE) {
if (this._secondarySources.length > 0) {
ret.push('A Project with a CodePipeline Source cannot have secondary sources. ' +
"Use the CodeBuild Pipeline Actions' `extraInputs` property instead");
}
if (this._secondaryArtifacts.length > 0) {
ret.push('A Project with a CodePipeline Source cannot have secondary artifacts. ' +
"Use the CodeBuild Pipeline Actions' `outputs` property instead");
}
}
return ret;
}
private set encryptionKey(encryptionKey: kms.IKey) {
this._encryptionKey = encryptionKey;
encryptionKey.grantEncryptDecrypt(this);
}
private createLoggingPermission() {
const logGroupArn = Stack.of(this).formatArn({
service: 'logs',
resource: 'log-group',
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
resourceName: `/aws/codebuild/${this.projectName}`,
});
const logGroupStarArn = `${logGroupArn}:*`;
return new iam.PolicyStatement({
resources: [logGroupArn, logGroupStarArn],
actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
});
}
private renderEnvironment(
props: ProjectProps,
projectVars: { [name: string]: BuildEnvironmentVariable } = {}): CfnProject.EnvironmentProperty {
const env = props.environment ?? {};
const vars: { [name: string]: BuildEnvironmentVariable } = {};
const containerVars = env.environmentVariables || {};
// first apply environment variables from the container definition
for (const name of Object.keys(containerVars)) {
vars[name] = containerVars[name];
}
// now apply project-level vars
for (const name of Object.keys(projectVars)) {
vars[name] = projectVars[name];
}
const hasEnvironmentVars = Object.keys(vars).length > 0;
const errors = this.buildImage.validate(env);
errors.push(...this.validateLambdaBuildImage(this.buildImage, props));
if (errors.length > 0) {
throw new ValidationError('Invalid CodeBuild environment: ' + errors.join('\n'), this);
}
const imagePullPrincipalType = this.isLambdaBuildImage(this.buildImage) ? undefined :
this.buildImage.imagePullPrincipalType === ImagePullPrincipalType.CODEBUILD
? ImagePullPrincipalType.CODEBUILD
: ImagePullPrincipalType.SERVICE_ROLE;
if (this.buildImage.repository) {
if (imagePullPrincipalType === ImagePullPrincipalType.SERVICE_ROLE) {
this.buildImage.repository.grantPull(this);
} else {
const statement = new iam.PolicyStatement({
principals: [new iam.ServicePrincipal('codebuild.amazonaws.com')],
actions: ['ecr:GetDownloadUrlForLayer', 'ecr:BatchGetImage', 'ecr:BatchCheckLayerAvailability'],
});
statement.sid = 'CodeBuild';
this.buildImage.repository.addToResourcePolicy(statement);
}
}
if (imagePullPrincipalType === ImagePullPrincipalType.SERVICE_ROLE) {
this.buildImage.secretsManagerCredentials?.grantRead(this);
}
const secret = this.buildImage.secretsManagerCredentials;
return {
type: this.buildImage.type,
image: this.buildImage.imageId,
imagePullCredentialsType: imagePullPrincipalType,
registryCredential: secret
? {
credentialProvider: 'SECRETS_MANAGER',
// Secrets must be referenced by either the full ARN (with SecretsManager suffix), or by name.
// "Partial" ARNs (without the suffix) will fail a validation regex at deploy-time.
credential: secret.secretFullArn ?? secret.secretName,
}
: undefined,
certificate: env.certificate?.bucket.arnForObjects(env.certificate.objectKey),
privilegedMode: env.privileged || false,
fleet: this.configureFleet(env),
computeType: env.computeType || this.buildImage.defaultComputeType,
environmentVariables: hasEnvironmentVars
? Project.serializeEnvVariables(vars, props.checkSecretsInPlainTextEnvVariables ?? true, this)
: undefined,
};
}
private renderFileSystemLocations(): CfnProject.ProjectFileSystemLocationProperty[] | undefined {
return this._fileSystemLocations.length === 0
? undefined
: this._fileSystemLocations;
}
private renderSecondarySources(): CfnProject.SourceProperty[] | undefined {
return this._secondarySources.length === 0
? undefined
: this._secondarySources;
}
private renderSecondarySourceVersions(): CfnProject.ProjectSourceVersionProperty[] | undefined {
return this._secondarySourceVersions.length === 0
? undefined
: this._secondarySourceVersions;
}
private renderSecondaryArtifacts(): CfnProject.ArtifactsProperty[] | undefined {
return this._secondaryArtifacts.length === 0
? undefined
: this._secondaryArtifacts;
}
private configureFleet({ fleet }: BuildEnvironment): { fleetArn: string } | undefined {
if (!fleet) {
return undefined;
}
// If the fleetArn is resolved, the fleet is imported and we cannot validate the environment type
if (Token.isUnresolved(fleet.fleetArn) && this.buildImage.type !== fleet.environmentType) {
throw new ValidationError(`The environment type of the fleet (${fleet.environmentType}) must match the environment type of the build image (${this.buildImage.type})`, this);
}
return { fleetArn: fleet.fleetArn };
}
/**
* If configured, set up the VPC-related properties
*
* Returns the VpcConfig that should be added to the
* codebuild creation properties.
*/
private configureVpc(props: ProjectProps): CfnProject.VpcConfigProperty | undefined {
if ((props.securityGroups || props.allowAllOutbound !== undefined) && !props.vpc) {
throw new ValidationError('Cannot configure \'securityGroup\' or \'allowAllOutbound\' without configuring a VPC', this);
}
if (!props.vpc) { return undefined; }
if ((props.securityGroups && props.securityGroups.length > 0) && props.allowAllOutbound !== undefined) {
throw new ValidationError('Configure \'allowAllOutbound\' directly on the supplied SecurityGroup.', this);
}
let securityGroups: ec2.ISecurityGroup[];
if (props.securityGroups && props.securityGroups.length > 0) {
securityGroups = props.securityGroups;
} else {
const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {
vpc: props.vpc,
description: 'Automatic generated security group for CodeBuild ' + Names.uniqueId(this),
allowAllOutbound: props.allowAllOutbound,
});
securityGroups = [securityGroup];
}
this._connections = new ec2.Connections({ securityGroups });
return {
vpcId: props.vpc.vpcId,
subnets: props.vpc.selectSubnets(props.subnetSelection).subnetIds,
securityGroupIds: this.connections.securityGroups.map(s => s.securityGroupId),
};
}
private renderLoggingConfiguration(props: LoggingOptions | undefined): CfnProject.LogsConfigProperty | undefined {
if (props === undefined) {
return undefined;
}
let s3Config: CfnProject.S3LogsConfigProperty | undefined = undefined;
let cloudwatchConfig: CfnProject.CloudWatchLogsConfigProperty | undefined = undefined;
if (props.s3) {
const s3Logs = props.s3;
s3Config = {
status: (s3Logs.enabled ?? true) ? 'ENABLED' : 'DISABLED',
location: `${s3Logs.bucket.bucketName}` + (s3Logs.prefix ? `/${s3Logs.prefix}` : ''),
encryptionDisabled: s3Logs.encrypted,
};
s3Logs.bucket?.grantWrite(this);
}
if (props.cloudWatch) {
const cloudWatchLogs = props.cloudWatch;
const status = (cloudWatchLogs.enabled ?? true) ? 'ENABLED' : 'DISABLED';
if (status === 'ENABLED' && !(cloudWatchLogs.logGroup)) {
throw new ValidationError('Specifying a LogGroup is required if CloudWatch logging for CodeBuild is enabled', this);
}
cloudWatchLogs.logGroup?.grantWrite(this);
cloudwatchConfig = {
status,
groupName: cloudWatchLogs.logGroup?.logGroupName,
streamName: cloudWatchLogs.prefix,
};
}
return {
s3Logs: s3Config,
cloudWatchLogs: cloudwatchConfig,
};
}
private addVpcRequiredPermissions(props: ProjectProps, project: CfnProject): void {
if (!props.vpc || !this.role) {
return;
}
this.role.addToPrincipalPolicy(new iam.PolicyStatement({
resources: [`arn:${Aws.PARTITION}:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:network-interface/*`],
actions: ['ec2:CreateNetworkInterfacePermission'],
conditions: {
StringEquals: {
'ec2:Subnet': props.vpc
.selectSubnets(props.subnetSelection).subnetIds
.map(si => `arn:${Aws.PARTITION}:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:subnet/${si}`),
'ec2:AuthorizedService': 'codebuild.amazonaws.com',
},
},
}));
// If the same Role is used for multiple Projects, always creating a new `iam.Policy`
// will attach the same policy multiple times, probably exceeding the maximum size of the
// Role policy. Make sure we only do it once for the same role.
//
// This deduplication could be a feature of the Role itself, but that feels risky and
// is hard to implement (what with Tokens and all). Safer to fix it locally for now.
let policy: iam.Policy | undefined = (this.role as any)[VPC_POLICY_SYM];
if (!policy) {
policy = new iam.Policy(this, 'PolicyDocument', {
statements: [
new iam.PolicyStatement({
resources: ['*'],
actions: [
'ec2:CreateNetworkInterface',
'ec2:DescribeNetworkInterfaces',
'ec2:DeleteNetworkInterface',
'ec2:DescribeSubnets',
'ec2:DescribeSecurityGroups',
'ec2:DescribeDhcpOptions',
'ec2:DescribeVpcs',
],
}),
],
});
this.role.attachInlinePolicy(policy);
(this.role as any)[VPC_POLICY_SYM] = policy;
}
// add an explicit dependency between the EC2 Policy and this Project -
// otherwise, creating the Project fails, as it requires these permissions
// to be already attached to the Project's Role
project.node.addDependency(policy);
}
private validateCodePipelineSettings(artifacts: IArtifacts) {
const sourceType = this.source.type;
const artifactsType = artifacts.type;
if ((sourceType === CODEPIPELINE_SOURCE_ARTIFACTS_TYPE ||
artifactsType === CODEPIPELINE_SOURCE_ARTIFACTS_TYPE) &&
(sourceType !== artifactsType)) {
throw new ValidationError('Both source and artifacts must be set to CodePipeline', this);
}
}
private isLambdaBuildImage(buildImage: IBuildImage): boolean {
return buildImage instanceof LinuxLambdaBuildImage || buildImage instanceof LinuxArmLambdaBuildImage;
}
/**
* Validates a Lambda build image given the project properties.
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/lambda.html#lambda.limitations
*/
private validateLambdaBuildImage(buildImage: IBuildImage, props: ProjectProps): string[] {
if (!this.isLambdaBuildImage(buildImage)) return [];
const errors = [];
if (props.timeout) {
errors.push('Cannot specify timeout for Lambda compute');
}
if (props.queuedTimeout) {
errors.push('Cannot specify queuedTimeout for Lambda compute');
}
if (props.cache) {
errors.push('Cannot specify cache for Lambda compute');
}
if (props.badge) {
errors.push('Cannot enable badge for Lambda compute');
}
return errors;
}
}
/**
* The type of principal CodeBuild will use to pull your build Docker image.
*/
export enum ImagePullPrincipalType {
/**
* CODEBUILD specifies that CodeBuild uses its own identity when pulling the image.
* This means the resource policy of the ECR repository that hosts the image will be modified to trust
* CodeBuild's service principal.
* This is the required principal type when using CodeBuild's pre-defined images.
*/
CODEBUILD = 'CODEBUILD',
/**
* SERVICE_ROLE specifies that AWS CodeBuild uses the project's role when pulling the image.
* The role will be granted pull permissions on the ECR repository hosting the image.
*/
SERVICE_ROLE = 'SERVICE_ROLE',
}
export interface BuildEnvironment {
/**
* The image used for the builds.
*
* @default LinuxBuildImage.STANDARD_7_0
*/
readonly buildImage?: IBuildImage;
/**
* The type of compute to use for this build.
* See the `ComputeType` enum for the possible values.
*
* @default taken from `#buildImage#defaultComputeType`
*/
readonly computeType?: ComputeType;
/**
* Fleet resource for a reserved capacity CodeBuild project.
*
* Fleets allow for process builds or tests to run immediately and reduces build durations,
* by reserving compute resources for your projects.
*
* You will be charged for the resources in the fleet, even if they are idle.
*
* @default - No fleet will be attached to the project, which will remain on-demand.
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/fleets.html
*/
readonly fleet?: IFleet;
/**
* Indicates how the project builds Docker images. Specify true to enable
* running the Docker daemon inside a Docker container. This value must be
* set to true only if this build project will be used to build Docker
* images, and the specified build environment image is not one provided by
* AWS CodeBuild with Docker support. Otherwise, all associated builds that
* attempt to interact with the Docker daemon will fail.
*
* @default false
*/
readonly privileged?: boolean;
/**
* The location of the PEM-encoded certificate for the build project
*
* @default - No external certificate is added to the project
*/
readonly certificate?: BuildEnvironmentCertificate;
/**
* The environment variables that your builds can use.
*/
readonly environmentVariables?: { [name: string]: BuildEnvironmentVariable };
}
/**
* Represents a Docker image used for the CodeBuild Project builds.
* Use the concrete subclasses, either:
* `LinuxBuildImage` or `WindowsBuildImage`.
*/
export interface IBuildImage {
/**
* The type of build environment.
*/
readonly type: string;
/**
* The Docker image identifier that the build environment uses.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
*/
readonly imageId: string;
/**
* The default `ComputeType` to use with this image,
* if one was not specified in `BuildEnvironment#computeType` explicitly.
*/
readonly defaultComputeType: ComputeType;
/**
* The type of principal that CodeBuild will use to pull this build Docker image.
*
* @default ImagePullPrincipalType.SERVICE_ROLE
*/
readonly imagePullPrincipalType?: ImagePullPrincipalType;
/**
* The secretsManagerCredentials for access to a private registry.
*
* @default no credentials will be used
*/
readonly secretsManagerCredentials?: secretsmanager.ISecret;
/**
* An optional ECR repository that the image is hosted in.
*
* @default no repository
*/
readonly repository?: ecr.IRepository;
/**
* Allows the image a chance to validate whether the passed configuration is correct.
*
* @param buildEnvironment the current build environment
*/
validate(buildEnvironment: BuildEnvironment): string[];
/**
* Make a buildspec to run the indicated script
*/
runScriptBuildspec(entrypoint: string): BuildSpec;
}
/** Optional arguments to `IBuildImage.binder` - currently empty. */
export interface BuildImageBindOptions { }
/** The return type from `IBuildImage.binder` - currently empty. */
export interface BuildImageConfig { }
// @deprecated(not in tsdoc on purpose): add bind() to IBuildImage
// and get rid of IBindableBuildImage
/** A variant of `IBuildImage` that allows binding to the project. */
export interface IBindableBuildImage extends IBuildImage {
/** Function that allows the build image access to the construct tree. */
bind(scope: Construct, project: IProject, options: BuildImageBindOptions): BuildImageConfig;
}
/**
* The options when creating a CodeBuild Docker build image
* using `LinuxBuildImage.fromDockerRegistry`,
* `WindowsBuildImage.fromDockerRegistry`,
* or `MacBuildImage.fromDockerRegistry`
*/
export interface DockerImageOptions {
/**
* The credentials, stored in Secrets Manager,
* used for accessing the repository holding the image,
* if the repository is private.
*
* @default no credentials will be used (we assume the repository is public)
*/
readonly secretsManagerCredentials?: secretsmanager.ISecret;
}
/**
* Construction properties of `LinuxBuildImage`.
* Module-private, as the constructor of `LinuxBuildImage` is private.
*/
interface LinuxBuildImageProps {
readonly imageId: string;
readonly imagePullPrincipalType?: ImagePullPrincipalType;
readonly secretsManagerCredentials?: secretsmanager.ISecret;
readonly repository?: ecr.IRepository;
}
// Keep around to resolve a circular dependency until removing deprecated ARM image constants from LinuxBuildImage
// eslint-disable-next-line import/order
import { LinuxArmBuildImage } from './linux-arm-build-image';
/**
* A CodeBuild image running x86-64 Linux.
*
* This class has a bunch of public constants that represent the most popular images.
*
* You can also specify a custom image using one of the static methods:
*
* - LinuxBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }])
* - LinuxBuildImage.fromEcrRepository(repo[, tag])
* - LinuxBuildImage.fromAsset(parent, id, props)
*
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
*/
export class LinuxBuildImage implements IBuildImage {
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} instead. */
public static readonly STANDARD_1_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:1.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} instead. */
public static readonly STANDARD_2_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:2.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} instead. */
public static readonly STANDARD_3_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:3.0');
/**
* The `aws/codebuild/standard:4.0` build image.
* @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} instead.
* */
public static readonly STANDARD_4_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:4.0');
/** The `aws/codebuild/standard:5.0` build image. */
public static readonly STANDARD_5_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:5.0');
/** The `aws/codebuild/standard:6.0` build image. */
public static readonly STANDARD_6_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:6.0');
/** The `aws/codebuild/standard:7.0` build image. */
public static readonly STANDARD_7_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:7.0');
/** @deprecated Use {@link LinuxBuildImage.AMAZON_LINUX_2_5} instead. */
public static readonly AMAZON_LINUX_2 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:1.0');
/** @deprecated Use {@link LinuxBuildImage.AMAZON_LINUX_2_5} instead. */
public static readonly AMAZON_LINUX_2_2 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:2.0');
/**
* The Amazon Linux 2 x86_64 standard image, version `3.0`.
* @deprecated Use {@link LinuxBuildImage.AMAZON_LINUX_2_5} instead.
* */
public static readonly AMAZON_LINUX_2_3 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:3.0');
/** The Amazon Linux 2 x86_64 standard image, version `4.0`. */
public static readonly AMAZON_LINUX_2_4 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:4.0');
/** The Amazon Linux 2023 x86_64 standard image, version `5.0`. */
public static readonly AMAZON_LINUX_2_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:5.0');
/** The Amazon Linux 2023 x86_64 standard image, version `4.0`. */
public static readonly AMAZON_LINUX_2023_4 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux-x86_64-standard:4.0');
/** The Amazon Linux 2023 x86_64 standard image, version `5.0`. */
public static readonly AMAZON_LINUX_2023_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux-x86_64-standard:5.0');
/** The Amazon Coretto 8 image x86_64, based on Amazon Linux 2. */
public static readonly AMAZON_LINUX_2_CORETTO_8 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:corretto8');
/** The Amazon Coretto 11 image x86_64, based on Amazon Linux 2. */
public static readonly AMAZON_LINUX_2_CORETTO_11 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:corretto11');
/** The Amazon Coretto 8 image x86_64, based on Amazon Linux 2023. */
public static readonly AMAZON_LINUX_2023_CORETTO_8 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux-x86_64-standard:corretto8');
/** The Amazon Coretto 11 image x86_64, based on Amazon Linux 2023. */
public static readonly AMAZON_LINUX_2023_CORETTO_11 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux-x86_64-standard:corretto11');
/**
* Image "aws/codebuild/amazonlinux2-aarch64-standard:1.0".
* @see {LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_1_0}
*
* @deprecated Use {@link LinuxArmBuildImage.AMAZON_LINUX_2_ARM_3} instead.
**/
public static readonly AMAZON_LINUX_2_ARM = LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_1_0;
/**
* Image "aws/codebuild/amazonlinux2-aarch64-standard:2.0".
* @see {LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_2_0}
* */
public static readonly AMAZON_LINUX_2_ARM_2 = LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_2_0;
/**
* Image "aws/codebuild/amazonlinux2-aarch64-standard:3.0".
* @see {LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0}
* */
public static readonly AMAZON_LINUX_2_ARM_3 = LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0;
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_BASE = LinuxBuildImage.codeBuildImage('aws/codebuild/ubuntu-base:14.04');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_ANDROID_JAVA8_24_4_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/android-java-8:24.4.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_ANDROID_JAVA8_26_1_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/android-java-8:26.1.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_DOCKER_17_09_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/docker:17.09.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_DOCKER_18_09_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/docker:18.09.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_GOLANG_1_10 = LinuxBuildImage.codeBuildImage('aws/codebuild/golang:1.10');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_GOLANG_1_11 = LinuxBuildImage.codeBuildImage('aws/codebuild/golang:1.11');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_OPEN_JDK_8 = LinuxBuildImage.codeBuildImage('aws/codebuild/java:openjdk-8');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_OPEN_JDK_9 = LinuxBuildImage.codeBuildImage('aws/codebuild/java:openjdk-9');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_OPEN_JDK_11 = LinuxBuildImage.codeBuildImage('aws/codebuild/java:openjdk-11');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_NODEJS_10_14_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:10.14.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_NODEJS_10_1_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:10.1.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_NODEJS_8_11_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:8.11.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_NODEJS_6_3_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:6.3.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PHP_5_6 = LinuxBuildImage.codeBuildImage('aws/codebuild/php:5.6');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PHP_7_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/php:7.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PHP_7_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/php:7.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PYTHON_3_7_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.7.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PYTHON_3_6_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.6.5');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PYTHON_3_5_2 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.5.2');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PYTHON_3_4_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.4.5');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PYTHON_3_3_6 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.3.6');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_PYTHON_2_7_12 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:2.7.12');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_RUBY_2_5_3 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.5.3');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_RUBY_2_5_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.5.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_RUBY_2_3_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.3.1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_RUBY_2_2_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.2.5');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_DOTNET_CORE_1_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/dot-net:core-1');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_DOTNET_CORE_2_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/dot-net:core-2.0');
/** @deprecated Use {@link LinuxBuildImage.STANDARD_7_0} and specify runtime in buildspec runtime-versions section */
public static readonly UBUNTU_14_04_DOTNET_CORE_2_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/dot-net:core-2.1');
/**
* @returns a x86-64 Linux build image from a Docker Hub image.
*/
public static fromDockerRegistry(name: string, options: DockerImageOptions = {}): IBuildImage {
return new LinuxBuildImage({
...options,
imageId: name,
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
});
}
/**
* @returns A x86-64 Linux build image from an ECR repository.
*
* NOTE: if the repository is external (i.e. imported), then we won't be able to add
* a resource policy statement for it so CodeBuild can pull the image.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html
*
* @param repository The ECR repository
* @param tagOrDigest Image tag or digest (default "latest", digests must start with `sha256:`)
*/
public static fromEcrRepository(repository: ecr.IRepository, tagOrDigest: string = 'latest'): IBuildImage {
return new LinuxBuildImage({
imageId: repository.repositoryUriForTagOrDigest(tagOrDigest),
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
repository,
});
}
/**
* Uses an Docker image asset as a x86-64 Linux build image.
*/
public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): IBuildImage {
const asset = new DockerImageAsset(scope, id, props);
return new LinuxBuildImage({
imageId: asset.imageUri,
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
repository: asset.repository,
});
}
/**
* Uses a Docker image provided by CodeBuild.
*
* @returns A Docker image provided by CodeBuild.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
*
* @param id The image identifier
* @example 'aws/codebuild/standard:4.0'
*/
public static fromCodeBuildImageId(id: string): IBuildImage {
return LinuxBuildImage.codeBuildImage(id);
}
private static codeBuildImage(name: string): IBuildImage {
return new LinuxBuildImage({
imageId: name,
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
});
}
public readonly type = EnvironmentType.LINUX_CONTAINER as string;
public readonly defaultComputeType = ComputeType.SMALL;
public readonly imageId: string;
public readonly imagePullPrincipalType?: ImagePullPrincipalType;
public readonly secretsManagerCredentials?: secretsmanager.ISecret;
public readonly repository?: ecr.IRepository;
private constructor(props: LinuxBuildImageProps) {
this.imageId = props.imageId;
this.imagePullPrincipalType = props.imagePullPrincipalType;
this.secretsManagerCredentials = props.secretsManagerCredentials;
this.repository = props.repository;
}
public validate(env: BuildEnvironment): string[] {
const errors = [];
if (env.computeType && isLambdaComputeType(env.computeType)) {
errors.push('x86-64 images do not support Lambda compute types');
}
return errors;
}
public runScriptBuildspec(entrypoint: string): BuildSpec {
return runScriptLinuxBuildSpec(entrypoint);
}
}
/**
* Environment type for Windows Docker images
*/
export enum WindowsImageType {
/**
* The standard environment type, WINDOWS_CONTAINER
*/
STANDARD = 'WINDOWS_CONTAINER',
/**
* The WINDOWS_SERVER_2019_CONTAINER environment type
*/
SERVER_2019 = EnvironmentType.WINDOWS_SERVER_2019_CONTAINER,
/**
* The WINDOWS_SERVER_2022_CONTAINER environment type
*
* Notice: Cannot be used with on-demand compute, only with a {@link BuildEnvironment.fleet}.
*
* @see https://github.com/aws/aws-cdk/issues/29617
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/fleets.html
*/
SERVER_2022 = EnvironmentType.WINDOWS_SERVER_2022_CONTAINER,
}
/**
* Construction properties of `WindowsBuildImage`.
* Module-private, as the constructor of `WindowsBuildImage` is private.
*/
interface WindowsBuildImageProps {
readonly imageId: string;
readonly imagePullPrincipalType?: ImagePullPrincipalType;
readonly secretsManagerCredentials?: secretsmanager.ISecret;
readonly repository?: ecr.IRepository;
readonly imageType?: WindowsImageType;
}
/**
* A CodeBuild image running Windows.
*
* This class has a bunch of public constants that represent the most popular images.
*
* You can also specify a custom image using one of the static methods:
*
* - WindowsBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }, imageType])
* - WindowsBuildImage.fromEcrRepository(repo[, tag, imageType])
* - WindowsBuildImage.fromAsset(parent, id, props, [, imageType])
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
*/
export class WindowsBuildImage implements IBuildImage {
/**
* Corresponds to the standard CodeBuild image `aws/codebuild/windows-base:1.0`.
*
* @deprecated {@link WindowsBuildImage.WIN_SERVER_CORE_2019_BASE_3_0} should be used instead.
*/
public static readonly WIN_SERVER_CORE_2016_BASE: IBuildImage = new WindowsBuildImage({
imageId: 'aws/codebuild/windows-base:1.0',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
});
/**
* The standard CodeBuild image `aws/codebuild/windows-base:2.0`, which is
* based off Windows Server Core 2016.
*
* @deprecated {@link WindowsBuildImage.WIN_SERVER_CORE_2019_BASE_3_0} should be used instead.
*/
public static readonly WINDOWS_BASE_2_0: IBuildImage = new WindowsBuildImage({
imageId: 'aws/codebuild/windows-base:2.0',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
});
/**
* The standard CodeBuild image `aws/codebuild/windows-base:2019-1.0`, which is
* based off Windows Server Core 2019.
*/
public static readonly WIN_SERVER_CORE_2019_BASE: IBuildImage = new WindowsBuildImage({
imageId: 'aws/codebuild/windows-base:2019-1.0',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
imageType: WindowsImageType.SERVER_2019,
});
/**
* The standard CodeBuild image `aws/codebuild/windows-base:2019-2.0`, which is
* based off Windows Server Core 2019.
*/
public static readonly WIN_SERVER_CORE_2019_BASE_2_0: IBuildImage = new WindowsBuildImage({
imageId: 'aws/codebuild/windows-base:2019-2.0',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
imageType: WindowsImageType.SERVER_2019,
});
/**
* The standard CodeBuild image `aws/codebuild/windows-base:2019-3.0`, which is
* based off Windows Server Core 2019.
*/
public static readonly WIN_SERVER_CORE_2019_BASE_3_0: IBuildImage = new WindowsBuildImage({
imageId: 'aws/codebuild/windows-base:2019-3.0',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
imageType: WindowsImageType.SERVER_2019,
});
/**
* The standard CodeBuild image `aws/codebuild/windows-base:2022-1.0`, which is
* based off Windows Server Core 2022.
*
* Notice: Cannot be used with on-demand compute, only with a {@link BuildEnvironment.fleet}.
*
* @see https://github.com/aws/aws-cdk/issues/29617
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/fleets.html
*/
public static readonly WIN_SERVER_CORE_2022_BASE_3_0: IBuildImage = new WindowsBuildImage({
imageId: 'aws/codebuild/windows-base:2022-1.0',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
imageType: WindowsImageType.SERVER_2022,
});
/**
* @returns a Windows build image from a Docker Hub image.
*/
public static fromDockerRegistry(
name: string,
options: DockerImageOptions = {},
imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage {
return new WindowsBuildImage({
...options,
imageId: name,
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
imageType,
});
}
/**
* @returns A Windows build image from an ECR repository.
*
* NOTE: if the repository is external (i.e. imported), then we won't be able to add
* a resource policy statement for it so CodeBuild can pull the image.
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html
*
* @param repository The ECR repository
* @param tagOrDigest Image tag or digest (default "latest", digests must start with `sha256:`)
*/
public static fromEcrRepository(
repository: ecr.IRepository,
tagOrDigest: string = 'latest',
imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage {
return new WindowsBuildImage({
imageId: repository.repositoryUriForTagOrDigest(tagOrDigest),
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
imageType,
repository,
});
}
/**
* Uses an Docker image asset as a Windows build image.
*/
public static fromAsset(
scope: Construct,
id: string,
props: DockerImageAssetProps,
imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage {
const asset = new DockerImageAsset(scope, id, props);
return new WindowsBuildImage({
imageId: asset.imageUri,
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
imageType,
repository: asset.repository,
});
}
public readonly type: string;
public readonly defaultComputeType = ComputeType.MEDIUM;
public readonly imageId: string;
public readonly imagePullPrincipalType?: ImagePullPrincipalType;
public readonly secretsManagerCredentials?: secretsmanager.ISecret;
public readonly repository?: ecr.IRepository;
private constructor(props: WindowsBuildImageProps) {
this.type = (props.imageType ?? WindowsImageType.STANDARD).toString();
this.imageId = props.imageId;
this.imagePullPrincipalType = props.imagePullPrincipalType;
this.secretsManagerCredentials = props.secretsManagerCredentials;
this.repository = props.repository;
}
public validate(buildEnvironment: BuildEnvironment): string[] {
const errors: string[] = [];
if (buildEnvironment.privileged) {
errors.push('Windows images do not support privileged mode');
}
if (buildEnvironment.computeType && isLambdaComputeType(buildEnvironment.computeType)) {
errors.push('Windows images do not support Lambda compute types');
}
const unsupportedComputeTypes = [ComputeType.SMALL, ComputeType.X_LARGE, ComputeType.X2_LARGE];
if (buildEnvironment.computeType !== undefined && unsupportedComputeTypes.includes(buildEnvironment.computeType)) {
errors.push(`Windows images do not support the '${buildEnvironment.computeType}' compute type`);
}
if (!buildEnvironment.fleet && this.type === WindowsImageType.SERVER_2022) {
errors.push('Windows Server 2022 images must be used with a fleet');
}
return errors;
}
public runScriptBuildspec(entrypoint: string): BuildSpec {
return BuildSpec.fromObject({
version: '0.2',
phases: {
pre_build: {
// Would love to do downloading here and executing in the next step,
// but I don't know how to propagate the value of $TEMPDIR.
//
// Punting for someone who knows PowerShell well enough.
commands: [],
},
build: {
commands: [
'Set-Variable -Name TEMPDIR -Value (New-TemporaryFile).DirectoryName',
`aws s3 cp s3://$env:${S3_BUCKET_ENV}/$env:${S3_KEY_ENV} $TEMPDIR\\scripts.zip`,
'New-Item -ItemType Directory -Path $TEMPDIR\\scriptdir',
'Expand-Archive -Path $TEMPDIR/scripts.zip -DestinationPath $TEMPDIR\\scriptdir',
'$env:SCRIPT_DIR = "$TEMPDIR\\scriptdir"',
`& $TEMPDIR\\scriptdir\\${entrypoint}`,
],
},
},
});
}
}
/**
* Construction properties of `MacBuildImage`.
* Module-private, as the constructor of `MacBuildImage` is private.
*/
interface MacBuildImageProps {
readonly imageId: string;
readonly imagePullPrincipalType?: ImagePullPrincipalType;
readonly secretsManagerCredentials?: secretsmanager.ISecret;
readonly repository?: ecr.IRepository;
}
/**
* A CodeBuild image running ARM MacOS.
*
* This class has a bunch of public constants that represent the most popular images.
*
* You can also specify a custom image using one of the static methods:
*
* - MacBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }])
* - MacBuildImage.fromEcrRepository(repo[, tag])
* - MacBuildImage.fromAsset(parent, id, props)
*
*
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
*/
export class MacBuildImage implements IBuildImage {
/**
* Corresponds to the standard CodeBuild image `aws/codebuild/macos-arm-base:14`.
*/
public static readonly BASE_14: IBuildImage = new MacBuildImage({
imageId: 'aws/codebuild/macos-arm-base:14',
imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD,
});
/**
* Makes an ARM MacOS build image from a Docker Hub image.
*/
public static fromDockerRegistry(name: string, options: DockerImageOptions = {}): IBuildImage {
return new MacBuildImage({
...options,
imageId: name,
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
});
}
/**
* Makes an ARM MacOS build image from an ECR repository.
*/
public static fromEcrRepository(repository: ecr.IRepository, tagOrDigest: string = 'latest'): IBuildImage {
return new MacBuildImage({
imageId: repository.repositoryUriForTagOrDigest(tagOrDigest),
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
repository,
});
}
/**
* Uses an Docker image asset as a ARM MacOS build image.
*/
public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): IBuildImage {
const asset = new DockerImageAsset(scope, id, props);
return new MacBuildImage({
imageId: asset.imageUri,
imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE,
repository: asset.repository,
});
}
public readonly type = EnvironmentType.MAC_ARM as string;
public readonly defaultComputeType = ComputeType.MEDIUM;
public readonly imageId: string;
public readonly imagePullPrincipalType?: ImagePullPrincipalType;
public readonly secretsManagerCredentials?: secretsmanager.ISecret;
public readonly repository?: ecr.IRepository;
private constructor(props: MacBuildImageProps) {
this.imageId = props.imageId;
this.imagePullPrincipalType = props.imagePullPrincipalType;
this.secretsManagerCredentials = props.secretsManagerCredentials;
this.repository = props.repository;
}
public validate(buildEnvironment: BuildEnvironment): string[] {
const errors: string[] = [];
if (buildEnvironment.computeType && isLambdaComputeType(buildEnvironment.computeType)) {
errors.push('Mac images do not support Lambda compute types');
}
if (!buildEnvironment.fleet) {
errors.push('Mac images must be used with a fleet');
}
return errors;
}
public runScriptBuildspec(entrypoint: string): BuildSpec {
// Reuse Linux BuildSpec, since it is compatible
return runScriptLinuxBuildSpec(entrypoint);
}
}
export interface BuildEnvironmentVariable {
/**
* The type of environment variable.
* @default PlainText
*/
readonly type?: BuildEnvironmentVariableType;
/**
* The value of the environment variable.
* For plain-text variables (the default), this is the literal value of variable.
* For SSM parameter variables, pass the name of the parameter here (`parameterName` property of `IParameter`).
* For SecretsManager variables secrets, pass either the secret name (`secretName` property of `ISecret`)
* or the secret ARN (`secretArn` property of `ISecret`) here,
* along with optional SecretsManager qualifiers separated by ':', like the JSON key, or the version or stage
* (see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager for details).
*/
readonly value: any;
}
export enum BuildEnvironmentVariableType {
/**
* An environment variable in plaintext format.
*/
PLAINTEXT = 'PLAINTEXT',
/**
* An environment variable stored in Systems Manager Parameter Store.
*/
PARAMETER_STORE = 'PARAMETER_STORE',
/**
* An environment variable stored in AWS Secrets Manager.
*/
SECRETS_MANAGER = 'SECRETS_MANAGER',
}
/**
* Specifies the visibility of the project's builds.
*/
export enum ProjectVisibility {
/**
* The project builds are visible to the public.
*/
PUBLIC_READ = 'PUBLIC_READ',
/**
* The project builds are not visible to the public.
*/
PRIVATE = 'PRIVATE',
}
/**
* The list of event types for AWS Codebuild
* @see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#events-ref-buildproject
*/
export enum ProjectNotificationEvents {
/**
* Trigger notification when project build state failed
*/
BUILD_FAILED = 'codebuild-project-build-state-failed',
/**
* Trigger notification when project build state succeeded
*/
BUILD_SUCCEEDED = 'codebuild-project-build-state-succeeded',
/**
* Trigger notification when project build state in progress
*/
BUILD_IN_PROGRESS = 'codebuild-project-build-state-in-progress',
/**
* Trigger notification when project build state stopped
*/
BUILD_STOPPED = 'codebuild-project-build-state-stopped',
/**
* Trigger notification when project build phase failure
*/
BUILD_PHASE_FAILED = 'codebuild-project-build-phase-failure',
/**
* Trigger notification when project build phase success
*/
BUILD_PHASE_SUCCEEDED = 'codebuild-project-build-phase-success',
}
function isBindableBuildImage(x: unknown): x is IBindableBuildImage {
return typeof x === 'object' && !!x && !!(x as any).bind;
}
export function isLambdaComputeType(computeType: ComputeType): boolean {
const lambdaComputeTypes = Object.values(ComputeType).filter(value => value.startsWith('BUILD_LAMBDA'));
return lambdaComputeTypes.includes(computeType);
}