constructor()

in cdk/lib/bandit.ts [23:191]


	constructor(scope: App, id: string, props: GuStackProps) {
		super(scope, id, props);

		const loggingFormat = LoggingFormat.TEXT;

		const getBanditTestsLambda = new GuLambdaFunction(
			this,
			'get-bandit-tests',
			{
				app: appName,
				functionName: `${appName}-get-bandit-tests-${this.stage}`,
				runtime: Runtime.NODEJS_20_X,
				handler: 'get-bandit-tests/get-bandit-tests.run',
				fileName: `${appName}.zip`,
				loggingFormat,
				initialPolicy: [
					new PolicyStatement({
						actions: ['dynamodb:Query'],
						resources: [
							`arn:aws:dynamodb:eu-west-1:${this.account}:table/support-admin-console-channel-tests-${this.stage}`,
						],
					}),
				],
			},
		);

		const getBanditTestsTask = new LambdaInvoke(
			this,
			'get-bandit-tests-task',
			{
				lambdaFunction: getBanditTestsLambda,
			},
		);

		const banditsTable = new Table(this, 'bandits-table', {
			tableName: `support-bandit-${this.stage}`,
			removalPolicy: RemovalPolicy.RETAIN,
			billingMode: BillingMode.PAY_PER_REQUEST,
			partitionKey: {
				name: 'testName',
				type: AttributeType.STRING,
			},
			sortKey: {
				name: 'timestamp',
				type: AttributeType.STRING,
			},
			pointInTimeRecovery: this.stage === 'PROD',
			timeToLiveAttribute: 'ttlInSeconds',
		});

		const queryLambdaRole = new Role(this, 'query-lambda-role', {
			// Set the name of the role rather than using an autogenerated name.
			// This is in preparation for the migration to BigQuery, because if the ARN is too long then it breaks the authentication request to GCP
			roleName: `bandit-query-${this.stage}`,
			assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
		});
		queryLambdaRole.addToPolicy(
			new PolicyStatement({
				actions: [
					'logs:CreateLogGroup',
					'logs:CreateLogStream',
					'logs:PutLogEvents',
				],
				resources: ['*'],
			}),
		);
		queryLambdaRole.addToPolicy(
			new PolicyStatement({
				actions: ['ssm:GetParameter'],
				resources: [
					`arn:aws:ssm:${this.region}:${this.account}:parameter/bandit-testing/${this.stage}/gcp-wif-credentials-config`,
				],
			}),
		);

		queryLambdaRole.addToPolicy(
			new PolicyStatement({
				actions: ['dynamodb:BatchWriteItem'],
				resources: [banditsTable.tableArn],
			}),
		);

		const queryLambda = new GuLambdaFunction(this, 'query-lambda', {
			app: appName,
			functionName: `${appName}-query-${this.stage}`,
			runtime: Runtime.NODEJS_20_X,
			handler: 'query-lambda/query-lambda.run',
			fileName: `${appName}.zip`,
			loggingFormat,
			timeout: Duration.seconds(60),
			role: queryLambdaRole,
		});

		const queryTask = new LambdaInvoke(this, 'query-task', {
			lambdaFunction: queryLambda,
			inputPath: '$.Payload',
		});

		const stateMachine = new StateMachine(this, 'state-machine', {
			stateMachineName: `${appName}-${this.stage}`,
			definitionBody: DefinitionBody.fromChainable(
				getBanditTestsTask
					.next(
						queryTask.addRetry({
							errors: [Errors.ALL],
							interval: Duration.minutes(2),
							maxAttempts: 5,
							backoffRate: 2,
						}),
					)
					.next(new Succeed(this, 'state-machine-success')),
			),
		});

		new Rule(this, `${appName}Startup`, {
			enabled: true,
			targets: [
				new SfnStateMachine(stateMachine, {
					input: RuleTargetInput.fromObject({}),
				}),
			],
			schedule: Schedule.cron({ minute: '15' }),
			ruleName: `${appName}-startup-${this.stage}`,
		});
		// Alarms
		const isProd = this.stage === 'PROD';

		new GuAlarm(this, 'TimeoutAlarm', {
			app: appName,
			actionsEnabled: isProd,
			snsTopicName: `alarms-handler-topic-${this.stage}`,
			alarmName: `Support Bandit Timeout in ${this.stage}.`,
			alarmDescription: `There was a timeout whilst setting up bandit data. Check https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/statemachines/view/arn%3Aaws%3Astates%3Aeu-west-1%3A865473395570%3AstateMachine%3Asupport-bandit-${this.stage}?statusFilter=TIMED_OUT`,
			metric: new Metric({
				metricName: 'ExecutionsTimedOut',
				namespace: 'AWS/States',
				dimensionsMap: {
					StateMachineArn: stateMachine.stateMachineArn,
				},
				statistic: 'Sum',
				period: Duration.seconds(60),
			}),
			comparisonOperator:
				ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
			evaluationPeriods: 1,
			threshold: 1,
		}).node.addDependency(stateMachine);

		new GuAlarm(this, 'ExecutionFailureAlarm', {
			app: appName,
			actionsEnabled: isProd,
			snsTopicName: `alarms-handler-topic-${this.stage}`,
			alarmName: `Execution Failure in Support Bandit ${this.stage}.`,
			alarmDescription: `There was a failure whilst setting up support bandit data . Check https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/statemachines/view/arn%3Aaws%3Astates%3Aeu-west-1%3A865473395570%3AstateMachine%3Asupport-bandit-${this.stage}?statusFilter=FAILED`,
			metric: new Metric({
				metricName: 'ExecutionsFailed',
				namespace: 'AWS/States',
				dimensionsMap: {
					StateMachineArn: stateMachine.stateMachineArn,
				},
				statistic: 'Sum',
				period: Duration.seconds(60),
			}),
			comparisonOperator:
				ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
			evaluationPeriods: 1,
			threshold: 1,
		}).node.addDependency(stateMachine);
	}