Skip to content

Commit 0bad458

Browse files
authored
feat: Add support for 'VolumeConfigurations' property on both UpdateService and RunTask API call (#721)
* Add Support for VolumeConfigurations for both UpdateService and RunTask api call * Add service-managed-ebs-volume and run-task-managed-ebs-volume inputs to the action.yml file * Update convertToManagedEbsVolumeObject syntax * Include additional logs to debug error in Update Service * changing convertToManagedEbsVolumeObject method to a snchronous function * Correct variable name * Removed debug log lines * Correct variable name in Run Task * Set VolumeConfigurations to be null by default * Add a null check for the VolumeConfigurations property * Update VolumeConfigurations to be a empty list instead of not passing it during subsequent Update Service Calls * Update parameter descriptions for managed-ebs-volume * Update README.md to contain information on how to configure Amazon EBS Volumes * Add unit tests * Update Readme * Updating warning message to clarify that VolumeConfigurations property will be ignored if service or task managed-ebs-volume-name property is not provided
1 parent ce8c673 commit 0bad458

File tree

5 files changed

+476
-21
lines changed

5 files changed

+476
-21
lines changed

README.md

+44
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,50 @@ You can propagate your custom tags from your existing service using `propagate-t
132132
propagate-tags: SERVICE
133133
```
134134

135+
### EBS Volume Configuration
136+
This action supports configuring Amazon EBS volumes for both services and standalone tasks.
137+
138+
For Services (Update Service):
139+
140+
```yaml
141+
- name: Deploy to Amazon ECS with EBS Volume
142+
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
143+
with:
144+
task-definition: task-definition.json
145+
service: my-service
146+
cluster: my-cluster
147+
wait-for-service-stability: true
148+
service-managed-ebs-volume-name: "ebs1"
149+
service-managed-ebs-volume: '{"sizeInGiB": 30, "volumeType": "gp3", "encrypted": true, "roleArn":"arn:aws:iam::<account-id>:role/ebs-role"}'
150+
```
151+
152+
Note: Your task definition must include a volume that is configuredAtLaunch:
153+
154+
```json
155+
...
156+
"volumes": [
157+
{
158+
"name": "ebs1",
159+
"configuredAtLaunch": true
160+
}
161+
],
162+
...
163+
```
164+
165+
For Standalone Tasks (RunTask):
166+
167+
```yaml
168+
- name: Deploy to Amazon ECS
169+
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
170+
with:
171+
task-definition: task-definition.json
172+
cluster: my-cluster
173+
run-task: true
174+
run-task-launch-type: EC2
175+
run-task-managed-ebs-volume-name: "ebs1"
176+
run-task-managed-ebs-volume: '{"filesystemType":"xfs", "roleArn":"arn:aws:iam::<account-id>:role/github-actions-setup-stack-EBSRole-YwVmgS4g7gQE", "encrypted":false, "sizeInGiB":30}'
177+
```
178+
135179
## Credentials and Region
136180

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

action.yml

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ inputs:
4040
force-new-deployment:
4141
description: 'Whether to force a new deployment of the service. Valid value is "true". Will default to not force a new deployment.'
4242
required: false
43+
service-managed-ebs-volume-name:
44+
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."
45+
required: false
46+
service-managed-ebs-volume:
47+
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."
48+
required: false
4349
run-task:
4450
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 .'
4551
required: false
@@ -67,6 +73,12 @@ inputs:
6773
run-task-tags:
6874
description: 'A JSON array of tags.'
6975
required: false
76+
run-task-managed-ebs-volume-name:
77+
description: "The name of the volume. This value must match the volume name from the Volume object in the task definition, that was configuredAtLaunch."
78+
required: false
79+
run-task-managed-ebs-volume:
80+
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."
81+
required: false
7082
wait-for-task-stopped:
7183
description: 'Whether to wait for the task to stop when running it outside of a service. Will default to not wait.'
7284
required: false

dist/index.js

+79-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
3939
const assignPublicIP = core.getInput('run-task-assign-public-IP', { required: false }) || 'DISABLED';
4040
const tags = JSON.parse(core.getInput('run-task-tags', { required: false }) || '[]');
4141
const capacityProviderStrategy = JSON.parse(core.getInput('run-task-capacity-provider-strategy', { required: false }) || '[]');
42+
const runTaskManagedEBSVolumeName = core.getInput('run-task-managed-ebs-volume-name', { required: false }) || '';
43+
const runTaskManagedEBSVolume = core.getInput('run-task-managed-ebs-volume', { required: false }) || '{}';
4244

4345
let awsvpcConfiguration = {}
4446

@@ -53,6 +55,20 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
5355
if(assignPublicIP != "" && (subnetIds != "" || securityGroupIds != "")){
5456
awsvpcConfiguration["assignPublicIp"] = assignPublicIP
5557
}
58+
let volumeConfigurations = [];
59+
let taskManagedEBSVolumeObject;
60+
61+
if (runTaskManagedEBSVolumeName != '') {
62+
if (runTaskManagedEBSVolume != '{}') {
63+
taskManagedEBSVolumeObject = convertToManagedEbsVolumeObject(runTaskManagedEBSVolume);
64+
volumeConfigurations = [{
65+
name: runTaskManagedEBSVolumeName,
66+
managedEBSVolume: taskManagedEBSVolumeObject
67+
}];
68+
} else {
69+
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`);
70+
}
71+
}
5672

5773
const runTaskResponse = await ecs.runTask({
5874
startedBy: startedBy,
@@ -65,7 +81,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
6581
launchType: capacityProviderStrategy.length === 0 ? launchType : null,
6682
networkConfiguration: Object.keys(awsvpcConfiguration).length === 0 ? null : { awsvpcConfiguration: awsvpcConfiguration },
6783
enableECSManagedTags: enableECSManagedTags,
68-
tags: tags
84+
tags: tags,
85+
volumeConfigurations: volumeConfigurations
6986
});
7087

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

112+
function convertToManagedEbsVolumeObject(managedEbsVolume) {
113+
managedEbsVolumeObject = {}
114+
const ebsVolumeObject = JSON.parse(managedEbsVolume);
115+
if ('roleArn' in ebsVolumeObject){ // required property
116+
managedEbsVolumeObject.roleArn = ebsVolumeObject.roleArn;
117+
core.debug(`Found RoleArn ${ebsVolumeObject['roleArn']}`);
118+
} else {
119+
throw new Error('managed-ebs-volume must provide "role-arn" to associate with the EBS volume')
120+
}
121+
122+
if ('encrypted' in ebsVolumeObject) {
123+
managedEbsVolumeObject.encrypted = ebsVolumeObject.encrypted;
124+
}
125+
if ('filesystemType' in ebsVolumeObject) {
126+
managedEbsVolumeObject.filesystemType = ebsVolumeObject.filesystemType;
127+
}
128+
if ('iops' in ebsVolumeObject) {
129+
managedEbsVolumeObject.iops = ebsVolumeObject.iops;
130+
}
131+
if ('kmsKeyId' in ebsVolumeObject) {
132+
managedEbsVolumeObject.kmsKeyId = ebsVolumeObject.kmsKeyId;
133+
}
134+
if ('sizeInGiB' in ebsVolumeObject) {
135+
managedEbsVolumeObject.sizeInGiB = ebsVolumeObject.sizeInGiB;
136+
}
137+
if ('snapshotId' in ebsVolumeObject) {
138+
managedEbsVolumeObject.snapshotId = ebsVolumeObject.snapshotId;
139+
}
140+
if ('tagSpecifications' in ebsVolumeObject) {
141+
managedEbsVolumeObject.tagSpecifications = ebsVolumeObject.tagSpecifications;
142+
}
143+
if (('throughput' in ebsVolumeObject) && (('volumeType' in ebsVolumeObject) && (ebsVolumeObject.volumeType == 'gp3'))){
144+
managedEbsVolumeObject.throughput = ebsVolumeObject.throughput;
145+
}
146+
if ('volumeType' in ebsVolumeObject) {
147+
managedEbsVolumeObject.volumeType = ebsVolumeObject.volumeType;
148+
}
149+
core.debug(`Created managedEbsVolumeObject: ${JSON.stringify(managedEbsVolumeObject)}`);
150+
return managedEbsVolumeObject;
151+
}
152+
95153
// Poll tasks until they enter a stopped state
96154
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
97155
if (waitForMinutes > MAX_WAIT_MINUTES) {
@@ -142,13 +200,32 @@ async function tasksExitCode(ecs, clusterName, taskArns) {
142200
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
143201
core.debug('Updating the service');
144202

203+
const serviceManagedEBSVolumeName = core.getInput('service-managed-ebs-volume-name', { required: false }) || '';
204+
const serviceManagedEBSVolume = core.getInput('service-managed-ebs-volume', { required: false }) || '{}';
205+
206+
let volumeConfigurations = [];
207+
let serviceManagedEbsVolumeObject;
208+
209+
if (serviceManagedEBSVolumeName != '') {
210+
if (serviceManagedEBSVolume != '{}') {
211+
serviceManagedEbsVolumeObject = convertToManagedEbsVolumeObject(serviceManagedEBSVolume);
212+
volumeConfigurations = [{
213+
name: serviceManagedEBSVolumeName,
214+
managedEBSVolume: serviceManagedEbsVolumeObject
215+
}];
216+
} else {
217+
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');
218+
}
219+
}
220+
145221
let params = {
146222
cluster: clusterName,
147223
service: service,
148224
taskDefinition: taskDefArn,
149225
forceNewDeployment: forceNewDeployment,
150226
enableECSManagedTags: enableECSManagedTags,
151-
propagateTags: propagateTags
227+
propagateTags: propagateTags,
228+
volumeConfigurations: volumeConfigurations
152229
};
153230

154231
// Add the desiredCount property only if it is defined and a number.

index.js

+79-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
3333
const assignPublicIP = core.getInput('run-task-assign-public-IP', { required: false }) || 'DISABLED';
3434
const tags = JSON.parse(core.getInput('run-task-tags', { required: false }) || '[]');
3535
const capacityProviderStrategy = JSON.parse(core.getInput('run-task-capacity-provider-strategy', { required: false }) || '[]');
36+
const runTaskManagedEBSVolumeName = core.getInput('run-task-managed-ebs-volume-name', { required: false }) || '';
37+
const runTaskManagedEBSVolume = core.getInput('run-task-managed-ebs-volume', { required: false }) || '{}';
3638

3739
let awsvpcConfiguration = {}
3840

@@ -47,6 +49,20 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
4749
if(assignPublicIP != "" && (subnetIds != "" || securityGroupIds != "")){
4850
awsvpcConfiguration["assignPublicIp"] = assignPublicIP
4951
}
52+
let volumeConfigurations = [];
53+
let taskManagedEBSVolumeObject;
54+
55+
if (runTaskManagedEBSVolumeName != '') {
56+
if (runTaskManagedEBSVolume != '{}') {
57+
taskManagedEBSVolumeObject = convertToManagedEbsVolumeObject(runTaskManagedEBSVolume);
58+
volumeConfigurations = [{
59+
name: runTaskManagedEBSVolumeName,
60+
managedEBSVolume: taskManagedEBSVolumeObject
61+
}];
62+
} else {
63+
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`);
64+
}
65+
}
5066

