constructor()

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,
    });
  }