UNPKG

@aws-cdk/aws-codebuild

Version:

The CDK Construct Library for AWS::CodeBuild

1,102 lines 215 kB
"use strict"; var _a, _b, _c; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectNotificationEvents = exports.BuildEnvironmentVariableType = exports.WindowsBuildImage = exports.WindowsImageType = exports.LinuxBuildImage = exports.ImagePullPrincipalType = exports.ComputeType = exports.Project = void 0; const jsiiDeprecationWarnings = require("../.warnings.jsii.js"); const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti"); const cloudwatch = require("@aws-cdk/aws-cloudwatch"); const notifications = require("@aws-cdk/aws-codestarnotifications"); const ec2 = require("@aws-cdk/aws-ec2"); const aws_ecr_assets_1 = require("@aws-cdk/aws-ecr-assets"); const events = require("@aws-cdk/aws-events"); const iam = require("@aws-cdk/aws-iam"); const kms = require("@aws-cdk/aws-kms"); const core_1 = require("@aws-cdk/core"); const build_spec_1 = require("./build-spec"); const cache_1 = require("./cache"); const codebuild_canned_metrics_generated_1 = require("./codebuild-canned-metrics.generated"); const codebuild_generated_1 = require("./codebuild.generated"); const codepipeline_artifacts_1 = require("./codepipeline-artifacts"); const no_artifacts_1 = require("./no-artifacts"); const no_source_1 = require("./no-source"); const run_script_linux_build_spec_1 = require("./private/run-script-linux-build-spec"); const report_group_utils_1 = require("./report-group-utils"); const source_types_1 = require("./source-types"); const VPC_POLICY_SYM = Symbol.for('@aws-cdk/aws-codebuild.roleVpcPolicy'); /** * Represents a reference to a CodeBuild Project. * * If you're managing the Project alongside the rest of your CDK resources, * use the {@link Project} class. * * If you want to reference an already existing Project * (or one defined in a different CDK Stack), * use the {@link import} method. */ class ProjectBase extends core_1.Resource { /** * Access the Connections object. * Will fail if this Project does not have a VPC set. */ get connections() { if (!this._connections) { throw new Error('Only VPC-associated Projects have security groups to manage. Supply the "vpc" parameter when creating the Project'); } return this._connections; } enableBatchBuilds() { return undefined; } /** * Add a permission only if there's a policy attached. * @param statement The permissions statement to add */ addToRolePolicy(statement) { 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 */ onEvent(id, options = {}) { 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 */ onStateChange(id, options = {}) { 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 */ onPhaseChange(id, options = {}) { 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. */ onBuildStarted(id, options = {}) { 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. */ onBuildFailed(id, options = {}) { 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. */ onBuildSucceeded(id, options = {}) { 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 */ metric(metricName, props) { 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 */ metricBuilds(props) { return this.cannedMetric(codebuild_canned_metrics_generated_1.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 */ metricDuration(props) { return this.cannedMetric(codebuild_canned_metrics_generated_1.CodeBuildMetrics.durationAverage, props); } /** * Measures the number of successful builds. * * Units: Count * * Valid CloudWatch statistics: Sum * * @default sum over 5 minutes */ metricSucceededBuilds(props) { return this.cannedMetric(codebuild_canned_metrics_generated_1.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 */ metricFailedBuilds(props) { return this.cannedMetric(codebuild_canned_metrics_generated_1.CodeBuildMetrics.failedBuildsSum, props); } notifyOn(id, target, options) { return new notifications.NotificationRule(this, id, { ...options, source: this, targets: [target], }); } notifyOnBuildSucceeded(id, target, options) { return this.notifyOn(id, target, { ...options, events: [ProjectNotificationEvents.BUILD_SUCCEEDED], }); } notifyOnBuildFailed(id, target, options) { return this.notifyOn(id, target, { ...options, events: [ProjectNotificationEvents.BUILD_FAILED], }); } bindAsNotificationRuleSource(_scope) { return { sourceArn: this.projectArn, }; } cannedMetric(fn, props) { return new cloudwatch.Metric({ ...fn({ ProjectName: this.projectName }), ...props, }).attachTo(this); } } /** * A representation of a CodeBuild Project. */ class Project extends ProjectBase { constructor(scope, id, props) { super(scope, id, { physicalName: props.projectName, }); try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_ProjectProps(props); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, Project); } throw error; } this.role = props.role || new iam.Role(this, 'Role', { roleName: core_1.PhysicalName.GENERATE_IF_NEEDED, assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'), }); this.grantPrincipal = this.role; this.buildImage = (props.environment && props.environment.buildImage) || LinuxBuildImage.STANDARD_1_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 no_source_1.NoSource(); const sourceConfig = this.source.bind(this, this); if (props.badge && !this.source.badgeSupported) { throw new Error(`Badge is not supported for source type ${this.source.type}`); } const artifacts = props.artifacts ? props.artifacts : (this.source.type === source_types_1.CODEPIPELINE_SOURCE_ARTIFACTS_TYPE ? new codepipeline_artifacts_1.CodePipelineArtifacts() : new no_artifacts_1.NoArtifacts()); const artifactsConfig = artifacts.bind(this, this); const cache = props.cache || cache_1.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 === source_types_1.NO_SOURCE_TYPE && (buildSpec === undefined || !buildSpec.isImmediate)) { throw new Error("If the Project's source is NoSource, you need to provide a concrete buildSpec"); } 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); } const resource = new codebuild_generated_1.CfnProject(this, 'Resource', { description: props.description, source: { ...sourceConfig.sourceProperty, buildSpec: buildSpec && buildSpec.toBuildSpec(), }, artifacts: artifactsConfig.artifactsProperty, serviceRole: this.role.roleArn, environment: this.renderEnvironment(props, environmentVariables), fileSystemLocations: core_1.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: core_1.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: core_1.Lazy.any({ produce: () => this.renderSecondarySources() }), secondarySourceVersions: core_1.Lazy.any({ produce: () => this.renderSecondarySourceVersions() }), secondaryArtifacts: core_1.Lazy.any({ produce: () => this.renderSecondaryArtifacts() }), triggers: sourceConfig.buildTriggers, sourceVersion: sourceConfig.sourceVersion, vpcConfig: this.configureVpc(props), logsConfig: this.renderLoggingConfiguration(props.logging), buildBatchConfig: core_1.Lazy.any({ produce: () => { const config = this._batchServiceRole ? { serviceRole: this._batchServiceRole.roleArn, } : undefined; return config; }, }), }); 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: [report_group_utils_1.renderReportGroupArn(this, `${this.projectName}-*`)], })); } if (props.encryptionKey) { this.encryptionKey = props.encryptionKey; } // bind if (isBindableBuildImage(this.buildImage)) { this.buildImage.bind(this, this, {}); } } static fromProjectArn(scope, id, projectArn) { const parsedArn = core_1.Stack.of(scope).splitArn(projectArn, core_1.ArnFormat.SLASH_RESOURCE_NAME); class Import extends ProjectBase { constructor(s, i) { super(s, i, { account: parsedArn.account, region: parsedArn.region, }); this.projectArn = projectArn; this.projectName = parsedArn.resourceName; this.role = undefined; 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 {@link 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 */ static fromProjectName(scope, id, projectName) { class Import extends ProjectBase { constructor(s, i) { super(s, i); this.role = undefined; this.projectArn = core_1.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 {@link BuildEnvironmentVariable}, * which is the customer-facing type, to a list of {@link 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 {@link CfnProject.EnvironmentVariableProperty} instances */ static serializeEnvVariables(environmentVariables, validateNoPlainTextSecrets = false, principal) { const ret = new Array(); const ssmIamResources = new Array(); const secretsManagerIamResources = new Set(); const kmsIamResources = new Set(); for (const [name, envVariable] of Object.entries(environmentVariables)) { const envVariableValue = envVariable.value?.toString(); const cfnEnvVariable = { 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 = core_1.Tokenization.reverseString(cfnEnvVariable.value); for (const token of fragments.tokens) { if (token instanceof core_1.SecretValue) { throw new Error(`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 = core_1.Stack.of(principal); // 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, core_1.ArnFormat.COLON_RESOURCE_NAME); if (!parsedArn.resourceName) { throw new Error('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: core_1.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 && core_1.Token.compareStrings(parsedArn.account, stack.account) === core_1.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: core_1.ArnFormat.SLASH_RESOURCE_NAME, partition: parsedArn.partition, account: parsedArn.account, region: parsedArn.region, })); } } else if (core_1.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 = core_1.Tokenization.reverseString(envVariableValue); if (fragments.tokens.length === 1) { const resolvable = fragments.tokens[0]; if (core_1.Reference.isReference(resolvable)) { // check the Stack the resource owning the reference belongs to const resourceStack = core_1.Stack.of(resolvable.target); if (core_1.Token.compareStrings(stack.account, resourceStack.account) === core_1.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: core_1.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: core_1.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; } enableBatchBuilds() { if (!this._batchServiceRole) { this._batchServiceRole = new iam.Role(this, 'BatchServiceRole', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'), }); this._batchServiceRole.addToPrincipalPolicy(new iam.PolicyStatement({ resources: [core_1.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 */ addSecondarySource(secondarySource) { try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_ISource(secondarySource); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, this.addSecondarySource); } throw error; } if (!secondarySource.identifier) { throw new Error('The identifier attribute is mandatory for secondary sources'); } 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 */ addFileSystemLocation(fileSystemLocation) { try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_IFileSystemLocation(fileSystemLocation); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, this.addFileSystemLocation); } throw error; } 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 */ addSecondaryArtifact(secondaryArtifact) { try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_IArtifacts(secondaryArtifact); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, this.addSecondaryArtifact); } throw error; } if (!secondaryArtifact.identifier) { throw new Error('The identifier attribute is mandatory for secondary artifacts'); } 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 */ bindToCodePipeline(_scope, options) { try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_BindToCodePipelineOptions(options); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, this.bindToCodePipeline); } throw error; } // 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 = core_1.Stack.of(options.artifactBucket.encryptionKey); const projectStack = core_1.Stack.of(this); if (!(options.artifactBucket.encryptionKey instanceof kms.Key && (keyStack.account !== projectStack.account || keyStack.region !== projectStack.region))) { this.encryptionKey = options.artifactBucket.encryptionKey; } } } /** * @override */ validate() { const ret = new Array(); if (this.source.type === source_types_1.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; } set encryptionKey(encryptionKey) { this._encryptionKey = encryptionKey; encryptionKey.grantEncryptDecrypt(this); } createLoggingPermission() { const logGroupArn = core_1.Stack.of(this).formatArn({ service: 'logs', resource: 'log-group', arnFormat: core_1.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'], }); } renderEnvironment(props, projectVars = {}) { const env = props.environment ?? {}; const vars = {}; 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); if (errors.length > 0) { throw new Error('Invalid CodeBuild environment: ' + errors.join('\n')); } const imagePullPrincipalType = 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, computeType: env.computeType || this.buildImage.defaultComputeType, environmentVariables: hasEnvironmentVars ? Project.serializeEnvVariables(vars, props.checkSecretsInPlainTextEnvVariables ?? true, this) : undefined, }; } renderFileSystemLocations() { return this._fileSystemLocations.length === 0 ? undefined : this._fileSystemLocations; } renderSecondarySources() { return this._secondarySources.length === 0 ? undefined : this._secondarySources; } renderSecondarySourceVersions() { return this._secondarySourceVersions.length === 0 ? undefined : this._secondarySourceVersions; } renderSecondaryArtifacts() { return this._secondaryArtifacts.length === 0 ? undefined : this._secondaryArtifacts; } /** * If configured, set up the VPC-related properties * * Returns the VpcConfig that should be added to the * codebuild creation properties. */ configureVpc(props) { if ((props.securityGroups || props.allowAllOutbound !== undefined) && !props.vpc) { throw new Error('Cannot configure \'securityGroup\' or \'allowAllOutbound\' without configuring a VPC'); } if (!props.vpc) { return undefined; } if ((props.securityGroups && props.securityGroups.length > 0) && props.allowAllOutbound !== undefined) { throw new Error('Configure \'allowAllOutbound\' directly on the supplied SecurityGroup.'); } let securityGroups; 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 ' + core_1.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), }; } renderLoggingConfiguration(props) { if (props === undefined) { return undefined; } let s3Config = undefined; let cloudwatchConfig = 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 Error('Specifying a LogGroup is required if CloudWatch logging for CodeBuild is enabled'); } cloudWatchLogs.logGroup?.grantWrite(this); cloudwatchConfig = { status, groupName: cloudWatchLogs.logGroup?.logGroupName, streamName: cloudWatchLogs.prefix, }; } return { s3Logs: s3Config, cloudWatchLogs: cloudwatchConfig, }; } addVpcRequiredPermissions(props, project) { if (!props.vpc || !this.role) { return; } this.role.addToPrincipalPolicy(new iam.PolicyStatement({ resources: [`arn:${core_1.Aws.PARTITION}:ec2:${core_1.Aws.REGION}:${core_1.Aws.ACCOUNT_ID}:network-interface/*`], actions: ['ec2:CreateNetworkInterfacePermission'], conditions: { StringEquals: { 'ec2:Subnet': props.vpc .selectSubnets(props.subnetSelection).subnetIds .map(si => `arn:${core_1.Aws.PARTITION}:ec2:${core_1.Aws.REGION}:${core_1.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 = this.role[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[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); } validateCodePipelineSettings(artifacts) { const sourceType = this.source.type; const artifactsType = artifacts.type; if ((sourceType === source_types_1.CODEPIPELINE_SOURCE_ARTIFACTS_TYPE || artifactsType === source_types_1.CODEPIPELINE_SOURCE_ARTIFACTS_TYPE) && (sourceType !== artifactsType)) { throw new Error('Both source and artifacts must be set to CodePipeline'); } } } exports.Project = Project; _a = JSII_RTTI_SYMBOL_1; Project[_a] = { fqn: "@aws-cdk/aws-codebuild.Project", version: "1.204.0" }; /** * Build machine compute type. */ var ComputeType; (function (ComputeType) { ComputeType["SMALL"] = "BUILD_GENERAL1_SMALL"; ComputeType["MEDIUM"] = "BUILD_GENERAL1_MEDIUM"; ComputeType["LARGE"] = "BUILD_GENERAL1_LARGE"; ComputeType["X2_LARGE"] = "BUILD_GENERAL1_2XLARGE"; })(ComputeType = exports.ComputeType || (exports.ComputeType = {})); /** * The type of principal CodeBuild will use to pull your build Docker image. */ var ImagePullPrincipalType; (function (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. */ ImagePullPrincipalType["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. */ ImagePullPrincipalType["SERVICE_ROLE"] = "SERVICE_ROLE"; })(ImagePullPrincipalType = exports.ImagePullPrincipalType || (exports.ImagePullPrincipalType = {})); // Keep around to resolve a circular dependency until removing deprecated ARM image constants from LinuxBuildImage // eslint-disable-next-line no-duplicate-imports, import/order const linux_arm_build_image_1 = require("./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 */ class LinuxBuildImage { constructor(props) { this.type = 'LINUX_CONTAINER'; this.defaultComputeType = ComputeType.SMALL; this.imageId = props.imageId; this.imagePullPrincipalType = props.imagePullPrincipalType; this.secretsManagerCredentials = props.secretsManagerCredentials; this.repository = props.repository; } /** * @returns a x86-64 Linux build image from a Docker Hub image. */ static fromDockerRegistry(name, options = {}) { try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_DockerImageOptions(options); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, this.fromDockerRegistry); } throw error; } 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:`) */ static fromEcrRepository(repository, tagOrDigest = 'latest') { return new LinuxBuildImage({ imageId: repository.repositoryUriForTagOrDigest(tagOrDigest), imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, repository, }); } /** * Uses an Docker image asset as a x86-64 Linux build image. */ static fromAsset(scope, id, props) { const asset = new aws_ecr_assets_1.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' */ static fromCodeBuildImageId(id) { return LinuxBuildImage.codeBuildImage(id); } static codeBuildImage(name) { return new LinuxBuildImage({ imageId: name, imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD, }); } validate(_) { try { jsiiDeprecationWarnings._aws_cdk_aws_codebuild_BuildEnvironment(_); } catch (error) { if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") { Error.captureStackTrace(error, this.validate); } throw error; } return []; } runScriptBuildspec(entrypoint) { return run_script_linux_build_spec_1.runScriptLinuxBuildSpec(entrypoint); } } exports.LinuxBuildImage = LinuxBuildImage; _b = JSII_RTTI_SYMBOL_1; LinuxBuildImage[_b] = { fqn: "@aws-cdk/aws-codebuild.LinuxBuildImage", version: "1.204.0" }; LinuxBuildImage.STANDARD_1_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:1.0'); LinuxBuildImage.STANDARD_2_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:2.0'); LinuxBuildImage.STANDARD_3_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:3.0'); /** The `aws/codebuild/standard:4.0` build image. */ LinuxBuildImage.STANDARD_4_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:4.0'); /** The `aws/codebuild/standard:5.0` build image. */ LinuxBuildImage.STANDARD_5_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:5.0'); LinuxBuildImage.AMAZON_LINUX_2 = LinuxBuildImage.codeBuildImage('aws/codebuild/amazonlinux2-x86_64-standard:1.0'); LinuxBuildImage.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`. */ LinuxBuildImage.AMAZON_LINUX_2_3 = LinuxBuildImage.code