diff --git a/package-lock.json b/package-lock.json index c23eb55c..2c464ffc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10316,6 +10316,10 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obligatron": { + "resolved": "packages/obligatron", + "link": true + }, "node_modules/octokit": { "version": "3.1.2", "license": "MIT", @@ -13724,6 +13728,9 @@ "@octokit/types": "^12.5.0" } }, + "packages/obligatron": { + "version": "1.0.0" + }, "packages/repocop": { "version": "1.0.0", "dependencies": { diff --git a/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap b/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap index 10fb3b55..e85ec4f9 100644 --- a/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap +++ b/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap @@ -2,6 +2,115 @@ exports[`The ServiceCatalogue stack matches the snapshot 1`] = ` { + "Mappings": { + "ServiceprincipalMap": { + "af-south-1": { + "states": "states.af-south-1.amazonaws.com", + }, + "ap-east-1": { + "states": "states.ap-east-1.amazonaws.com", + }, + "ap-northeast-1": { + "states": "states.ap-northeast-1.amazonaws.com", + }, + "ap-northeast-2": { + "states": "states.ap-northeast-2.amazonaws.com", + }, + "ap-northeast-3": { + "states": "states.ap-northeast-3.amazonaws.com", + }, + "ap-south-1": { + "states": "states.ap-south-1.amazonaws.com", + }, + "ap-south-2": { + "states": "states.ap-south-2.amazonaws.com", + }, + "ap-southeast-1": { + "states": "states.ap-southeast-1.amazonaws.com", + }, + "ap-southeast-2": { + "states": "states.ap-southeast-2.amazonaws.com", + }, + "ap-southeast-3": { + "states": "states.ap-southeast-3.amazonaws.com", + }, + "ap-southeast-4": { + "states": "states.ap-southeast-4.amazonaws.com", + }, + "ca-central-1": { + "states": "states.ca-central-1.amazonaws.com", + }, + "cn-north-1": { + "states": "states.cn-north-1.amazonaws.com", + }, + "cn-northwest-1": { + "states": "states.cn-northwest-1.amazonaws.com", + }, + "eu-central-1": { + "states": "states.eu-central-1.amazonaws.com", + }, + "eu-central-2": { + "states": "states.eu-central-2.amazonaws.com", + }, + "eu-north-1": { + "states": "states.eu-north-1.amazonaws.com", + }, + "eu-south-1": { + "states": "states.eu-south-1.amazonaws.com", + }, + "eu-south-2": { + "states": "states.eu-south-2.amazonaws.com", + }, + "eu-west-1": { + "states": "states.eu-west-1.amazonaws.com", + }, + "eu-west-2": { + "states": "states.eu-west-2.amazonaws.com", + }, + "eu-west-3": { + "states": "states.eu-west-3.amazonaws.com", + }, + "il-central-1": { + "states": "states.il-central-1.amazonaws.com", + }, + "me-central-1": { + "states": "states.me-central-1.amazonaws.com", + }, + "me-south-1": { + "states": "states.me-south-1.amazonaws.com", + }, + "sa-east-1": { + "states": "states.sa-east-1.amazonaws.com", + }, + "us-east-1": { + "states": "states.us-east-1.amazonaws.com", + }, + "us-east-2": { + "states": "states.us-east-2.amazonaws.com", + }, + "us-gov-east-1": { + "states": "states.us-gov-east-1.amazonaws.com", + }, + "us-gov-west-1": { + "states": "states.us-gov-west-1.amazonaws.com", + }, + "us-iso-east-1": { + "states": "states.amazonaws.com", + }, + "us-iso-west-1": { + "states": "states.amazonaws.com", + }, + "us-isob-east-1": { + "states": "states.amazonaws.com", + }, + "us-west-1": { + "states": "states.us-west-1.amazonaws.com", + }, + "us-west-2": { + "states": "states.us-west-2.amazonaws.com", + }, + }, + }, "Metadata": { "gu:cdk:constructs": [ "GuSubnetListParameter", @@ -18,6 +127,9 @@ exports[`The ServiceCatalogue stack matches the snapshot 1`] = ` "GuLambdaFunction", "GuScheduledLambda", "GuLambdaFunction", + "GuLambdaFunction", + "GuLambdaFunction", + "GuLambdaFunction", ], "gu:cdk:version": "TEST", }, @@ -16573,6 +16685,858 @@ spec: }, "Type": "AWS::IAM::Policy", }, + "ObligatronLambdaFindAllTeams14A33571": { + "DependsOn": [ + "ObligatronLambdaFindAllTeamsServiceRoleDefaultPolicy60036B7E", + "ObligatronLambdaFindAllTeamsServiceRole2F258FB9", + ], + "Properties": { + "Architectures": [ + "arm64", + ], + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName", + }, + "S3Key": "deploy/TEST/obligatron/obligatron.zip", + }, + "Environment": { + "Variables": { + "APP": "obligatron", + "STACK": "deploy", + "STAGE": "TEST", + }, + }, + "Handler": "FindAllTeams/index.handler", + "LoggingConfig": { + "LogFormat": "JSON", + }, + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "ObligatronLambdaFindAllTeamsServiceRole2F258FB9", + "Arn", + ], + }, + "Runtime": "nodejs20.x", + "Tags": [ + { + "Key": "App", + "Value": "obligatron", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + "Timeout": 30, + }, + "Type": "AWS::Lambda::Function", + }, + "ObligatronLambdaFindAllTeamsServiceRole2F258FB9": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "obligatron", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ObligatronLambdaFindAllTeamsServiceRoleDefaultPolicy60036B7E": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Ref": "DistributionBucketName", + }, + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Ref": "DistributionBucketName", + }, + "/deploy/TEST/obligatron/obligatron.zip", + ], + ], + }, + ], + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":parameter/TEST/deploy/obligatron", + ], + ], + }, + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":parameter/TEST/deploy/obligatron/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ObligatronLambdaFindAllTeamsServiceRoleDefaultPolicy60036B7E", + "Roles": [ + { + "Ref": "ObligatronLambdaFindAllTeamsServiceRole2F258FB9", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "ObligatronLambdaSaveObligationResults571BA4CC": { + "DependsOn": [ + "ObligatronLambdaSaveObligationResultsServiceRoleDefaultPolicyC86DBFA3", + "ObligatronLambdaSaveObligationResultsServiceRole9357A026", + ], + "Properties": { + "Architectures": [ + "arm64", + ], + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName", + }, + "S3Key": "deploy/TEST/obligatron/obligatron.zip", + }, + "Environment": { + "Variables": { + "APP": "obligatron", + "STACK": "deploy", + "STAGE": "TEST", + }, + }, + "Handler": "SaveObligationResults/index.handler", + "LoggingConfig": { + "LogFormat": "JSON", + }, + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "ObligatronLambdaSaveObligationResultsServiceRole9357A026", + "Arn", + ], + }, + "Runtime": "nodejs20.x", + "Tags": [ + { + "Key": "App", + "Value": "obligatron", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + "Timeout": 30, + }, + "Type": "AWS::Lambda::Function", + }, + "ObligatronLambdaSaveObligationResultsServiceRole9357A026": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "obligatron", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ObligatronLambdaSaveObligationResultsServiceRoleDefaultPolicyC86DBFA3": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Ref": "DistributionBucketName", + }, + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Ref": "DistributionBucketName", + }, + "/deploy/TEST/obligatron/obligatron.zip", + ], + ], + }, + ], + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":parameter/TEST/deploy/obligatron", + ], + ], + }, + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":parameter/TEST/deploy/obligatron/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ObligatronLambdaSaveObligationResultsServiceRoleDefaultPolicyC86DBFA3", + "Roles": [ + { + "Ref": "ObligatronLambdaSaveObligationResultsServiceRole9357A026", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "ObligatronLambdaTagging3D59DEAB": { + "DependsOn": [ + "ObligatronLambdaTaggingServiceRoleDefaultPolicy1D8436C5", + "ObligatronLambdaTaggingServiceRoleC009BA01", + ], + "Properties": { + "Architectures": [ + "arm64", + ], + "Code": { + "S3Bucket": { + "Ref": "DistributionBucketName", + }, + "S3Key": "deploy/TEST/obligatron/obligatron.zip", + }, + "Environment": { + "Variables": { + "APP": "obligatron", + "STACK": "deploy", + "STAGE": "TEST", + }, + }, + "Handler": "ObligationTagging/index.handler", + "LoggingConfig": { + "LogFormat": "JSON", + }, + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "ObligatronLambdaTaggingServiceRoleC009BA01", + "Arn", + ], + }, + "Runtime": "nodejs20.x", + "Tags": [ + { + "Key": "App", + "Value": "obligatron", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + "Timeout": 30, + }, + "Type": "AWS::Lambda::Function", + }, + "ObligatronLambdaTaggingServiceRoleC009BA01": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "obligatron", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ObligatronLambdaTaggingServiceRoleDefaultPolicy1D8436C5": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Ref": "DistributionBucketName", + }, + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":s3:::", + { + "Ref": "DistributionBucketName", + }, + "/deploy/TEST/obligatron/obligatron.zip", + ], + ], + }, + ], + }, + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":parameter/TEST/deploy/obligatron", + ], + ], + }, + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":parameter/TEST/deploy/obligatron/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ObligatronLambdaTaggingServiceRoleDefaultPolicy1D8436C5", + "Roles": [ + { + "Ref": "ObligatronLambdaTaggingServiceRoleC009BA01", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "ObligatronStateMachineA4B7794A": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "ObligatronStateMachineRoleDefaultPolicy0495271D", + "ObligatronStateMachineRole076DDDF0", + ], + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{"StartAt":"FindAllTeams","States":{"FindAllTeams":{"Next":"MapAllTeams","Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","Resource":"arn:", + { + "Ref": "AWS::Partition", + }, + ":states:::lambda:invoke","Parameters":{"FunctionName":"", + { + "Fn::GetAtt": [ + "ObligatronLambdaFindAllTeams14A33571", + "Arn", + ], + }, + "","Payload.$":"$"}},"MapAllTeams":{"Type":"Map","Next":"ObligatronStepInvokeSaveObligationResults","ItemProcessor":{"ProcessorConfig":{"Mode":"INLINE"},"StartAt":"InvokeObligationsParallel","States":{"InvokeObligationsParallel":{"Type":"Parallel","End":true,"Branches":[{"StartAt":"InvokeObligatronLambdaTagging","States":{"InvokeObligatronLambdaTagging":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","Resource":"arn:", + { + "Ref": "AWS::Partition", + }, + ":states:::lambda:invoke","Parameters":{"FunctionName":"", + { + "Fn::GetAtt": [ + "ObligatronLambdaTagging3D59DEAB", + "Arn", + ], + }, + "","Payload.$":"$"}}}}]}}}},"ObligatronStepInvokeSaveObligationResults":{"End":true,"Retry":[{"ErrorEquals":["Lambda.ClientExecutionTimeoutException","Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException"],"IntervalSeconds":2,"MaxAttempts":6,"BackoffRate":2}],"Type":"Task","Resource":"arn:", + { + "Ref": "AWS::Partition", + }, + ":states:::lambda:invoke","Parameters":{"FunctionName":"", + { + "Fn::GetAtt": [ + "ObligatronLambdaSaveObligationResults571BA4CC", + "Arn", + ], + }, + "","Payload.$":"$"}}}}", + ], + ], + }, + "RoleArn": { + "Fn::GetAtt": [ + "ObligatronStateMachineRole076DDDF0", + "Arn", + ], + }, + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::StepFunctions::StateMachine", + "UpdateReplacePolicy": "Delete", + }, + "ObligatronStateMachineRole076DDDF0": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region", + }, + "states", + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ObligatronStateMachineRoleDefaultPolicy0495271D": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ObligatronLambdaFindAllTeams14A33571", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ObligatronLambdaFindAllTeams14A33571", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ObligatronLambdaSaveObligationResults571BA4CC", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ObligatronLambdaSaveObligationResults571BA4CC", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ObligatronLambdaTagging3D59DEAB", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ObligatronLambdaTagging3D59DEAB", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ObligatronStateMachineRoleDefaultPolicy0495271D", + "Roles": [ + { + "Ref": "ObligatronStateMachineRole076DDDF0", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "PostgresAccessSecurityGroupParam38DFE001": { "Properties": { "DataType": "text", diff --git a/packages/cdk/lib/obligatron.ts b/packages/cdk/lib/obligatron.ts new file mode 100644 index 00000000..807e9eb5 --- /dev/null +++ b/packages/cdk/lib/obligatron.ts @@ -0,0 +1,75 @@ +import { GuStack } from '@guardian/cdk/lib/constructs/core'; +import { GuLambdaFunction } from '@guardian/cdk/lib/constructs/lambda'; +import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'; +import { + DefinitionBody, + Map, + Parallel, + StateMachine, +} from 'aws-cdk-lib/aws-stepfunctions'; +import { LambdaInvoke } from 'aws-cdk-lib/aws-stepfunctions-tasks'; + +export class Obligatron { + constructor(stack: GuStack) { + const app = 'obligatron'; + + const findAllTeams = new GuLambdaFunction( + stack, + 'ObligatronLambdaFindAllTeams', + { + app, + fileName: `${app}.zip`, + handler: 'FindAllTeams/index.handler', + runtime: Runtime.NODEJS_20_X, + architecture: Architecture.ARM_64, + }, + ); + + const obligationLambas = [ + new GuLambdaFunction(stack, `ObligatronLambdaTagging`, { + app, + fileName: `${app}.zip`, + handler: `ObligationTagging/index.handler`, + runtime: Runtime.NODEJS_20_X, + architecture: Architecture.ARM_64, + }), + ]; + + const saveObligationResults = new GuLambdaFunction( + stack, + 'ObligatronLambdaSaveObligationResults', + { + app, + fileName: `${app}.zip`, + handler: 'SaveObligationResults/index.handler', + runtime: Runtime.NODEJS_20_X, + architecture: Architecture.ARM_64, + }, + ); + + const definition = new LambdaInvoke(stack, 'FindAllTeams', { + lambdaFunction: findAllTeams, + }) + .next( + new Map(stack, 'MapAllTeams').itemProcessor( + new Parallel(stack, 'InvokeObligationsParallel').branch( + ...obligationLambas.map( + (lambda) => + new LambdaInvoke(stack, `Invoke${stack.getLogicalId(lambda)}`, { + lambdaFunction: lambda, + }), + ), + ), + ), + ) + .next( + new LambdaInvoke(stack, 'ObligatronStepInvokeSaveObligationResults', { + lambdaFunction: saveObligationResults, + }), + ); + + new StateMachine(stack, 'ObligatronStateMachine', { + definitionBody: DefinitionBody.fromChainable(definition), + }); + } +} diff --git a/packages/cdk/lib/service-catalogue.ts b/packages/cdk/lib/service-catalogue.ts index 4142bee4..dcc4ae1b 100644 --- a/packages/cdk/lib/service-catalogue.ts +++ b/packages/cdk/lib/service-catalogue.ts @@ -45,6 +45,7 @@ import { addGithubActionsUsageLambda } from './github-actions-usage'; import { InteractiveMonitor } from './interactive-monitor'; import { addPrismaMigrateTask } from './prisma-migrate-task'; import { Repocop } from './repocop'; +import { Obligatron } from './obligatron'; interface ServiceCatalogueProps extends GuStackProps { //TODO add fields for every kind of job to make schedule explicit at a glance. @@ -256,5 +257,7 @@ export class ServiceCatalogue extends GuStack { dbAccess: applicationToPostgresSecurityGroup, cluster: cloudqueryCluster, }); + + new Obligatron(this); } } diff --git a/packages/obligatron/lambdas/FindAllTeams/index.ts b/packages/obligatron/lambdas/FindAllTeams/index.ts new file mode 100644 index 00000000..f44bde6c --- /dev/null +++ b/packages/obligatron/lambdas/FindAllTeams/index.ts @@ -0,0 +1,10 @@ +import { getObligatronPrismaClient } from '../../lib/db'; + +export const handler = async () => { + const prismaClient = await getObligatronPrismaClient(); + + // Find all teams in Galaxies + const teams = await prismaClient.galaxies_teams_table.findMany(); + + return teams.map((team) => team.team_id); +}; diff --git a/packages/obligatron/lambdas/ObligationTagging/index.ts b/packages/obligatron/lambdas/ObligationTagging/index.ts new file mode 100644 index 00000000..1e5ac52a --- /dev/null +++ b/packages/obligatron/lambdas/ObligationTagging/index.ts @@ -0,0 +1,14 @@ +export const handler = async (input: ObligationLambdaInput[]) => { + // load rows from service-catalogue / AWS / Github + // do some calculation + + return { + ...input, + // Has a team passed the obligation? + passed: true, + // Calculate a score (between 0 and 1) to give us a more fine grained view on whether teams are improving. + // Should be 1 if they have passed. Calculation will change based on the obligation + // For example, for the tagging obligation this could be (Untagged Resources / Total Resources) + score: 1, + }; +}; diff --git a/packages/obligatron/lambdas/SaveObligationResults/index.ts b/packages/obligatron/lambdas/SaveObligationResults/index.ts new file mode 100644 index 00000000..89d21049 --- /dev/null +++ b/packages/obligatron/lambdas/SaveObligationResults/index.ts @@ -0,0 +1,4 @@ +export const handler = async (input: ObligationLambdaOutput[]) => { + // TODO: Save results to DB instead of logging. + console.log(JSON.stringify(input)); +}; diff --git a/packages/obligatron/lib/db.ts b/packages/obligatron/lib/db.ts new file mode 100644 index 00000000..5bd1c11b --- /dev/null +++ b/packages/obligatron/lib/db.ts @@ -0,0 +1,25 @@ +import { + DatabaseConfig, + getDatabaseConfig, + getDatabaseConnectionString, + getDevDatabaseConfig, + getPrismaClient, +} from 'common/database'; +import { getEnvOrThrow } from 'common/functions'; +import { PrismaClient } from '@prisma/client'; + +export const getObligatronPrismaClient = async (): Promise => { + const stage = getEnvOrThrow('STAGE'); + + const databaseConfig: DatabaseConfig = + stage === 'DEV' + ? await getDevDatabaseConfig() + : await getDatabaseConfig(stage, 'obligatron'); + + const queryLogging = (process.env['QUERY_LOGGING'] ?? 'false') === 'true'; + + return getPrismaClient({ + databaseConnectionString: getDatabaseConnectionString(databaseConfig), + withQueryLogging: queryLogging, + }); +}; diff --git a/packages/obligatron/lib/types.ts b/packages/obligatron/lib/types.ts new file mode 100644 index 00000000..d9c98d1a --- /dev/null +++ b/packages/obligatron/lib/types.ts @@ -0,0 +1,10 @@ +type ObligationLambdaInput = { + teamId: string; +}; + +type ObligationLambdaOutput = { + teamId: string; + passed: boolean; + score: number; + drillDownUrl?: string; +}; diff --git a/packages/obligatron/package.json b/packages/obligatron/package.json new file mode 100644 index 00000000..abbf5f6c --- /dev/null +++ b/packages/obligatron/package.json @@ -0,0 +1,10 @@ +{ + "name": "obligatron", + "version": "1.0.0", + "description": "A collection of lambdas which evaluate teams adherances to obligations", + "scripts": { + "start": "APP=cloudbuster tsx src/run-locally.ts", + "build": "esbuild lambdas/**/* --bundle --platform=node --target=node20 --outdir=dist --external:@aws-sdk --external:@prisma/client --external:prisma" + }, + "dependencies": {} +} diff --git a/scripts/ci.sh b/scripts/ci.sh index 4d11aae6..ceff423c 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -75,3 +75,4 @@ createPrismaZip createLambdaWithPrisma "repocop" createLambdaWithPrisma "data-audit" createLambdaWithPrisma "github-actions-usage" +createLambdaWithPrisma "obligatron"