constructor()

in cdk/lib/printable-recipe-generator.ts [32:181]


	constructor(scope: GuStack, id: string, { eventBus }: TestConstructProps) {
		super(scope, id);

		const vpcid = new GuParameter(scope, 'VpcIdParam', {
			fromSSM: true,
			default: `/account/vpc/primary/id`,
		});

		const publicSubnetIds = new GuParameter(scope, 'VpcPublicParam', {
			fromSSM: true,
			default: '/account/vpc/primary/subnets/public',
			type: 'List<String>',
		});

		const privateSubnetIds = new GuParameter(scope, 'VpcPrivateParam', {
			fromSSM: true,
			default: '/account/vpc/primary/subnets/private',
			type: 'List<String>',
		});

		const availabilityZones = new GuParameter(scope, 'VpcAZParam', {
			fromSSM: true,
			default: '/account/vpc/primary/availability-zones',
			type: 'List<String>',
		});

		const vpc = Vpc.fromVpcAttributes(this, 'VPC', {
			vpcId: vpcid.valueAsString,
			publicSubnetIds: publicSubnetIds.valueAsList,
			privateSubnetIds: privateSubnetIds.valueAsList,
			availabilityZones: availabilityZones.valueAsList,
		});

		const clusterArnParam = new GuParameter(scope, 'ClusterArn', {
			fromSSM: true,
			default: '/account/services/ecs-cluster-name',
		});

		const repoNameParam = new GuParameter(scope, 'RepoName', {
			fromSSM: true,
			default: `/${scope.stage}/feast/recipes-backend/ecr-repo-printables`,
		});

		const cluster = Cluster.fromClusterAttributes(this, 'ECSCluster', {
			clusterName: clusterArnParam.valueAsString,
			vpc,
		});

		const role = new Role(this, 'IAMRole', {
			roleName: `printable-recipe-generator-${scope.stage}`,
			assumedBy: ServicePrincipal.fromStaticServicePrincipleName(
				'ecs-tasks.amazonaws.com',
			),
			inlinePolicies: {
				s3write: new PolicyDocument({
					statements: [
						new PolicyStatement({
							effect: Effect.ALLOW,
							actions: ['s3:PutObject'],
							resources: [
								`arn:aws:s3:::feast-recipes-static-${scope.stage.toLowerCase()}/content/*`,
							],
						}),
					],
				}),
			},
		});

		const taskDefinition = new TaskDefinition(this, 'PrintableRecipeGenTD', {
			compatibility: Compatibility.FARGATE,
			cpu: '1024', //1 vcpu
			ephemeralStorageGiB: 25, //minimum 21Gb ephemeral storage
			memoryMiB: '2048', //2 Gb RAM, minimum value for 1vcpu
			runtimePlatform: {
				cpuArchitecture: CpuArchitecture.X86_64, //Chrome headless does not appear to have an ARM64 package at present :-/
				operatingSystemFamily: OperatingSystemFamily.LINUX,
			},
			volumes: [{ name: 'tmp-volume' }, { name: 'home-volume' }],
			taskRole: role,
		});

		const ecrRepo = Repository.fromRepositoryName(
			this,
			'ECRRepo',
			repoNameParam.valueAsString,
		);

		const imageTag = process.env['IMAGE_TAG'] ?? 'latest';

		const container = taskDefinition.addContainer('main', {
			image: ContainerImage.fromEcrRepository(ecrRepo, imageTag),
			memoryLimitMiB: 2048,
			readonlyRootFilesystem: true,
			logging: LogDriver.awsLogs({
				streamPrefix: 'printable-recipe-generator-',
			}),
			workingDirectory: '/home/pdfrender',
		});

		container.addMountPoints({
			sourceVolume: 'tmp-volume',
			containerPath: '/tmp',
			readOnly: false,
		});

		container.addMountPoints({
			sourceVolume: 'home-volume',
			containerPath: '/home/pdfrender',
			readOnly: false,
		});

		const ruleTarget = new EcsTask({
			taskDefinition,
			cluster,
			launchType: LaunchType.FARGATE,
			containerOverrides: [
				{
					containerName: 'main',
					environment: [
						{
							name: 'RECIPE_UID',
							value: EventField.fromPath('$.detail.uid'),
						},
						{
							name: 'RECIPE_CSID',
							value: EventField.fromPath('$.detail.checksum'),
						},
						{
							name: 'CONTENT',
							value: EventField.fromPath('$.detail.blob'),
						},
						{
							name: 'BUCKET',
							value: `feast-recipes-static-${scope.stage.toLowerCase()}`,
						},
					],
				},
			],
		});

		new Rule(this, 'PublicationConnect', {
			enabled: scope.stage !== 'PROD', //only enabled in the CODE environment until design work fully implemented, but available for testing in PROD
			eventBus,
			targets: [ruleTarget],
			eventPattern: {
				source: ['recipe-responder'],
				detailType: ['recipe-update'],
			},
		});
	}