Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/aws-sdk/client-cod…
Browse files Browse the repository at this point in the history
…edeploy-3.734.0
  • Loading branch information
s3cube authored Jan 31, 2025
2 parents 39a2076 + 8230edf commit bef9563
Show file tree
Hide file tree
Showing 8 changed files with 485 additions and 23 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [2.3.0](https://github.com/aws-actions/amazon-ecs-deploy-task-definition/compare/v2.2.0...v2.3.0) (2025-01-30)


### Features

* Add support for 'VolumeConfigurations' property on both UpdateService and RunTask API call ([#721](https://github.com/aws-actions/amazon-ecs-deploy-task-definition/issues/721)) ([0bad458](https://github.com/aws-actions/amazon-ecs-deploy-task-definition/commit/0bad458c6aa901707e510cd05b797b05da075633))

## [2.2.0](https://github.com/aws-actions/amazon-ecs-deploy-task-definition/compare/v2.1.2...v2.2.0) (2024-12-06)


Expand Down
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,50 @@ You can propagate your custom tags from your existing service using `propagate-t
propagate-tags: SERVICE
```

### EBS Volume Configuration
This action supports configuring Amazon EBS volumes for both services and standalone tasks.

For Services (Update Service):

```yaml
- name: Deploy to Amazon ECS with EBS Volume
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: task-definition.json
service: my-service
cluster: my-cluster
wait-for-service-stability: true
service-managed-ebs-volume-name: "ebs1"
service-managed-ebs-volume: '{"sizeInGiB": 30, "volumeType": "gp3", "encrypted": true, "roleArn":"arn:aws:iam::<account-id>:role/ebs-role"}'
```

Note: Your task definition must include a volume that is configuredAtLaunch:

```json
...
"volumes": [
{
"name": "ebs1",
"configuredAtLaunch": true
}
],
...
```

For Standalone Tasks (RunTask):

```yaml
- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: task-definition.json
cluster: my-cluster
run-task: true
run-task-launch-type: EC2
run-task-managed-ebs-volume-name: "ebs1"
run-task-managed-ebs-volume: '{"filesystemType":"xfs", "roleArn":"arn:aws:iam::<account-id>:role/github-actions-setup-stack-EBSRole-YwVmgS4g7gQE", "encrypted":false, "sizeInGiB":30}'
```

## Credentials and Region

This action relies on the [default behavior of the AWS SDK for Javascript](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html) to determine AWS credentials and region.
Expand Down
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ inputs:
force-new-deployment:
description: 'Whether to force a new deployment of the service. Valid value is "true". Will default to not force a new deployment.'
required: false
service-managed-ebs-volume-name:
description: "The name of the volume, to be manage in the ECS service. This value must match the volume name from the Volume object in the task definition, that was configuredAtLaunch."
required: false
service-managed-ebs-volume:
description: "A JSON object defining the configuration settings for the EBS Service volume that was ConfiguredAtLaunch. You can configure size, volumeType, IOPS, throughput, snapshot and encryption in ServiceManagedEBSVolumeConfiguration. Currently, the only supported volume type is an Amazon EBS volume."
required: false
run-task:
description: 'A boolean indicating whether to run a stand-alone task in a ECS cluster. Task will run before the service is updated if both are provided. Default value is false .'
required: false
Expand Down Expand Up @@ -67,6 +73,12 @@ inputs:
run-task-tags:
description: 'A JSON array of tags.'
required: false
run-task-managed-ebs-volume-name:
description: "The name of the volume. This value must match the volume name from the Volume object in the task definition, that was configuredAtLaunch."
required: false
run-task-managed-ebs-volume:
description: "A JSON object defining the configuration settings for the Amazon EBS task volume that was configuredAtLaunch. These settings are used to create each Amazon EBS volume, with one volume created for each task in the service. The Amazon EBS volumes are visible in your account in the Amazon EC2 console once they are created."
required: false
wait-for-task-stopped:
description: 'Whether to wait for the task to stop when running it outside of a service. Will default to not wait.'
required: false
Expand Down
81 changes: 79 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
const assignPublicIP = core.getInput('run-task-assign-public-IP', { required: false }) || 'DISABLED';
const tags = JSON.parse(core.getInput('run-task-tags', { required: false }) || '[]');
const capacityProviderStrategy = JSON.parse(core.getInput('run-task-capacity-provider-strategy', { required: false }) || '[]');
const runTaskManagedEBSVolumeName = core.getInput('run-task-managed-ebs-volume-name', { required: false }) || '';
const runTaskManagedEBSVolume = core.getInput('run-task-managed-ebs-volume', { required: false }) || '{}';

let awsvpcConfiguration = {}

Expand All @@ -53,6 +55,20 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
if(assignPublicIP != "" && (subnetIds != "" || securityGroupIds != "")){
awsvpcConfiguration["assignPublicIp"] = assignPublicIP
}
let volumeConfigurations = [];
let taskManagedEBSVolumeObject;

if (runTaskManagedEBSVolumeName != '') {
if (runTaskManagedEBSVolume != '{}') {
taskManagedEBSVolumeObject = convertToManagedEbsVolumeObject(runTaskManagedEBSVolume);
volumeConfigurations = [{
name: runTaskManagedEBSVolumeName,
managedEBSVolume: taskManagedEBSVolumeObject
}];
} else {
core.warning(`run-task-managed-ebs-volume-name provided without run-task-managed-ebs-volume value. VolumeConfigurations property will not be included in the RunTask API call`);
}
}

const runTaskResponse = await ecs.runTask({
startedBy: startedBy,
Expand All @@ -65,7 +81,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
launchType: capacityProviderStrategy.length === 0 ? launchType : null,
networkConfiguration: Object.keys(awsvpcConfiguration).length === 0 ? null : { awsvpcConfiguration: awsvpcConfiguration },
enableECSManagedTags: enableECSManagedTags,
tags: tags
tags: tags,
volumeConfigurations: volumeConfigurations
});

core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
Expand All @@ -92,6 +109,47 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
}
}

function convertToManagedEbsVolumeObject(managedEbsVolume) {
managedEbsVolumeObject = {}
const ebsVolumeObject = JSON.parse(managedEbsVolume);
if ('roleArn' in ebsVolumeObject){ // required property
managedEbsVolumeObject.roleArn = ebsVolumeObject.roleArn;
core.debug(`Found RoleArn ${ebsVolumeObject['roleArn']}`);
} else {
throw new Error('managed-ebs-volume must provide "role-arn" to associate with the EBS volume')
}

if ('encrypted' in ebsVolumeObject) {
managedEbsVolumeObject.encrypted = ebsVolumeObject.encrypted;
}
if ('filesystemType' in ebsVolumeObject) {
managedEbsVolumeObject.filesystemType = ebsVolumeObject.filesystemType;
}
if ('iops' in ebsVolumeObject) {
managedEbsVolumeObject.iops = ebsVolumeObject.iops;
}
if ('kmsKeyId' in ebsVolumeObject) {
managedEbsVolumeObject.kmsKeyId = ebsVolumeObject.kmsKeyId;
}
if ('sizeInGiB' in ebsVolumeObject) {
managedEbsVolumeObject.sizeInGiB = ebsVolumeObject.sizeInGiB;
}
if ('snapshotId' in ebsVolumeObject) {
managedEbsVolumeObject.snapshotId = ebsVolumeObject.snapshotId;
}
if ('tagSpecifications' in ebsVolumeObject) {
managedEbsVolumeObject.tagSpecifications = ebsVolumeObject.tagSpecifications;
}
if (('throughput' in ebsVolumeObject) && (('volumeType' in ebsVolumeObject) && (ebsVolumeObject.volumeType == 'gp3'))){
managedEbsVolumeObject.throughput = ebsVolumeObject.throughput;
}
if ('volumeType' in ebsVolumeObject) {
managedEbsVolumeObject.volumeType = ebsVolumeObject.volumeType;
}
core.debug(`Created managedEbsVolumeObject: ${JSON.stringify(managedEbsVolumeObject)}`);
return managedEbsVolumeObject;
}

// Poll tasks until they enter a stopped state
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
if (waitForMinutes > MAX_WAIT_MINUTES) {
Expand Down Expand Up @@ -142,13 +200,32 @@ async function tasksExitCode(ecs, clusterName, taskArns) {
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
core.debug('Updating the service');

const serviceManagedEBSVolumeName = core.getInput('service-managed-ebs-volume-name', { required: false }) || '';
const serviceManagedEBSVolume = core.getInput('service-managed-ebs-volume', { required: false }) || '{}';

let volumeConfigurations = [];
let serviceManagedEbsVolumeObject;

if (serviceManagedEBSVolumeName != '') {
if (serviceManagedEBSVolume != '{}') {
serviceManagedEbsVolumeObject = convertToManagedEbsVolumeObject(serviceManagedEBSVolume);
volumeConfigurations = [{
name: serviceManagedEBSVolumeName,
managedEBSVolume: serviceManagedEbsVolumeObject
}];
} else {
core.warning('service-managed-ebs-volume-name provided without service-managed-ebs-volume value. VolumeConfigurations property will not be included in the UpdateService API call');
}
}

let params = {
cluster: clusterName,
service: service,
taskDefinition: taskDefArn,
forceNewDeployment: forceNewDeployment,
enableECSManagedTags: enableECSManagedTags,
propagateTags: propagateTags
propagateTags: propagateTags,
volumeConfigurations: volumeConfigurations
};

// Add the desiredCount property only if it is defined and a number.
Expand Down
81 changes: 79 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
const assignPublicIP = core.getInput('run-task-assign-public-IP', { required: false }) || 'DISABLED';
const tags = JSON.parse(core.getInput('run-task-tags', { required: false }) || '[]');
const capacityProviderStrategy = JSON.parse(core.getInput('run-task-capacity-provider-strategy', { required: false }) || '[]');
const runTaskManagedEBSVolumeName = core.getInput('run-task-managed-ebs-volume-name', { required: false }) || '';
const runTaskManagedEBSVolume = core.getInput('run-task-managed-ebs-volume', { required: false }) || '{}';

let awsvpcConfiguration = {}

Expand All @@ -47,6 +49,20 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
if(assignPublicIP != "" && (subnetIds != "" || securityGroupIds != "")){
awsvpcConfiguration["assignPublicIp"] = assignPublicIP
}
let volumeConfigurations = [];
let taskManagedEBSVolumeObject;

if (runTaskManagedEBSVolumeName != '') {
if (runTaskManagedEBSVolume != '{}') {
taskManagedEBSVolumeObject = convertToManagedEbsVolumeObject(runTaskManagedEBSVolume);
volumeConfigurations = [{
name: runTaskManagedEBSVolumeName,
managedEBSVolume: taskManagedEBSVolumeObject
}];
} else {
core.warning(`run-task-managed-ebs-volume-name provided without run-task-managed-ebs-volume value. VolumeConfigurations property will not be included in the RunTask API call`);
}
}

const runTaskResponse = await ecs.runTask({
startedBy: startedBy,
Expand All @@ -59,7 +75,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
launchType: capacityProviderStrategy.length === 0 ? launchType : null,
networkConfiguration: Object.keys(awsvpcConfiguration).length === 0 ? null : { awsvpcConfiguration: awsvpcConfiguration },
enableECSManagedTags: enableECSManagedTags,
tags: tags
tags: tags,
volumeConfigurations: volumeConfigurations
});

core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
Expand All @@ -86,6 +103,47 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
}
}

function convertToManagedEbsVolumeObject(managedEbsVolume) {
managedEbsVolumeObject = {}
const ebsVolumeObject = JSON.parse(managedEbsVolume);
if ('roleArn' in ebsVolumeObject){ // required property
managedEbsVolumeObject.roleArn = ebsVolumeObject.roleArn;
core.debug(`Found RoleArn ${ebsVolumeObject['roleArn']}`);
} else {
throw new Error('managed-ebs-volume must provide "role-arn" to associate with the EBS volume')
}

if ('encrypted' in ebsVolumeObject) {
managedEbsVolumeObject.encrypted = ebsVolumeObject.encrypted;
}
if ('filesystemType' in ebsVolumeObject) {
managedEbsVolumeObject.filesystemType = ebsVolumeObject.filesystemType;
}
if ('iops' in ebsVolumeObject) {
managedEbsVolumeObject.iops = ebsVolumeObject.iops;
}
if ('kmsKeyId' in ebsVolumeObject) {
managedEbsVolumeObject.kmsKeyId = ebsVolumeObject.kmsKeyId;
}
if ('sizeInGiB' in ebsVolumeObject) {
managedEbsVolumeObject.sizeInGiB = ebsVolumeObject.sizeInGiB;
}
if ('snapshotId' in ebsVolumeObject) {
managedEbsVolumeObject.snapshotId = ebsVolumeObject.snapshotId;
}
if ('tagSpecifications' in ebsVolumeObject) {
managedEbsVolumeObject.tagSpecifications = ebsVolumeObject.tagSpecifications;
}
if (('throughput' in ebsVolumeObject) && (('volumeType' in ebsVolumeObject) && (ebsVolumeObject.volumeType == 'gp3'))){
managedEbsVolumeObject.throughput = ebsVolumeObject.throughput;
}
if ('volumeType' in ebsVolumeObject) {
managedEbsVolumeObject.volumeType = ebsVolumeObject.volumeType;
}
core.debug(`Created managedEbsVolumeObject: ${JSON.stringify(managedEbsVolumeObject)}`);
return managedEbsVolumeObject;
}

// Poll tasks until they enter a stopped state
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
if (waitForMinutes > MAX_WAIT_MINUTES) {
Expand Down Expand Up @@ -136,13 +194,32 @@ async function tasksExitCode(ecs, clusterName, taskArns) {
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
core.debug('Updating the service');

const serviceManagedEBSVolumeName = core.getInput('service-managed-ebs-volume-name', { required: false }) || '';
const serviceManagedEBSVolume = core.getInput('service-managed-ebs-volume', { required: false }) || '{}';

let volumeConfigurations = [];
let serviceManagedEbsVolumeObject;

if (serviceManagedEBSVolumeName != '') {
if (serviceManagedEBSVolume != '{}') {
serviceManagedEbsVolumeObject = convertToManagedEbsVolumeObject(serviceManagedEBSVolume);
volumeConfigurations = [{
name: serviceManagedEBSVolumeName,
managedEBSVolume: serviceManagedEbsVolumeObject
}];
} else {
core.warning('service-managed-ebs-volume-name provided without service-managed-ebs-volume value. VolumeConfigurations property will not be included in the UpdateService API call');
}
}

let params = {
cluster: clusterName,
service: service,
taskDefinition: taskDefArn,
forceNewDeployment: forceNewDeployment,
enableECSManagedTags: enableECSManagedTags,
propagateTags: propagateTags
propagateTags: propagateTags,
volumeConfigurations: volumeConfigurations
};

// Add the desiredCount property only if it is defined and a number.
Expand Down
Loading

0 comments on commit bef9563

Please sign in to comment.