Skip to content

Support ARN being passed in as well as a file #658

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

Closed
wants to merge 2 commits into from
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
45 changes: 31 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Registers an Amazon ECS task definition and deploys it to an ECS service.
<!-- toc -->

- [Usage](#usage)
+ [Task definition file](#task-definition-file)
+ [Task definition container image values](#task-definition-container-image-values)
- [Task definition file](#task-definition-file)
- [Task definition container image values](#task-definition-container-image-values)
- [Credentials and Region](#credentials-and-region)
- [Permissions](#permissions)
- [AWS CodeDeploy Support](#aws-codedeploy-support)
Expand All @@ -31,27 +31,30 @@ Registers an Amazon ECS task definition and deploys it to an ECS service.
```

See [action.yml](action.yml) for the full documentation for this action's inputs and outputs.
In most cases when running a one-off task, subnet ID's, subnet groups, and assign public IP will be required.
Assign public IP will only be applied when a subnet or security group is defined.
In most cases when running a one-off task, subnet ID's, subnet groups, and assign public IP will be required.
Assign public IP will only be applied when a subnet or security group is defined.

### Task definition file

It is highly recommended to treat the task definition "as code" by checking it into your git repository as a JSON file. Changes to any task definition attributes like container images, environment variables, CPU, and memory can be deployed with this GitHub action by editing your task definition file and pushing a new git commit.

An existing task definition can be downloaded to a JSON file with the following command. Account IDs can be removed from the file by removing the `taskDefinitionArn` attribute, and updating the `executionRoleArn` and `taskRoleArn` attribute values to contain role names instead of role ARNs.

```sh
aws ecs describe-task-definition \
--task-definition my-task-definition-family \
--query taskDefinition > task-definition.json
```

Alternatively, you can start a new task definition file from scratch with the following command. In the generated file, fill in your attribute values and remove any attributes not needed for your application.

```sh
aws ecs register-task-definition \
--generate-cli-skeleton > task-definition.json
```

If you do not wish to store your task definition as a file in your git repository, your GitHub Actions workflow can download the existing task definition.

```yaml
- name: Download task definition
run: |
Expand Down Expand Up @@ -104,6 +107,18 @@ The task definition file can be updated prior to deployment with the new contain
wait-for-service-stability: true
```

If you're using CloudFormation tools such as AWS CDK, Serverless Framework, or others to construct your task definition, you can directly pass the ARN of the task definition. For example:

```yaml
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: arn:aws:ecs:<region>:<aws_account_id>:task-definition/<task_definition_name>:<revision_number>
service: my-service
cluster: my-cluster
wait-for-service-stability: true
```

### Tags

To turn on [Amazon ECS-managed tags](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-using-tags.html#managed-tags) `aws:ecs:serviceName` and `aws:ecs:clusterName` for the tasks in the service or the standalone tasks by setting `enable-ecs-managed-tags`:
Expand Down Expand Up @@ -138,15 +153,16 @@ This action relies on the [default behavior of the AWS SDK for Javascript](https
Use [the `aws-actions/configure-aws-credentials` action](https://github.com/aws-actions/configure-aws-credentials) to configure the GitHub Actions environment with environment variables containing AWS credentials and your desired region.

We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) for the AWS credentials used in GitHub Actions workflows, including:
* Do not store credentials in your repository's code. You may use [GitHub Actions secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) to store credentials and redact credentials from GitHub Actions workflow logs.
* [Create an individual IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#create-iam-users) with an access key for use in GitHub Actions workflows, preferably one per repository. Do not use the AWS account root user access key.
* [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) to the credentials used in GitHub Actions workflows. Grant only the permissions required to perform the actions in your GitHub Actions workflows. See the Permissions section below for the permissions required by this action.
* [Rotate the credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials) used in GitHub Actions workflows regularly.
* [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows.
- Do not store credentials in your repository's code. You may use [GitHub Actions secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) to store credentials and redact credentials from GitHub Actions workflow logs.
- [Create an individual IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#create-iam-users) with an access key for use in GitHub Actions workflows, preferably one per repository. Do not use the AWS account root user access key.
- [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) to the credentials used in GitHub Actions workflows. Grant only the permissions required to perform the actions in your GitHub Actions workflows. See the Permissions section below for the permissions required by this action.
- [Rotate the credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials) used in GitHub Actions workflows regularly.
- [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows.

## Permissions

Running a service requires the following minimum set of permissions:

```json
{
"Version":"2012-10-17",
Expand Down Expand Up @@ -184,8 +200,9 @@ 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
{
"Version": "2012-10-17",
Expand Down Expand Up @@ -214,6 +231,7 @@ Running a one-off/stand-alone task requires the following minimum set of permiss
]
}
```

Note: the policy above assumes the account has opted in to the ECS long ARN format.

## AWS CodeDeploy Support
Expand Down Expand Up @@ -298,15 +316,15 @@ 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

To tag your tasks:

* to turn on Amazon ECS-managed tags (`aws:ecs:clusterName`), use `enable-ecs-managed-tags`
* for custom tags, use `run-task-tags`
- to turn on Amazon ECS-managed tags (`aws:ecs:clusterName`), use `enable-ecs-managed-tags`
- for custom tags, use `run-task-tags`

```yaml
- name: Deploy to Amazon ECS
Expand All @@ -333,4 +351,3 @@ This code is made available under the MIT license.
## Security Disclosures

If you would like to report a potential security issue in this project, please do not create a GitHub issue. Instead, please follow the instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:[email protected]).

52 changes: 34 additions & 18 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ async function run() {
});

// Get inputs
const taskDefinitionFile = core.getInput('task-definition', { required: true });
const taskDefinitionContent = core.getInput('task-definition', { required: true });
const service = core.getInput('service', { required: false });
const cluster = core.getInput('cluster', { required: false });
const waitForService = core.getInput('wait-for-service-stability', { required: false });
Expand All @@ -406,23 +406,39 @@ async function run() {
const enableECSManagedTags = enableECSManagedTagsInput.toLowerCase() === 'true';
const propagateTags = core.getInput('propagate-tags', { required: false }) || 'NONE';

// 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);
} 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);
let taskDefArn = null;

// Of taskDefContent starts with arn: then we assume it is a task definition ARN
if (taskDefinitionContent.startsWith("arn:")) {
taskDefArn = taskDefinitionContent;

//
// Else we assume it is a task definition file
} else {
const taskDefinitionFile = taskDefinitionContent;

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))));
try {
const registerResponse = await ecs.registerTaskDefinition(taskDefContents);
taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;
} 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);
}
}

