in lib/pull-request/pr.ts [217:322]
constructor(parent: Construct, id: string, props: AutoPullRequestProps) {
super(parent, id);
this.props = props;
this.baseBranch = props.base?.name ?? 'master';
this.headSource = props.head.source ?? this.baseBranch;
this.exports = props.exports ?? {};
for (const ex of Object.keys(this.exports)) {
if (this.headSource.includes(`\${${ex}}`) || this.headSource.includes(`\$${ex}`)) {
throw new Error(`head source (${this.headSource}) cannot contain dynamic exports: ${ex}`);
}
}
const sshKeySecret = props.repo.sshKeySecret;
const commitEmail = props.repo.commitEmail;
const commitUsername = props.repo.commitUsername;
const cloneDepth = props.cloneDepth === undefined ? 0 : props.cloneDepth;
const needsGitHubTokenSecret = !this.props.pushOnly || !!this.props.skipIfOpenPrsWithLabels;
let commands: string[] = [
...this.configureSshAccess(),
// when the job is triggered as a CodePipeline action, the working directory
// is populated with the output artifact of the CodeCommitSourceAction, which doesn't include
// the .git directory in the zipped s3 archive. (Yeah, fun stuff).
// see https://itnext.io/how-to-access-git-metadata-in-codebuild-when-using-codepipeline-codecommit-ceacf2c5c1dc
...this.cloneIfNeeded(),
];
if (this.props.condition) {
// there's no way to stop a BuildSpec execution halfway through without throwing an error. Believe me, I
// checked the code. Instead we define a variable that we will switch all other lines on/off.
commands.push(`${this.props.condition} ` +
'&& { echo \'Skip condition is met, skipping...\' && export SKIP=true; } ' +
'|| { echo \'Skip condition is not met, continuing...\' && export SKIP=false; }');
}
// read the token
if (needsGitHubTokenSecret) {
commands.push(`export GITHUB_TOKEN=$(aws secretsmanager get-secret-value --secret-id "${this.props.repo.tokenSecretArn}" --output=text --query=SecretString)`);
}
if (this.props.skipIfOpenPrsWithLabels) {
commands.push(...this.skipIfOpenPrs(this.props.skipIfOpenPrsWithLabels));
}
commands.push(
...this.createHead(),
...this.pushHead(),
);
if (!this.props.pushOnly) {
commands.push(...this.createPullRequest());
}
// toggle all commands according to the SKIP variable.
commands = commands.map((command: string) => `$SKIP || { ${command} ; }`);
// intially all commands are enabled.
commands.unshift('export SKIP=false');
this.project = new cbuild.Project(this, 'PullRequest', {
source: props.repo.createBuildSource(this, false, { cloneDepth }),
description: props.projectDescription,
environment: createBuildEnvironment(props.build ?? {}),
buildSpec: cbuild.BuildSpec.fromObject({
version: '0.2',
phases: {
pre_build: {
commands: [
`git config --global user.email "${commitEmail}"`,
`git config --global user.name "${commitUsername}"`,
],
},
build: { commands },
},
}),
});
if (this.project.role) {
permissions.grantSecretRead(sshKeySecret, this.project.role);
if (needsGitHubTokenSecret) {
permissions.grantSecretRead({ secretArn: props.repo.tokenSecretArn }, this.project.role);
}
}
if (props.scheduleExpression) {
const schedule = events.Schedule.expression(props.scheduleExpression);
new events.Rule(this, 'Scheduler', {
description: 'Schedules an automatic Pull Request for this repository',
schedule,
targets: [new events_targets.CodeBuildProject(this.project)],
});
}
this.alarm = this.project.metricFailedBuilds({ period: Duration.seconds(300) }).createAlarm(this, 'AutoPullRequestFailedAlarm', {
threshold: 1,
evaluationPeriods: 1,
treatMissingData: cloudwatch.TreatMissingData.IGNORE,
});
}