import * as SDK from 'aws-sdk'; import CloudRunnerTaskDef from '../cloud-runner-services/cloud-runner-task-def'; import CloudRunnerSecret from '../cloud-runner-services/cloud-runner-secret'; import CloudRunnerConstants from '../cloud-runner-services/cloud-runner-constants'; import { customAlphabet } from 'nanoid'; import { AWSBaseStack } from './aws-base-stack'; import { AWSTemplates } from './aws-templates'; import CloudRunnerLogger from '../cloud-runner-services/cloud-runner-logger'; import * as fs from 'fs'; import { AWSError } from './aws-error'; export class AWSJobStack { private baseStackName: string; constructor(baseStackName: string) { this.baseStackName = baseStackName; } public async setupCloudFormations( CF: SDK.CloudFormation, buildGuid: string, image: string, entrypoint: string[], commands: string[], mountdir: string, workingdir: string, secrets: CloudRunnerSecret[], ): Promise { const logGuid = customAlphabet(CloudRunnerConstants.alphabet, 9)(); commands[1] += ` echo "${logGuid}" `; await new AWSBaseStack(this.baseStackName).setupBaseStack(CF); const taskDefStackName = `${this.baseStackName}-${buildGuid}`; let taskDefCloudFormation = AWSTemplates.readTaskCloudFormationTemplate(); const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`; const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8'); for (const secret of secrets) { if (typeof secret.ParameterValue == 'number') { secret.ParameterValue = `${secret.ParameterValue}`; } if (!secret.ParameterValue || secret.ParameterValue === '') { secrets = secrets.filter((x) => x !== secret); continue; } taskDefCloudFormation = AWSTemplates.insertAtTemplate( taskDefCloudFormation, 'p1 - input', AWSTemplates.getParameterTemplate(secret.ParameterKey.replace(/[^\dA-Za-z]/g, '')), ); taskDefCloudFormation = AWSTemplates.insertAtTemplate( taskDefCloudFormation, 'p2 - secret', AWSTemplates.getSecretTemplate(secret.ParameterKey.replace(/[^\dA-Za-z]/g, '')), ); taskDefCloudFormation = AWSTemplates.insertAtTemplate( taskDefCloudFormation, 'p3 - container def', AWSTemplates.getSecretDefinitionTemplate( secret.EnvironmentVariable, secret.ParameterKey.replace(/[^\dA-Za-z]/g, ''), ), ); } const secretsMappedToCloudFormationParameters = secrets.map((x) => { return { ParameterKey: x.ParameterKey.replace(/[^\dA-Za-z]/g, ''), ParameterValue: x.ParameterValue }; }); const parameters = [ { ParameterKey: 'EnvironmentName', ParameterValue: this.baseStackName, }, { ParameterKey: 'ImageUrl', ParameterValue: image, }, { ParameterKey: 'ServiceName', ParameterValue: taskDefStackName, }, { ParameterKey: 'Command', ParameterValue: 'echo "this template should be overwritten when running a task"', }, { ParameterKey: 'EntryPoint', ParameterValue: entrypoint.join(','), }, { ParameterKey: 'WorkingDirectory', ParameterValue: workingdir, }, { ParameterKey: 'EFSMountDirectory', ParameterValue: mountdir, }, { ParameterKey: 'BUILDID', ParameterValue: buildGuid, }, ...secretsMappedToCloudFormationParameters, ]; try { await CF.createStack({ StackName: taskDefStackName, TemplateBody: taskDefCloudFormation, Capabilities: ['CAPABILITY_IAM'], Parameters: parameters, }).promise(); CloudRunnerLogger.log('Creating cloud runner job'); await CF.createStack({ StackName: cleanupTaskDefStackName, TemplateBody: cleanupCloudFormation, Capabilities: ['CAPABILITY_IAM'], Parameters: [ { ParameterKey: 'StackName', ParameterValue: taskDefStackName, }, { ParameterKey: 'DeleteStackName', ParameterValue: cleanupTaskDefStackName, }, { ParameterKey: 'TTL', ParameterValue: '100', }, { ParameterKey: 'BUILDID', ParameterValue: buildGuid, }, { ParameterKey: 'EnvironmentName', ParameterValue: this.baseStackName, }, ], }).promise(); await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise(); } catch (error) { await AWSError.handleStackCreationFailure( error, CF, taskDefStackName, taskDefCloudFormation, parameters, secrets, ); throw error; } const taskDefResources = ( await CF.describeStackResources({ StackName: taskDefStackName, }).promise() ).StackResources; const baseResources = (await CF.describeStackResources({ StackName: this.baseStackName }).promise()).StackResources; // TODO: offer a parameter to decide if you want the guaranteed shutdown or fastest startup time possible return { taskDefStackName, taskDefCloudFormation, taskDefStackNameTTL: cleanupTaskDefStackName, ttlCloudFormation: cleanupCloudFormation, taskDefResources, baseResources, logid: logGuid, }; } }