if (!taskDefArn) {
core.setFailed('Task definition ARN is not defined');
throw new Error('Task definition ARN is not defined');
}
const taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;

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

// Run the task outside of the service
Expand Down Expand Up @@ -481,7 +497,7 @@ module.exports = run;
if (require.main === require.cache[eval('__filename')]) {
run();
}

//

/***/ }),

Expand Down
51 changes: 34 additions & 17 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ async function run() {
});

// Get inputs
const taskDefinitionFile = core.getInput('task-definition', { required: true });
const taskDefinitionContent = core.getInput('task-definition', { required: true });
const service = core.getInput('service', { required: false });
const cluster = core.getInput('cluster', { required: false });
const waitForService = core.getInput('wait-for-service-stability', { required: false });
Expand All @@ -400,23 +400,39 @@ async function run() {
const enableECSManagedTags = enableECSManagedTagsInput.toLowerCase() === 'true';
const propagateTags = core.getInput('propagate-tags', { required: false }) || 'NONE';

// 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);
} 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);
let taskDefArn = null;

// Of taskDefContent starts with arn: then we assume it is a task definition ARN
if (taskDefinitionContent.startsWith("arn:")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for this PR!

I am wondering if it would be a good idea to introduce another variable for taskDefinitionArn instead of re-using existing variable? This could ease error handling by helping us return proper error messages back to users.

We have something similar implemented in the amazon-ecs-render-task-definition repo as well:
https://github.com/aws-actions/amazon-ecs-render-task-definition/blob/master/dist/index.js#L32

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I will implement it in the equivalent way to the render task definition action.

taskDefArn = taskDefinitionContent;

//
// Else we assume it is a task definition file
} else {
const taskDefinitionFile = taskDefinitionContent;

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))));
try {
const registerResponse = await ecs.registerTaskDefinition(taskDefContents);
taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;
} 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);
}
}

if (!taskDefArn) {
core.setFailed('Task definition ARN is not defined');
throw new Error('Task definition ARN is not defined');
}
const taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;

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

// Run the task outside of the service
Expand Down Expand Up @@ -475,3 +491,4 @@ module.exports = run;
if (require.main === module) {
run();
}
//
14 changes: 14 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ describe('Deploy to ECS', () => {
waitUntilDeploymentSuccessful.mockImplementation(() => Promise.resolve({}));
});

test('uses task definition ARN if taskDefinitionContent starts with arn:', async () => {
core.getInput = jest
.fn()
.mockReturnValueOnce('arn:aws:ecs:region:account-id:task-definition/task-name:task-revision') // task-definition
.mockReturnValueOnce('service-456') // service
.mockReturnValueOnce('cluster-789'); // cluster

await run();

expect(core.setFailed).toHaveBeenCalledTimes(0);
expect(mockEcsRegisterTaskDef).toHaveBeenCalledTimes(0); // Importante, não deve chamar a função de registro
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'task-definition-arn', 'arn:aws:ecs:region:account-id:task-definition/task-name:task-revision');
});

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