From 41d8ca3a3fa25662d8a5f6010364f63198bdae90 Mon Sep 17 00:00:00 2001 From: "Geoffrey S. Zub" Date: Thu, 31 Jul 2025 07:59:33 -0400 Subject: [PATCH] feat: adding keep-null-value-keys #780 --- README.md | 19 +++ action.yml | 3 + dist/index.js | 50 ++++-- index.js | 50 ++++-- index.test.js | 428 +++++++++++++++++++++++++++++++++----------------- 5 files changed, 376 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 378af2676..649ae5956 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,25 @@ To tag your tasks: wait-for-task-stopped: true ``` + +## Preserving Empty Values with keep-null-value-keys + +By default, this action removes empty string, array, and object values from the ECS task definition before registering it. If you want to preserve empty values for specific keys, use the `keep-null-value-keys` input. This is a comma-separated list of key names. When specified, any empty value for those keys will be kept in the registered task definition. + +**Example:** + +```yaml + - name: Deploy to Amazon ECS + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 + with: + task-definition: task-definition.json + service: my-service + cluster: my-cluster + keep-null-value-keys: tag,command,placementConstraints +``` + +This is useful for cases where a default value is non-null and you want to override the value and set it to null. + ## Troubleshooting This action emits debug logs to help troubleshoot deployment failures. To see the debug logs, create a secret named `ACTIONS_STEP_DEBUG` with value `true` in your repository. diff --git a/action.yml b/action.yml index e9cdfc228..296630b8e 100644 --- a/action.yml +++ b/action.yml @@ -88,6 +88,9 @@ inputs: propagate-tags: description: "Determines to propagate the tags from the 'SERVICE' to the task." required: false + keep-null-value-keys: + description: 'A comma-separated list of keys whose empty values (empty string, array, or object) should be preserved in the task definition. By default, empty values are removed.' + required: false outputs: task-definition-arn: description: 'The ARN of the registered ECS task definition.' diff --git a/dist/index.js b/dist/index.js index 5b0db2b18..e9e28738b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -276,14 +276,20 @@ function findAppSpecKey(obj, keyName) { throw new Error(`AppSpec file must include property '${keyName}'`); } -function isEmptyValue(value) { + +// Accepts an optional set of keys to keep even if their value is null or empty string +function isEmptyValue(value, key, keepNullValueKeysSet) { + // If key is in keepNullValueKeysSet, do not treat as empty + if (keepNullValueKeysSet && key && keepNullValueKeysSet.has(key)) { + return false; + } if (value === null || value === undefined || value === '') { return true; } if (Array.isArray(value)) { for (var element of value) { - if (!isEmptyValue(element)) { + if (!isEmptyValue(element, undefined, keepNullValueKeysSet)) { // the array has at least one non-empty element return false; } @@ -306,20 +312,28 @@ function isEmptyValue(value) { return false; } -function emptyValueReplacer(_, value) { - if (isEmptyValue(value)) { - return undefined; - } - - if (Array.isArray(value)) { - return value.filter(e => !isEmptyValue(e)); - } - return value; +// Accepts keepNullValueKeysSet as closure +function makeEmptyValueReplacer(keepNullValueKeysSet) { + return function emptyValueReplacer(key, value) { + if (isEmptyValue(value, key, keepNullValueKeysSet)) { + return undefined; + } + if (Array.isArray(value)) { + return value.filter(e => !isEmptyValue(e, undefined, keepNullValueKeysSet)); + } + return value; + }; } -function cleanNullKeys(obj) { - return JSON.parse(JSON.stringify(obj, emptyValueReplacer)); + +// Accepts an optional array of keys to keep if null/empty +function cleanNullKeys(obj, keepNullValueKeys) { + let keepNullValueKeysSet = null; + if (Array.isArray(keepNullValueKeys) && keepNullValueKeys.length > 0) { + keepNullValueKeysSet = new Set(keepNullValueKeys); + } + return JSON.parse(JSON.stringify(obj, makeEmptyValueReplacer(keepNullValueKeysSet))); } function removeIgnoredAttributes(taskDef) { @@ -492,13 +506,21 @@ async function run() { propagateTags = propagateTagsInput; } + + // Get keep-null-value-keys input comma-separated + let keepNullValueKeysInput = core.getInput('keep-null-value-keys', { required: false }) || ''; + let keepNullValueKeys = []; + if (keepNullValueKeysInput) { + keepNullValueKeys = keepNullValueKeysInput.split(',').map(k => k.trim()).filter(Boolean); + } + // Register the task definition core.debug('Registering the task definition'); const taskDefPath = path.isAbsolute(taskDefinitionFile) ? taskDefinitionFile : path.join(process.env.GITHUB_WORKSPACE, taskDefinitionFile); const fileContents = fs.readFileSync(taskDefPath, 'utf8'); - const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents)))); + const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents), keepNullValueKeys))); let registerResponse; try { registerResponse = await ecs.registerTaskDefinition(taskDefContents); diff --git a/index.js b/index.js index f5d29bf0e..810bba50f 100644 --- a/index.js +++ b/index.js @@ -270,14 +270,20 @@ function findAppSpecKey(obj, keyName) { throw new Error(`AppSpec file must include property '${keyName}'`); } -function isEmptyValue(value) { + +// Accepts an optional set of keys to keep even if their value is null or empty string +function isEmptyValue(value, key, keepNullValueKeysSet) { + // If key is in keepNullValueKeysSet, do not treat as empty + if (keepNullValueKeysSet && key && keepNullValueKeysSet.has(key)) { + return false; + } if (value === null || value === undefined || value === '') { return true; } if (Array.isArray(value)) { for (var element of value) { - if (!isEmptyValue(element)) { + if (!isEmptyValue(element, undefined, keepNullValueKeysSet)) { // the array has at least one non-empty element return false; } @@ -300,20 +306,28 @@ function isEmptyValue(value) { return false; } -function emptyValueReplacer(_, value) { - if (isEmptyValue(value)) { - return undefined; - } - if (Array.isArray(value)) { - return value.filter(e => !isEmptyValue(e)); - } - - return value; +// Accepts keepNullValueKeysSet as closure +function makeEmptyValueReplacer(keepNullValueKeysSet) { + return function emptyValueReplacer(key, value) { + if (isEmptyValue(value, key, keepNullValueKeysSet)) { + return undefined; + } + if (Array.isArray(value)) { + return value.filter(e => !isEmptyValue(e, undefined, keepNullValueKeysSet)); + } + return value; + }; } -function cleanNullKeys(obj) { - return JSON.parse(JSON.stringify(obj, emptyValueReplacer)); + +// Accepts an optional array of keys to keep if null/empty +function cleanNullKeys(obj, keepNullValueKeys) { + let keepNullValueKeysSet = null; + if (Array.isArray(keepNullValueKeys) && keepNullValueKeys.length > 0) { + keepNullValueKeysSet = new Set(keepNullValueKeys); + } + return JSON.parse(JSON.stringify(obj, makeEmptyValueReplacer(keepNullValueKeysSet))); } function removeIgnoredAttributes(taskDef) { @@ -486,13 +500,21 @@ async function run() { propagateTags = propagateTagsInput; } + + // Get keep-null-value-keys input comma-separated + let keepNullValueKeysInput = core.getInput('keep-null-value-keys', { required: false }) || ''; + let keepNullValueKeys = []; + if (keepNullValueKeysInput) { + keepNullValueKeys = keepNullValueKeysInput.split(',').map(k => k.trim()).filter(Boolean); + } + // Register the task definition core.debug('Registering the task definition'); const taskDefPath = path.isAbsolute(taskDefinitionFile) ? taskDefinitionFile : path.join(process.env.GITHUB_WORKSPACE, taskDefinitionFile); const fileContents = fs.readFileSync(taskDefPath, 'utf8'); - const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents)))); + const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents), keepNullValueKeys))); let registerResponse; try { registerResponse = await ecs.registerTaskDefinition(taskDefContents); diff --git a/index.test.js b/index.test.js index 459a722b0..f634b58b8 100644 --- a/index.test.js +++ b/index.test.js @@ -333,6 +333,147 @@ describe('Deploy to ECS', () => { }); }); + test('preserves empty string values for specified keys when using keep-null-value-keys', async () => { + core.getInput = jest.fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'keep-null-value-keys') return '["environment", "tag"]'; + return ''; + }); + + fs.readFileSync.mockImplementation((pathInput, encoding) => { + if (encoding != 'utf8') { + throw new Error(`Wrong encoding ${encoding}`); + } + + // Create a task definition with properties that we want to preserve + return JSON.stringify({ + family: 'task-def-family', + containerDefinitions: [{ + name: 'sample-container', + environment: [{ + name: 'TEST_ENV', + value: '' // Empty string value that should be preserved + }], + logConfiguration: { + logDriver: 'splunk', + options: { + 'tag': '' // Empty tag value that should be preserved + } + }, + essential: true + }] + }); + }); + + // Modify the mockEcsRegisterTaskDef to check for preserved empty values + const originalMockImplementation = mockEcsRegisterTaskDef.getMockImplementation(); + mockEcsRegisterTaskDef.mockImplementation(taskDef => { + // Check that our empty environment variable value is preserved + expect(taskDef.containerDefinitions[0].environment[0].value).toBe(''); + + // Check that our empty tag value is preserved in options + expect(taskDef.containerDefinitions[0].logConfiguration.options.tag).toBe(''); + + // Return the original mock implementation response + return Promise.resolve({ taskDefinition: { taskDefinitionArn: 'task:def:arn' } }); + }); + + await run(); + + // Reset the mock implementation + mockEcsRegisterTaskDef.mockImplementation(originalMockImplementation); + }); + + test('preserves empty array values for specified keys when using keep-null-value-keys', async () => { + core.getInput = jest.fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'keep-null-value-keys') return '["command", "environment"]'; + return ''; + }); + + fs.readFileSync.mockImplementation((pathInput, encoding) => { + if (encoding != 'utf8') { + throw new Error(`Wrong encoding ${encoding}`); + } + + // Create a task definition with empty array properties that we want to preserve + return JSON.stringify({ + family: 'task-def-family', + containerDefinitions: [{ + name: 'sample-container', + command: [], // Empty array that should be preserved + environment: [], // Empty array that should be preserved + essential: true + }] + }); + }); + + // Modify the mockEcsRegisterTaskDef to check for preserved empty arrays + const originalMockImplementation = mockEcsRegisterTaskDef.getMockImplementation(); + mockEcsRegisterTaskDef.mockImplementation(taskDef => { + // Check that our empty arrays are preserved + expect(Array.isArray(taskDef.containerDefinitions[0].command)).toBe(true); + expect(taskDef.containerDefinitions[0].command.length).toBe(0); + + expect(Array.isArray(taskDef.containerDefinitions[0].environment)).toBe(true); + expect(taskDef.containerDefinitions[0].environment.length).toBe(0); + + // Return the original mock implementation response + return Promise.resolve({ taskDefinition: { taskDefinitionArn: 'task:def:arn' } }); + }); + + await run(); + + // Reset the mock implementation + mockEcsRegisterTaskDef.mockImplementation(originalMockImplementation); + }); + + test('preserves empty object values for specified keys when using keep-null-value-keys', async () => { + core.getInput = jest.fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'keep-null-value-keys') return '["placementConstraints", "volumes"]'; + return ''; + }); + + fs.readFileSync.mockImplementation((pathInput, encoding) => { + if (encoding != 'utf8') { + throw new Error(`Wrong encoding ${encoding}`); + } + + // Create a task definition with empty object properties that we want to preserve + return JSON.stringify({ + family: 'task-def-family', + containerDefinitions: [{ + name: 'sample-container', + essential: true + }], + placementConstraints: {}, // Empty object that should be preserved + volumes: {} // Empty object that should be preserved + }); + }); + + // Modify the mockEcsRegisterTaskDef to check for preserved empty objects + const originalMockImplementation = mockEcsRegisterTaskDef.getMockImplementation(); + mockEcsRegisterTaskDef.mockImplementation(taskDef => { + // Check that our empty objects are preserved + expect(typeof taskDef.placementConstraints).toBe('object'); + expect(Object.keys(taskDef.placementConstraints).length).toBe(0); + + expect(typeof taskDef.volumes).toBe('object'); + expect(Object.keys(taskDef.volumes).length).toBe(0); + + // Return the original mock implementation response + return Promise.resolve({ taskDefinition: { taskDefinitionArn: 'task:def:arn' } }); + }); + + await run(); + + // Reset the mock implementation + mockEcsRegisterTaskDef.mockImplementation(originalMockImplementation); + }); + + + test('maintains empty keys in proxyConfiguration.properties for APPMESH', async () => { fs.readFileSync.mockImplementation((pathInput, encoding) => { if (encoding != 'utf8') { @@ -708,20 +849,16 @@ describe('Deploy to ECS', () => { test('does not wait for a CodeDeploy deployment, parses JSON appspec file', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('service-456') // service - .mockReturnValueOnce('cluster-789') // cluster - .mockReturnValueOnce('false') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // run-task - .mockReturnValueOnce('') // desired count - .mockReturnValueOnce('') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-task - .mockReturnValueOnce('/hello/appspec.json') // codedeploy-appspec - .mockReturnValueOnce('MyApplication') // codedeploy-application - .mockReturnValueOnce('MyDeploymentGroup'); // codedeploy-deployment-group + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'service') return 'service-456'; + if (input === 'cluster') return 'cluster-789'; + if (input === 'wait-for-service-stability') return 'false'; + if (input === 'codedeploy-appspec') return '/hello/appspec.json'; + if (input === 'codedeploy-application') return 'MyApplication'; + if (input === 'codedeploy-deployment-group') return 'MyDeploymentGroup'; + return ''; + }); fs.readFileSync.mockReturnValue(` { @@ -1133,17 +1270,17 @@ describe('Deploy to ECS', () => { test('run task', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('true'); // run-task + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'run-task') return 'true'; + if (input === 'run-task-launch-type') return 'FARGATE'; + if (input === 'run-task-container-overrides') return '[]'; + if (input === 'run-task-tags') return '[]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(core.setFailed).toHaveBeenCalledTimes(0); @@ -1169,25 +1306,24 @@ describe('Deploy to ECS', () => { test('run task with options', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('false') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true') // run-task - .mockReturnValueOnce('false') // wait-for-task-stopped - .mockReturnValueOnce('someJoe') // run-task-started-by - .mockReturnValueOnce('EC2') // run-task-launch-type - .mockReturnValueOnce('a,b') // run-task-subnet-ids - .mockReturnValueOnce('c,d') // run-task-security-group-ids - .mockReturnValueOnce(JSON.stringify([{ name: 'someapp', command: 'somecmd' }])) // run-task-container-overrides - .mockReturnValueOnce('') // run-task-assign-public-IP - .mockReturnValueOnce('[{"key": "project", "value": "myproject"}]'); // run-task-tags + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'cluster') return 'somecluster'; + if (input === 'enable-ecs-managed-tags') return 'false'; + if (input === 'run-task') return 'true'; + if (input === 'wait-for-task-stopped') return 'false'; + if (input === 'run-task-started-by') return 'someJoe'; + if (input === 'run-task-launch-type') return 'EC2'; + if (input === 'run-task-subnets') return 'a,b'; + if (input === 'run-task-security-groups') return 'c,d'; + if (input === 'run-task-container-overrides') return JSON.stringify([{ name: 'someapp', command: 'somecmd' }]); + if (input === 'run-task-assign-public-IP') return 'DISABLED'; + if (input === 'run-task-tags') return '[{"key": "project", "value": "myproject"}]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(core.setFailed).toHaveBeenCalledTimes(0); @@ -1211,26 +1347,24 @@ describe('Deploy to ECS', () => { test('run task with capacity provider strategy', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('false') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true') // run-task - .mockReturnValueOnce('false') // wait-for-task-stopped - .mockReturnValueOnce('someJoe') // run-task-started-by - .mockReturnValueOnce('') // run-task-launch-type - .mockReturnValueOnce('a,b') // run-task-subnet-ids - .mockReturnValueOnce('c,d') // run-task-security-group-ids - .mockReturnValueOnce(JSON.stringify([{ name: 'someapp', command: 'somecmd' }])) // run-task-container-overrides - .mockReturnValueOnce('') // run-task-assign-public-IP - .mockReturnValueOnce('[{"key": "project", "value": "myproject"}]') // run-task-tags - .mockReturnValueOnce('[{"capacityProvider":"FARGATE_SPOT","weight":1}]'); // run-task-capacity-provider-strategy + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'cluster') return 'somecluster'; + if (input === 'enable-ecs-managed-tags') return 'false'; + if (input === 'run-task') return 'true'; + if (input === 'wait-for-task-stopped') return 'false'; + if (input === 'run-task-started-by') return 'someJoe'; + if (input === 'run-task-launch-type') return ''; + if (input === 'run-task-subnets') return 'a,b'; + if (input === 'run-task-security-groups') return 'c,d'; + if (input === 'run-task-container-overrides') return JSON.stringify([{ name: 'someapp', command: 'somecmd' }]); + if (input === 'run-task-assign-public-IP') return 'DISABLED'; + if (input === 'run-task-tags') return '[{"key": "project", "value": "myproject"}]'; + if (input === 'run-task-capacity-provider-strategy') return '[{"capacityProvider":"FARGATE_SPOT","weight":1}]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(core.setFailed).toHaveBeenCalledTimes(0); @@ -1253,24 +1387,22 @@ describe('Deploy to ECS', () => { }); test('run task and service ', async () => { - core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('service-456') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('true') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true') // run-task - .mockReturnValueOnce('false') // wait-for-task-stopped - .mockReturnValueOnce('someJoe') // run-task-started-by - .mockReturnValueOnce('EC2') // run-task-launch-type - .mockReturnValueOnce('a,b') // run-task-subnet-ids - .mockReturnValueOnce('c,d') // run-task-security-group-ids - .mockReturnValueOnce(JSON.stringify([{ name: 'someapp', command: 'somecmd' }])); // run-task-container-overrides + core.getInput = jest.fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'service') return 'service-456'; + if (input === 'cluster') return 'somecluster'; + if (input === 'wait-for-service-stability') return 'true'; + if (input === 'run-task') return 'true'; + if (input === 'wait-for-task-stopped') return 'false'; + if (input === 'run-task-started-by') return 'someJoe'; + if (input === 'run-task-launch-type') return 'EC2'; + if (input === 'run-task-subnets') return 'a,b'; + if (input === 'run-task-security-groups') return 'c,d'; + if (input === 'run-task-assign-public-IP') return 'DISABLED'; + if (input === 'run-task-container-overrides') return JSON.stringify([{ name: 'someapp', command: 'somecmd' }]); + // Empty string for all other inputs + return ''; + }); await run(); expect(core.setFailed).toHaveBeenCalledTimes(0); @@ -1307,18 +1439,19 @@ describe('Deploy to ECS', () => { test('run task and wait for it to stop', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true') // run-task - .mockReturnValueOnce('true'); // wait-for-task-stopped + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'cluster') return 'somecluster'; + if (input === 'run-task') return 'true'; + if (input === 'wait-for-task-stopped') return 'true'; + if (input === 'run-task-launch-type') return 'FARGATE'; + if (input === 'run-task-container-overrides') return '[]'; + if (input === 'run-task-tags') return '[]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(core.setFailed).toHaveBeenCalledTimes(0); @@ -1332,24 +1465,22 @@ describe('Deploy to ECS', () => { test('run task in bridge network mode', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('service-456') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('true') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // enable-ecs-managed-tags - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true') // run-task - .mockReturnValueOnce('true') // wait-for-task-stopped - .mockReturnValueOnce('someJoe') // run-task-started-by - .mockReturnValueOnce('EC2') // run-task-launch-type - .mockReturnValueOnce('') // run-task-subnet-ids - .mockReturnValueOnce('') // run-task-security-group-ids - .mockReturnValueOnce('') // run-task-container-overrides - .mockReturnValueOnce('') // run-task-assign-public-IP + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'service') return 'service-456'; + if (input === 'cluster') return 'somecluster'; + if (input === 'wait-for-service-stability') return 'true'; + if (input === 'run-task') return 'true'; + if (input === 'wait-for-task-stopped') return 'true'; + if (input === 'run-task-started-by') return 'someJoe'; + if (input === 'run-task-launch-type') return 'EC2'; + if (input === 'run-task-container-overrides') return '[]'; + if (input === 'run-task-tags') return '[]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(mockRunTask).toHaveBeenCalledWith({ @@ -1368,17 +1499,19 @@ describe('Deploy to ECS', () => { test('run task with setting true to enableECSManagedTags', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('true') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true'); // run-task + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'cluster') return 'somecluster'; + if (input === 'enable-ecs-managed-tags') return 'true'; + if (input === 'run-task') return 'true'; + if (input === 'run-task-launch-type') return 'FARGATE'; + if (input === 'run-task-container-overrides') return '[]'; + if (input === 'run-task-tags') return '[]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(mockRunTask).toHaveBeenCalledWith({ @@ -1397,17 +1530,19 @@ describe('Deploy to ECS', () => { test('run task with setting false to enableECSManagedTags', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('false') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true'); // run-task + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'cluster') return 'somecluster'; + if (input === 'enable-ecs-managed-tags') return 'false'; + if (input === 'run-task') return 'true'; + if (input === 'run-task-launch-type') return 'FARGATE'; + if (input === 'run-task-container-overrides') return '[]'; + if (input === 'run-task-tags') return '[]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); await run(); expect(mockRunTask).toHaveBeenCalledWith({ @@ -1469,18 +1604,19 @@ describe('Deploy to ECS', () => { test('error is caught if run task fails with (wait-for-task-stopped: false) and with service', async () => { core.getInput = jest - .fn() - .mockReturnValueOnce('task-definition.json') // task-definition - .mockReturnValueOnce('') // service - .mockReturnValueOnce('somecluster') // cluster - .mockReturnValueOnce('') // wait-for-service-stability - .mockReturnValueOnce('') // wait-for-minutes - .mockReturnValueOnce('') // force-new-deployment - .mockReturnValueOnce('') // desired-count - .mockReturnValueOnce('') // enable-ecs-managed-tags - .mockReturnValueOnce('') // propagate-tags - .mockReturnValueOnce('true') // run-task - .mockReturnValueOnce('false'); // wait-for-task-stopped + .fn(input => { + if (input === 'task-definition') return 'task-definition.json'; + if (input === 'cluster') return 'somecluster'; + if (input === 'run-task') return 'true'; + if (input === 'wait-for-task-stopped') return 'false'; + if (input === 'run-task-launch-type') return 'FARGATE'; + if (input === 'run-task-container-overrides') return '[]'; + if (input === 'run-task-tags') return '[]'; + if (input === 'run-task-capacity-provider-strategy') return '[]'; + if (input === 'run-task-managed-ebs-volume-name') return ''; + if (input === 'run-task-managed-ebs-volume') return '{}'; + return ''; + }); mockRunTask.mockImplementation( () => Promise.resolve({