Skip to content

Commit 41d8ca3

Browse files
committed
feat: adding keep-null-value-keys #780
1 parent 4b08990 commit 41d8ca3

File tree

5 files changed

+376
-174
lines changed

5 files changed

+376
-174
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,25 @@ To tag your tasks:
366366
wait-for-task-stopped: true
367367
```
368368

369+
370+
## Preserving Empty Values with keep-null-value-keys
371+
372+
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.
373+
374+
**Example:**
375+
376+
```yaml
377+
- name: Deploy to Amazon ECS
378+
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
379+
with:
380+
task-definition: task-definition.json
381+
service: my-service
382+
cluster: my-cluster
383+
keep-null-value-keys: tag,command,placementConstraints
384+
```
385+
386+
This is useful for cases where a default value is non-null and you want to override the value and set it to null.
387+
369388
## Troubleshooting
370389

371390
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.

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ inputs:
8888
propagate-tags:
8989
description: "Determines to propagate the tags from the 'SERVICE' to the task."
9090
required: false
91+
keep-null-value-keys:
92+
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.'
93+
required: false
9194
outputs:
9295
task-definition-arn:
9396
description: 'The ARN of the registered ECS task definition.'

dist/index.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,20 @@ function findAppSpecKey(obj, keyName) {
276276
throw new Error(`AppSpec file must include property '${keyName}'`);
277277
}
278278

279-
function isEmptyValue(value) {
279+
280+
// Accepts an optional set of keys to keep even if their value is null or empty string
281+
function isEmptyValue(value, key, keepNullValueKeysSet) {
282+
// If key is in keepNullValueKeysSet, do not treat as empty
283+
if (keepNullValueKeysSet && key && keepNullValueKeysSet.has(key)) {
284+
return false;
285+
}
280286
if (value === null || value === undefined || value === '') {
281287
return true;
282288
}
283289

284290
if (Array.isArray(value)) {
285291
for (var element of value) {
286-
if (!isEmptyValue(element)) {
292+
if (!isEmptyValue(element, undefined, keepNullValueKeysSet)) {
287293
// the array has at least one non-empty element
288294
return false;
289295
}
@@ -306,20 +312,28 @@ function isEmptyValue(value) {
306312
return false;
307313
}
308314

309-
function emptyValueReplacer(_, value) {
310-
if (isEmptyValue(value)) {
311-
return undefined;
312-
}
313-
314-
if (Array.isArray(value)) {
315-
return value.filter(e => !isEmptyValue(e));
316-
}
317315

318-
return value;
316+
// Accepts keepNullValueKeysSet as closure
317+
function makeEmptyValueReplacer(keepNullValueKeysSet) {
318+
return function emptyValueReplacer(key, value) {
319+
if (isEmptyValue(value, key, keepNullValueKeysSet)) {
320+
return undefined;
321+
}
322+
if (Array.isArray(value)) {
323+
return value.filter(e => !isEmptyValue(e, undefined, keepNullValueKeysSet));
324+
}
325+
return value;
326+
};
319327
}
320328

321-
function cleanNullKeys(obj) {
322-
return JSON.parse(JSON.stringify(obj, emptyValueReplacer));
329+
330+
// Accepts an optional array of keys to keep if null/empty
331+
function cleanNullKeys(obj, keepNullValueKeys) {
332+
let keepNullValueKeysSet = null;
333+
if (Array.isArray(keepNullValueKeys) && keepNullValueKeys.length > 0) {
334+
keepNullValueKeysSet = new Set(keepNullValueKeys);
335+
}
336+
return JSON.parse(JSON.stringify(obj, makeEmptyValueReplacer(keepNullValueKeysSet)));
323337
}
324338

325339
function removeIgnoredAttributes(taskDef) {
@@ -492,13 +506,21 @@ async function run() {
492506
propagateTags = propagateTagsInput;
493507
}
494508

509+
510+
// Get keep-null-value-keys input comma-separated
511+
let keepNullValueKeysInput = core.getInput('keep-null-value-keys', { required: false }) || '';
512+
let keepNullValueKeys = [];
513+
if (keepNullValueKeysInput) {
514+
keepNullValueKeys = keepNullValueKeysInput.split(',').map(k => k.trim()).filter(Boolean);
515+
}
516+
495517
// Register the task definition
496518
core.debug('Registering the task definition');
497519
const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
498520
taskDefinitionFile :
499521
path.join(process.env.GITHUB_WORKSPACE, taskDefinitionFile);
500522
const fileContents = fs.readFileSync(taskDefPath, 'utf8');
501-
const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents))));
523+
const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents), keepNullValueKeys)));
502524
let registerResponse;
503525
try {
504526
registerResponse = await ecs.registerTaskDefinition(taskDefContents);

index.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -270,14 +270,20 @@ function findAppSpecKey(obj, keyName) {
270270
throw new Error(`AppSpec file must include property '${keyName}'`);
271271
}
272272

273-
function isEmptyValue(value) {
273+
274+
// Accepts an optional set of keys to keep even if their value is null or empty string
275+
function isEmptyValue(value, key, keepNullValueKeysSet) {
276+
// If key is in keepNullValueKeysSet, do not treat as empty
277+
if (keepNullValueKeysSet && key && keepNullValueKeysSet.has(key)) {
278+
return false;
279+
}
274280
if (value === null || value === undefined || value === '') {
275281
return true;
276282
}
277283

278284
if (Array.isArray(value)) {
279285
for (var element of value) {
280-
if (!isEmptyValue(element)) {
286+
if (!isEmptyValue(element, undefined, keepNullValueKeysSet)) {
281287
// the array has at least one non-empty element
282288
return false;
283289
}
@@ -300,20 +306,28 @@ function isEmptyValue(value) {
300306
return false;
301307
}
302308

303-
function emptyValueReplacer(_, value) {
304-
if (isEmptyValue(value)) {
305-
return undefined;
306-
}
307309

308-
if (Array.isArray(value)) {
309-
return value.filter(e => !isEmptyValue(e));
310-
}
311-
312-
return value;
310+
// Accepts keepNullValueKeysSet as closure
311+
function makeEmptyValueReplacer(keepNullValueKeysSet) {
312+
return function emptyValueReplacer(key, value) {
313+
if (isEmptyValue(value, key, keepNullValueKeysSet)) {
314+
return undefined;
315+
}
316+
if (Array.isArray(value)) {
317+
return value.filter(e => !isEmptyValue(e, undefined, keepNullValueKeysSet));
318+
}
319+
return value;
320+
};
313321
}
314322

315-
function cleanNullKeys(obj) {
316-
return JSON.parse(JSON.stringify(obj, emptyValueReplacer));
323+
324+
// Accepts an optional array of keys to keep if null/empty
325+
function cleanNullKeys(obj, keepNullValueKeys) {
326+
let keepNullValueKeysSet = null;
327+
if (Array.isArray(keepNullValueKeys) && keepNullValueKeys.length > 0) {
328+
keepNullValueKeysSet = new Set(keepNullValueKeys);
329+
}
330+
return JSON.parse(JSON.stringify(obj, makeEmptyValueReplacer(keepNullValueKeysSet)));
317331
}
318332

319333
function removeIgnoredAttributes(taskDef) {
@@ -486,13 +500,21 @@ async function run() {
486500
propagateTags = propagateTagsInput;
487501
}
488502

503+
504+
// Get keep-null-value-keys input comma-separated
505+
let keepNullValueKeysInput = core.getInput('keep-null-value-keys', { required: false }) || '';
506+
let keepNullValueKeys = [];
507+
if (keepNullValueKeysInput) {
508+
keepNullValueKeys = keepNullValueKeysInput.split(',').map(k => k.trim()).filter(Boolean);
509+
}
510+
489511
// Register the task definition
490512
core.debug('Registering the task definition');
491513
const taskDefPath = path.isAbsolute(taskDefinitionFile) ?
492514
taskDefinitionFile :
493515
path.join(process.env.GITHUB_WORKSPACE, taskDefinitionFile);
494516
const fileContents = fs.readFileSync(taskDefPath, 'utf8');
495-
const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents))));
517+
const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents), keepNullValueKeys)));
496518
let registerResponse;
497519
try {
498520
registerResponse = await ecs.registerTaskDefinition(taskDefContents);

0 commit comments

Comments
 (0)