in packages/aws-cdk-lib/aws-codebuild/lib/project.ts [1060:1222]
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() });
}