Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ Running a service requires the following minimum set of permissions:
]
}
```

Running a one-off/stand-alone task requires the following minimum set of permissions:
```json
{
Expand Down Expand Up @@ -342,7 +342,7 @@ In the following example, the service would not be updated until the ad-hoc task
wait-for-task-stopped: true
```

Overrides and VPC networking options are available as well. See [action.yml](action.yml) for more details. The `FARGATE`
Overrides and VPC networking options are available as well. See [action.yml](action.yml) for more details. The `FARGATE`
launch type requires `awsvpc` network mode in your task definition and you must specify a network configuration.

### Tags
Expand All @@ -369,21 +369,44 @@ To tag your tasks:

## 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.
By default, this action removes empty string, array, and object values from the ECS task definition before registering it. This behavior aligns with ECS defaults but can be problematic when you explicitly want to override a non-null default value with an empty or null value.

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, empty values for those keys will be retained in the registered task definition.

This is particularly useful in cases where ECS or a previous task definition applies a default value and you want to explicitly unset it.

**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
```
- 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
wait-for-service-stability: true
```

## Retries

To automatically retry a failed task definition deployment, use the max-retries input. This controls how many times the action will attempt to register and deploy the task definition before failing.

- Default: 3
- Minimum: 0 (no retries)

```
- 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
max-retries: 5
wait-for-service-stability: true
```

This is useful for cases where a default value is non-null and you want to override the value and set it to null.
Retries apply to transient failures during task definition registration or service update, such as eventual consistency issues or temporary AWS API errors.

## Troubleshooting

Expand Down
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ inputs:
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.'
max-retries:
description: 'The maximum number of retry attempts for AWS API calls. Defaults to 3.'
required: false
outputs:
task-definition-arn:
Expand Down
19 changes: 12 additions & 7 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,17 +474,11 @@ async function createCodeDeployDeployment(codedeploy, clusterName, service, task

async function run() {
try {
const ecs = new ECS({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions'
});
const codedeploy = new CodeDeploy({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions'
});

// Get inputs
const taskDefinitionFile = core.getInput('task-definition', { required: true });
const service = core.getInput('service', { required: false });
const cluster = core.getInput('cluster', { required: false });
const maxRetries = parseInt(core.getInput('max-retries', { required: false })) || 3;
const waitForService = core.getInput('wait-for-service-stability', { required: false });
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;

Expand Down Expand Up @@ -513,6 +507,17 @@ async function run() {
if (keepNullValueKeysInput) {
keepNullValueKeys = keepNullValueKeysInput.split(',').map(k => k.trim()).filter(Boolean);
}
const ecs = new ECS({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions',
maxAttempts: maxRetries,
retryMode: 'standard'
});

const codedeploy = new CodeDeploy({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions',
maxAttempts: maxRetries,
retryMode: 'standard'
});

// Register the task definition
core.debug('Registering the task definition');
Expand Down
19 changes: 12 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,17 +468,11 @@ async function createCodeDeployDeployment(codedeploy, clusterName, service, task

async function run() {
try {
const ecs = new ECS({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions'
});
const codedeploy = new CodeDeploy({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions'
});

// Get inputs
const taskDefinitionFile = core.getInput('task-definition', { required: true });
const service = core.getInput('service', { required: false });
const cluster = core.getInput('cluster', { required: false });
const maxRetries = parseInt(core.getInput('max-retries', { required: false })) || 3;
const waitForService = core.getInput('wait-for-service-stability', { required: false });
let waitForMinutes = parseInt(core.getInput('wait-for-minutes', { required: false })) || 30;

Expand Down Expand Up @@ -507,6 +501,17 @@ async function run() {
if (keepNullValueKeysInput) {
keepNullValueKeys = keepNullValueKeysInput.split(',').map(k => k.trim()).filter(Boolean);
}
const ecs = new ECS({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions',
maxAttempts: maxRetries,
retryMode: 'standard'
});

const codedeploy = new CodeDeploy({
customUserAgent: 'amazon-ecs-deploy-task-definition-for-github-actions',
maxAttempts: maxRetries,
retryMode: 'standard'
});

// Register the task definition
core.debug('Registering the task definition');
Expand Down
34 changes: 23 additions & 11 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ describe('Deploy to ECS', () => {
core.getInput = jest
.fn()
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789'); // cluster
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3'); // max-retries

process.env = Object.assign(process.env, { GITHUB_WORKSPACE: __dirname });

Expand Down Expand Up @@ -95,7 +96,7 @@ describe('Deploy to ECS', () => {
mockEcsDescribeServices.mockImplementation(
() => Promise.resolve({
failures: [],
services: [{
services: [{
status: 'ACTIVE'
}]
})
Expand Down Expand Up @@ -628,6 +629,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('TRUE'); // wait-for-service-stability

mockEcsDescribeServices.mockImplementation(
Expand Down Expand Up @@ -705,6 +707,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('TRUE') // wait-for-service-stability
.mockReturnValueOnce('60'); // wait-for-minutes

Expand Down Expand Up @@ -781,6 +784,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('TRUE') // wait-for-service-stability
.mockReturnValueOnce('1000'); // wait-for-minutes

Expand Down Expand Up @@ -1076,6 +1080,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('TRUE') // wait-for-service-stability
.mockReturnValueOnce(''); // desired count

Expand Down Expand Up @@ -1117,6 +1122,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('TRUE') // wait-for-service-stability
.mockReturnValueOnce('60') // wait-for-minutes
.mockReturnValueOnce(''); // desired count
Expand Down Expand Up @@ -1159,6 +1165,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('TRUE') // wait-for-service-stability
.mockReturnValueOnce('1000') // wait-for-minutes
.mockReturnValueOnce('abc'); // desired count is NaN
Expand Down Expand Up @@ -1201,6 +1208,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('false') // wait-for-service-stability
.mockReturnValueOnce('') // wait-for-minutes
.mockReturnValueOnce('true') // force-new-deployment
Expand Down Expand Up @@ -1403,7 +1411,7 @@ describe('Deploy to ECS', () => {
// Empty string for all other inputs
return '';
});

await run();
expect(core.setFailed).toHaveBeenCalledTimes(0);

Expand Down Expand Up @@ -1496,7 +1504,7 @@ describe('Deploy to ECS', () => {
volumeConfigurations: []
});
});

test('run task with setting true to enableECSManagedTags', async () => {
core.getInput = jest
.fn(input => {
Expand Down Expand Up @@ -1527,7 +1535,7 @@ describe('Deploy to ECS', () => {
volumeConfigurations: []
});
});

test('run task with setting false to enableECSManagedTags', async () => {
core.getInput = jest
.fn(input => {
Expand Down Expand Up @@ -1565,6 +1573,7 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('') // service
.mockReturnValueOnce('somecluster') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('') // wait-for-service-stability
.mockReturnValueOnce('') // wait-for-minutes
.mockReturnValueOnce('') // force-new-deployment
Expand Down Expand Up @@ -1617,7 +1626,7 @@ describe('Deploy to ECS', () => {
if (input === 'run-task-managed-ebs-volume') return '{}';
return '';
});

mockRunTask.mockImplementation(
() => Promise.resolve({
failures: [{
Expand Down Expand Up @@ -1732,12 +1741,13 @@ describe('Deploy to ECS', () => {
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('false') // wait-for-service-stability
.mockReturnValueOnce('') // wait-for-minutes
.mockReturnValueOnce('') // force-new-deployment
.mockReturnValueOnce('') // desired-count
.mockReturnValueOnce('') // enable-ecs-managed-tags
.mockReturnValueOnce('SERVICE'); // propagate-tags
.mockReturnValueOnce('SERVICE'); // propagate-tags

await run();
expect(core.setFailed).toHaveBeenCalledTimes(0);
Expand All @@ -1758,13 +1768,14 @@ describe('Deploy to ECS', () => {
volumeConfigurations: []
});
});

test('update service with setting true to enableECSManagedTags', async () => {
core.getInput = jest
.fn()
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('false') // wait-for-service-stability
.mockReturnValueOnce('') // wait-for-minutes
.mockReturnValueOnce('') // force-new-deployment
Expand All @@ -1791,13 +1802,14 @@ describe('Deploy to ECS', () => {
volumeConfigurations: []
});
});

test('update service with setting false to enableECSManagedTags', async () => {
core.getInput = jest
.fn()
.mockReturnValueOnce('task-definition.json') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789') // cluster
.mockReturnValueOnce('3') // max-retries
.mockReturnValueOnce('false') // wait-for-service-stability
.mockReturnValueOnce('') // wait-for-minutes
.mockReturnValueOnce('') // force-new-deployment
Expand Down Expand Up @@ -2051,4 +2063,4 @@ describe('Deploy to ECS', () => {
}]
});
});
});
});
Loading