Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for using existing or Terraform-managed task definitions #518

Closed
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ If you do not wish to store your task definition as a file in your git repositor
aws ecs describe-task-definition --task-definition my-task-definition-family --query taskDefinition > task-definition.json
```

If you don't want to create new revisions of your task definition, you can define the task definition family and revision as inputs for the action. By default, the action will use the latest revision of the task definition family if the revision is not specified.

```yaml
- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: my-task-definition-family
service: my-service
cluster: my-cluster
wait-for-service-stability: true
```

### Task definition container image values

It is highly recommended that each time your GitHub Actions workflow runs and builds a new container image for deployment, a new container image ID is generated. For example, use the commit ID as the new image's tag, instead of updating the 'latest' tag with the new image. Using a unique container image ID for each deployment allows rolling back to a previous container image.
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ branding:
color: 'orange'
inputs:
task-definition:
description: 'The path to the ECS task definition file to register'
description: 'The path to the ECS task definition file to register or the name of the task definition family to use. If the task definition family is given, the action will use the latest ACTIVE revision of the task definition.'
required: true
desired-count:
description: 'The number of instantiations of the task to place and keep running in your service.'
Expand Down
57 changes: 42 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,23 +276,50 @@ async function run() {
const desiredCount = parseInt((core.getInput('desired-count', {required: false})));


// 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))));
let registerResponse;
try {
registerResponse = await ecs.registerTaskDefinition(taskDefContents).promise();
} catch (error) {
core.setFailed("Failed to register task definition in ECS: " + error.message);
core.debug("Task definition contents:");
core.debug(JSON.stringify(taskDefContents, undefined, 4));
throw(error);
taskDefinitionFile :
path.join(process.env.GITHUB_WORKSPACE, taskDefinitionFile);

const isExistingTaskDef = fs.existsSync(taskDefPath);

let taskDefArn;

if (!isExistingTaskDef) {
core.debug(`Searching for task definition ${taskDefinitionFile} in ECS`);
try {
const describeResponse = await ecs.describeTaskDefinition({
taskDefinition: taskDefinitionFile
}).promise();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR needs to be updated to use AWS SDK for JavaScript (v3)

Refer: #529

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +290 to +292
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const describeResponse = await ecs.describeTaskDefinition({
taskDefinition: taskDefinitionFile
}).promise();
const describeResponse = await ecs.describeTaskDefinition({
taskDefinition: taskDefinitionFile
});

const taskDef = describeResponse.taskDefinition;

if (!taskDef) {
throw new Error(`Task definition ${taskDefinitionFile} not found in ECS`);
}

core.debug(`Found task definition ${taskDef.taskDefinitionArn}`);
taskDefArn = taskDef.taskDefinitionArn;
} catch (error) {
core.setFailed("Failed to describe task definition in ECS: " + error.message);
throw(error);
}
}
const taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;

if (isExistingTaskDef) {
core.debug('Registering the task definition');
const fileContents = fs.readFileSync(taskDefPath, 'utf8');
const taskDefContents = maintainValidObjects(removeIgnoredAttributes(cleanNullKeys(yaml.parse(fileContents))));
let registerResponse;
try {
registerResponse = await ecs.registerTaskDefinition(taskDefContents).promise();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
registerResponse = await ecs.registerTaskDefinition(taskDefContents).promise();
registerResponse = await ecs.registerTaskDefinition(taskDefContents);

} catch (error) {
core.setFailed("Failed to register task definition in ECS: " + error.message);
core.debug("Task definition contents:");
core.debug(JSON.stringify(taskDefContents, undefined, 4));
throw(error);
}
taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;
}

core.setOutput('task-definition-arn', taskDefArn);

// Update the service with the new task definition
Expand Down
72 changes: 71 additions & 1 deletion index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ jest.mock('@actions/core');
jest.mock('fs', () => ({
promises: { access: jest.fn() },
readFileSync: jest.fn(),
existsSync: jest.fn().mockReturnValue(true)
}));

const mockEcsRegisterTaskDef = jest.fn();
const mockEcsUpdateService = jest.fn();
const mockEcsDescribeServices = jest.fn();
const mockEcsWaiter = jest.fn();
const mockEcsDescribeTaskDef = jest.fn();
const mockCodeDeployCreateDeployment = jest.fn();
const mockCodeDeployGetDeploymentGroup = jest.fn();
const mockCodeDeployWaiter = jest.fn();
Expand All @@ -27,7 +29,8 @@ jest.mock('aws-sdk', () => {
registerTaskDefinition: mockEcsRegisterTaskDef,
updateService: mockEcsUpdateService,
describeServices: mockEcsDescribeServices,
waitFor: mockEcsWaiter
waitFor: mockEcsWaiter,
describeTaskDefinition: mockEcsDescribeTaskDef
})),
CodeDeploy: jest.fn(() => ({
createDeployment: mockCodeDeployCreateDeployment,
Expand Down Expand Up @@ -86,6 +89,14 @@ describe('Deploy to ECS', () => {
};
});

mockEcsDescribeTaskDef.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ taskDefinition: { taskDefinitionArn: 'task:def:arn' } });
}
};
})

mockEcsUpdateService.mockImplementation(() => {
return {
promise() {
Expand Down Expand Up @@ -151,6 +162,65 @@ describe('Deploy to ECS', () => {
});
});

test("try to get existing task definition ARN when user provides family instead of file", async () => {
fs.existsSync.mockReturnValueOnce(false);

core.getInput = jest
.fn()
.mockReturnValueOnce('task-def-family')
.mockReturnValueOnce('service-456')
.mockReturnValueOnce('cluster-789');

await run();

expect(core.setFailed).toHaveBeenCalledTimes(0);
expect(mockEcsDescribeTaskDef).toHaveBeenNthCalledWith(1, {
taskDefinition: "task-def-family"
});
expect(mockEcsRegisterTaskDef).not.toHaveBeenCalled();
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'task-definition-arn', 'task:def:arn');
expect(mockEcsDescribeServices).toHaveBeenNthCalledWith(1, {
cluster: 'cluster-789',
services: ['service-456']
});
expect(mockEcsUpdateService).toHaveBeenNthCalledWith(1, {
cluster: 'cluster-789',
service: 'service-456',
taskDefinition: 'task:def:arn',
forceNewDeployment: false
});
expect(mockEcsWaiter).toHaveBeenCalledTimes(0);
expect(core.info).toBeCalledWith("Deployment started. Watch this deployment's progress in the Amazon ECS console: https://console.aws.amazon.com/ecs/home?region=fake-region#/clusters/cluster-789/services/service-456/events");
});

test("should be able to throw an error when the task definition family is not found", async () => {
fs.existsSync.mockReturnValueOnce(false);

core.getInput = jest
.fn()
.mockReturnValueOnce('task-def-family')
.mockReturnValueOnce('service-456')
.mockReturnValueOnce('cluster-789');

mockEcsDescribeTaskDef.mockImplementation(() => {
return {
promise() {
return Promise.resolve({ taskDefinition: null });
}
};
});

await run()

expect(mockEcsDescribeTaskDef).toHaveBeenNthCalledWith(1, {
taskDefinition: "task-def-family"
});
expect(core.setFailed).toHaveBeenCalledTimes(2);
expect(mockEcsRegisterTaskDef).not.toHaveBeenCalled();
expect(core.setOutput).not.toHaveBeenNthCalledWith(1, 'task-definition-arn', 'task:def:arn');
expect(core.info).not.toBeCalledWith("Deployment started. Watch this deployment's progress in the Amazon ECS console: https://console.aws.amazon.com/ecs/home?region=fake-region#/clusters/cluster-789/services/service-456/events");
});

test('registers the task definition contents and updates the service', async () => {
await run();
expect(core.setFailed).toHaveBeenCalledTimes(0);
Expand Down
Loading