diff --git a/src/lib/aws.ts b/src/lib/aws.ts index ac06c9d..6aabf4b 100644 --- a/src/lib/aws.ts +++ b/src/lib/aws.ts @@ -19,6 +19,7 @@ import { GetResourcesCommandOutput, ResourceGroupsTaggingAPIClient, } from '@aws-sdk/client-resource-groups-tagging-api' +import { ensureValueWithExchange } from './utils' export const RUN_ID_TAG_KEY = 'runId' export const TITLE_TAG_KEY = 'title' @@ -46,7 +47,7 @@ export async function registerECSTaskDefinition( // The following defines a new family versus versioning the base family const containerDefinition = baseTaskDefinition.containerDefinitions?.map((container) => { - const environment = container.environment || [] + const environment = ensureValueWithExchange(container.environment, []) environment.push({ name: 'TRUSTED_OUTPUT_ENDPOINT', diff --git a/src/lib/run-studies.ts b/src/lib/run-studies.ts index 4cad448..8973a15 100644 --- a/src/lib/run-studies.ts +++ b/src/lib/run-studies.ts @@ -11,7 +11,7 @@ import { getTaskDefinitionsWithRunId, deleteECSTaskDefinitions, } from './aws' -import { filterManagementAppRuns, filterOrphanTaskDefinitions } from './utils' +import { ensureValueWithExchange, ensureValueWithError, filterManagementAppRuns, filterOrphanTaskDefinitions } from './utils' import { managementAppGetRunnableStudiesRequest, toaGetRunsRequest } from './api' import 'dotenv/config' import { ManagementAppGetRunnableStudiesResponse } from './types' @@ -32,10 +32,10 @@ async function launchStudy( { key: TITLE_TAG_KEY, value: studyTitle }, ] const baseTaskDefinitionData = await getECSTaskDefinition(client, baseTaskDefinition) - - if (baseTaskDefinitionData.taskDefinition === undefined) { - throw new Error(`Could not find task definition data for ${baseTaskDefinition}`) - } + baseTaskDefinitionData.taskDefinition = ensureValueWithError( + baseTaskDefinitionData.taskDefinition, + `Could not find task definition data for ${baseTaskDefinition}`, + ) const newTaskDefinitionFamily = `${baseTaskDefinitionData.taskDefinition.family}-${runId}` @@ -47,10 +47,14 @@ async function launchStudy( imageLocation, taskTags, ) - - if (registerTaskDefResponse.taskDefinition?.family === undefined) { - throw new Error('Generated task definition has undefined family') - } + registerTaskDefResponse.taskDefinition = ensureValueWithError( + registerTaskDefResponse.taskDefinition, + `Could not register task definition ${newTaskDefinitionFamily}`, + ) + registerTaskDefResponse.taskDefinition.family = ensureValueWithError( + registerTaskDefResponse.taskDefinition.family, + 'Generated task definition has undefined family', + ) return await runECSFargateTask( client, @@ -78,7 +82,10 @@ async function cleanupTaskDefs( ecsClient: ECSClient, ) { // Garbage collect orphan task definitions - const taskDefsWithRunId = (await getTaskDefinitionsWithRunId(taggingClient)).ResourceTagMappingList || [] + const taskDefsWithRunId = ensureValueWithExchange( + (await getTaskDefinitionsWithRunId(taggingClient)).ResourceTagMappingList, + [], + ) const orphanTaskDefinitions = filterOrphanTaskDefinitions(bmaResults, taskDefsWithRunId) await deleteECSTaskDefinitions(ecsClient, orphanTaskDefinitions) } @@ -88,10 +95,10 @@ export async function runStudies(options: { ignoreAWSRuns: boolean }): Promise { it('filters out runs in the TOA', () => { @@ -91,3 +91,29 @@ describe('filterOrphanTaskDefinitions', () => { expect(filterOrphanTaskDefinitions(mockManagementAppResponse, mockTaskDefResources)).toStrictEqual(['arn2']) }) }) + +describe('ensureValueWithError', () => { + it('makes sure values are defined', () => { + expect(ensureValueWithError(10)).toBe(10) + }) + + it('responds with the given error message if values are undefined', () => { + expect(() => ensureValueWithError(null, "Can't be null or undefined")).toThrowError("Can't be null or undefined") + expect(() => ensureValueWithError(undefined)).toThrowError() + }) +}) + +describe('ensureValueWithExchange', () => { + it('makes sure values are defined', () => { + const myValue: number | undefined = 10 + expect(ensureValueWithExchange(myValue, 0)).toBe(10) + }) + + it('exchanges the value if undefined', () => { + const myObject: { myStringArray: string[] | undefined; myNumber: number } = { + myStringArray: undefined, + myNumber: 10, + } + expect(ensureValueWithExchange(myObject.myStringArray, ['my string'])).toEqual(['my string']) + }) +}) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9e2bf86..70ae68c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -35,3 +35,19 @@ export const filterOrphanTaskDefinitions = ( return orphanTaskDefinitions } + +// returns given value with type certainty, or errors if value is null or undefined +export const ensureValueWithError = (value: T, message?: string): NonNullable => { + if (value === null || value === undefined) { + throw new Error(message) + } + return value +} + +// returns given value with type certainty, or silently exchanges value if null or undefined +export const ensureValueWithExchange = (value: T, exchange: NonNullable): NonNullable => { + if (value === null || value === undefined) { + return exchange + } + return value +}