5167
const runTaskResponse = await ecs.runTask({
5268
startedBy: startedBy,
@@ -59,7 +75,8 @@ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes, enableECSMa
5975
launchType: capacityProviderStrategy.length === 0 ? launchType : null,
6076
networkConfiguration: Object.keys(awsvpcConfiguration).length === 0 ? null : { awsvpcConfiguration: awsvpcConfiguration },
6177
enableECSManagedTags: enableECSManagedTags,
62-
tags: tags
78+
tags: tags,
79+
volumeConfigurations: volumeConfigurations
6380
});
6481

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

106+
function convertToManagedEbsVolumeObject(managedEbsVolume) {
107+
managedEbsVolumeObject = {}
108+
const ebsVolumeObject = JSON.parse(managedEbsVolume);
109+
if ('roleArn' in ebsVolumeObject){ // required property
110+
managedEbsVolumeObject.roleArn = ebsVolumeObject.roleArn;
111+
core.debug(`Found RoleArn ${ebsVolumeObject['roleArn']}`);
112+
} else {
113+
throw new Error('managed-ebs-volume must provide "role-arn" to associate with the EBS volume')
114+
}
115+
116+
if ('encrypted' in ebsVolumeObject) {
117+
managedEbsVolumeObject.encrypted = ebsVolumeObject.encrypted;
118+
}
119+
if ('filesystemType' in ebsVolumeObject) {
120+
managedEbsVolumeObject.filesystemType = ebsVolumeObject.filesystemType;
121+
}
122+
if ('iops' in ebsVolumeObject) {
123+
managedEbsVolumeObject.iops = ebsVolumeObject.iops;
124+
}
125+
if ('kmsKeyId' in ebsVolumeObject) {
126+
managedEbsVolumeObject.kmsKeyId = ebsVolumeObject.kmsKeyId;
127+
}
128+
if ('sizeInGiB' in ebsVolumeObject) {
129+
managedEbsVolumeObject.sizeInGiB = ebsVolumeObject.sizeInGiB;
130+
}
131+
if ('snapshotId' in ebsVolumeObject) {
132+
managedEbsVolumeObject.snapshotId = ebsVolumeObject.snapshotId;
133+
}
134+
if ('tagSpecifications' in ebsVolumeObject) {
135+
managedEbsVolumeObject.tagSpecifications = ebsVolumeObject.tagSpecifications;
136+
}
137+
if (('throughput' in ebsVolumeObject) && (('volumeType' in ebsVolumeObject) && (ebsVolumeObject.volumeType == 'gp3'))){
138+
managedEbsVolumeObject.throughput = ebsVolumeObject.throughput;
139+
}
140+
if ('volumeType' in ebsVolumeObject) {
141+
managedEbsVolumeObject.volumeType = ebsVolumeObject.volumeType;
142+
}
143+
core.debug(`Created managedEbsVolumeObject: ${JSON.stringify(managedEbsVolumeObject)}`);
144+
return managedEbsVolumeObject;
145+
}
146+
89147
// Poll tasks until they enter a stopped state
90148
async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
91149
if (waitForMinutes > MAX_WAIT_MINUTES) {
@@ -136,13 +194,32 @@ async function tasksExitCode(ecs, clusterName, taskArns) {
136194
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment, desiredCount, enableECSManagedTags, propagateTags) {
137195
core.debug('Updating the service');
138196

197+
const serviceManagedEBSVolumeName = core.getInput('service-managed-ebs-volume-name', { required: false }) || '';
198+
const serviceManagedEBSVolume = core.getInput('service-managed-ebs-volume', { required: false }) || '{}';
199+
200+
let volumeConfigurations = [];
201+
let serviceManagedEbsVolumeObject;
202+
203+
if (serviceManagedEBSVolumeName != '') {
204+
if (serviceManagedEBSVolume != '{}') {
205+
serviceManagedEbsVolumeObject = convertToManagedEbsVolumeObject(serviceManagedEBSVolume);
206+
volumeConfigurations = [{
207+
name: serviceManagedEBSVolumeName,
208+
managedEBSVolume: serviceManagedEbsVolumeObject
209+
}];
210+
} else {
211+
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');
212+
}
213+
}
214+
139215
let params = {
140216
cluster: clusterName,
141217
service: service,
142218
taskDefinition: taskDefArn,
143219
forceNewDeployment: forceNewDeployment,
144220
enableECSManagedTags: enableECSManagedTags,
145-
propagateTags: propagateTags
221+
propagateTags: propagateTags,
222+
volumeConfigurations: volumeConfigurations
146223
};
147224

148225
// Add the desiredCount property only if it is defined and a number.

0 commit comments

Comments
 (0)