packages/cdk/lib/repository.ts (145 lines of code) (raw):

import type { GuStackProps } from '@guardian/cdk/lib/constructs/core'; import { GuStack, GuStringParameter } from '@guardian/cdk/lib/constructs/core'; import type { App } from 'aws-cdk-lib'; import { CfnOutput, Fn, RemovalPolicy } from 'aws-cdk-lib'; import { Repository, TagMutability } from 'aws-cdk-lib/aws-ecr'; import { AccountPrincipal, ArnPrincipal, Effect, PolicyDocument, PolicyStatement, Role, } from 'aws-cdk-lib/aws-iam'; export class TranscriptionServiceRepository extends GuStack { constructor(scope: App, id: string, props: GuStackProps) { super(scope, id, props); const githubActionsIAMRoleArn = new GuStringParameter( this, 'GithubActionsIAMRoleArn', { description: 'IAM role for role used by github actions workflows', }, ); const deployToolsAccountNumber = new GuStringParameter( this, 'DeployToolsAccount', { description: 'Deploy tools account id - needed to give AMIgo access to this repository', }, ); const transcriptionRepository = new Repository( this, 'TranscriptionServiceRepository', { repositoryName: `transcription-service`, // these are confusing - see https://docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html lifecycleRules: [ // keep most recent 20 images tagged with 'main' { tagPrefixList: ['main'], maxImageCount: 20, rulePriority: 1, }, // keep 10 most recent images not tagged with 'main' { maxImageCount: 10, rulePriority: 2, }, ], imageTagMutability: TagMutability.MUTABLE, removalPolicy: RemovalPolicy.DESTROY, imageScanOnPush: true, }, ); const mediaDownloadRepository = new Repository( this, 'MediaDownloadRepository', { repositoryName: `transcription-service-media-download`, // these are confusing - see https://docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html lifecycleRules: [ // keep most recent 20 images tagged with 'main' { tagPrefixList: ['main'], maxImageCount: 20, rulePriority: 1, }, // keep 10 most recent images not tagged with 'main' { maxImageCount: 10, rulePriority: 2, }, ], imageTagMutability: TagMutability.MUTABLE, removalPolicy: RemovalPolicy.DESTROY, imageScanOnPush: true, }, ); // allow transcription workers read access to the repo const workerReadPolicyStatement = new PolicyStatement({ principals: [ new ArnPrincipal(Fn.importValue(`WorkerRoleArn-CODE`)), new ArnPrincipal(Fn.importValue(`WorkerRoleArn-PROD`)), ], actions: [ 'ecr:GetAuthorizationToken', 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', 'ecr:GetRepositoryPolicy', 'ecr:ListImages', 'ecr:DescribeImages', 'ecr:BatchGetImage', ], effect: Effect.ALLOW, }); transcriptionRepository.addToResourcePolicy(workerReadPolicyStatement); // allow github actions read/write access to the repo const githubReadWritePolicyStatement = new PolicyStatement({ principals: [new ArnPrincipal(githubActionsIAMRoleArn.valueAsString)], actions: [ 'ecr:GetAuthorizationToken', 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', 'ecr:GetRepositoryPolicy', 'ecr:DescribeRepositories', 'ecr:ListImages', 'ecr:DescribeImages', 'ecr:BatchGetImage', 'ecr:InitiateLayerUpload', 'ecr:UploadLayerPart', 'ecr:CompleteLayerUpload', 'ecr:PutImage', ], effect: Effect.ALLOW, }); transcriptionRepository.addToResourcePolicy(githubReadWritePolicyStatement); mediaDownloadRepository.addToResourcePolicy(githubReadWritePolicyStatement); const repoAccessRole = new Role(this, 'RepoAccessRole', { roleName: 'TranscriptionServiceRepoAccessRole', assumedBy: new AccountPrincipal(deployToolsAccountNumber.valueAsString), inlinePolicies: { TranscriptionServiceRepoAccessPolicy: new PolicyDocument({ statements: [ new PolicyStatement({ actions: ['ecr:GetAuthorizationToken'], resources: ['*'], effect: Effect.ALLOW, }), new PolicyStatement({ actions: [ 'ecr:GetDownloadUrlForLayer', 'ecr:BatchGetImage', 'ecr:BatchCheckLayerAvailability', 'ecr:DescribeImages', 'ecr:ListImages', 'ecr:GetDownloadUrlForLayer', ], resources: [ transcriptionRepository.repositoryArn, mediaDownloadRepository.repositoryArn, ], effect: Effect.ALLOW, }), ], }), }, }); new CfnOutput(this, 'AccessRoleArn', { value: repoAccessRole.roleArn, }); } }