@@ -192,6 +192,107 @@ const IGNORED_TASK_DEFINITION_ATTRIBUTES = [
192
192
'registeredBy'
193
193
];
194
194
195
+ // Run task outside of a service
196
+ async function runTask(ecs, clusterName, taskDefArn, waitForMinutes) {
197
+ core.info('Running task')
198
+
199
+ const waitForTask = core.getInput('wait-for-task-stopped', { required: false }) || 'false';
200
+ const startedBy = core.getInput('run-task-started-by', { required: false }) || 'GitHub-Actions';
201
+ const launchType = core.getInput('run-task-launch-type', { required: false }) || 'FARGATE';
202
+ const subnetIds = core.getInput('run-task-subnet-ids', { required: false }) || '';
203
+ const securityGroupIds = core.getInput('run-task-security-group-ids', { required: false }) || '';
204
+ const containerOverrides = JSON.parse(core.getInput('run-task-container-overrides', { required: false }) || '[]');
205
+ let awsVpcNetworkConfiguration = {}
206
+
207
+ if (subnetIds != "") {
208
+ awsVpcNetworkConfiguration["subnets"] = subnetIds.split(',')
209
+ }
210
+
211
+ if (securityGroupIds != "") {
212
+ awsVpcNetworkConfiguration["securityGroups"] = securityGroupIds.split(',')
213
+ }
214
+
215
+ const runTaskResponse = await ecs.runTask({
216
+ startedBy: startedBy,
217
+ cluster: clusterName,
218
+ taskDefinition: taskDefArn,
219
+ overrides: {
220
+ containerOverrides: containerOverrides
221
+ },
222
+ launchType: launchType,
223
+ networkConfiguration: awsVpcNetworkConfiguration === {} ? {} : { awsVpcNetworkConfiguration: awsVpcNetworkConfiguration }
224
+ }).promise();
225
+
226
+ core.debug(`Run task response ${JSON.stringify(runTaskResponse)}`)
227
+
228
+ const taskArns = runTaskResponse.tasks.map(task => task.taskArn);
229
+ core.setOutput('run-task-arn', taskArns);
230
+
231
+ taskArns.map(taskArn => {
232
+ let taskId = taskArn.split('/').pop();
233
+ core.info(`Task running: https://console.aws.amazon.com/ecs/home?region=${aws.config.region}#/clusters/${clusterName}/tasks`)
234
+ });
235
+
236
+ if (runTaskResponse.failures && runTaskResponse.failures.length > 0) {
237
+ const failure = runTaskResponse.failures[0];
238
+ throw new Error(`${failure.arn} is ${failure.reason}`);
239
+ }
240
+
241
+ // Wait for task to end
242
+ if (waitForTask && waitForTask.toLowerCase() === "true") {
243
+ await waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes)
244
+ await tasksExitCode(ecs, clusterName, taskArns)
245
+ } else {
246
+ core.debug('Not waiting for the task to stop');
247
+ }
248
+ }
249
+
250
+ // Poll tasks until they enter a stopped state
251
+ async function waitForTasksStopped(ecs, clusterName, taskArns, waitForMinutes) {
252
+ if (waitForMinutes > MAX_WAIT_MINUTES) {
253
+ waitForMinutes = MAX_WAIT_MINUTES;
254
+ }
255
+
256
+ core.info(`Waiting for tasks to stop. Will wait for ${waitForMinutes} minutes`);
257
+
258
+ const waitTaskResponse = await ecs.waitFor('tasksStopped', {
259
+ cluster: clusterName,
260
+ tasks: taskArns,
261
+ $waiter: {
262
+ delay: WAIT_DEFAULT_DELAY_SEC,
263
+ maxAttempts: (waitForMinutes * 60) / WAIT_DEFAULT_DELAY_SEC
264
+ }
265
+ }).promise();
266
+
267
+ core.debug(`Run task response ${JSON.stringify(waitTaskResponse)}`);
268
+ core.info('All tasks have stopped.');
269
+ }
270
+
271
+ // Check a task's exit code and fail the job on error
272
+ async function tasksExitCode(ecs, clusterName, taskArns) {
273
+ const describeResponse = await ecs.describeTasks({
274
+ cluster: clusterName,
275
+ tasks: taskArns
276
+ }).promise();
277
+
278
+ const containers = [].concat(...describeResponse.tasks.map(task => task.containers))
279
+ const exitCodes = containers.map(container => container.exitCode)
280
+ const reasons = containers.map(container => container.reason)
281
+
282
+ const failuresIdx = [];
283
+
284
+ exitCodes.filter((exitCode, index) => {
285
+ if (exitCode !== 0) {
286
+ failuresIdx.push(index)
287
+ }
288
+ })
289
+
290
+ const failures = reasons.filter((_, index) => failuresIdx.indexOf(index) !== -1)
291
+ if (failures.length > 0) {
292
+ throw new Error(`Run task failed: ${JSON.stringify(failures)}`);
293
+ }
294
+ }
295
+
195
296
// Deploy to a service that uses the 'ECS' deployment controller
196
297
async function updateEcsService(ecs, clusterName, service, taskDefArn, waitForService, waitForMinutes, forceNewDeployment) {
197
298
core.debug('Updating the service');
@@ -457,10 +558,18 @@ async function run() {
457
558
const taskDefArn = registerResponse.taskDefinition.taskDefinitionArn;
458
559
core.setOutput('task-definition-arn', taskDefArn);
459
560
561
+ // Run the task outside of the service
562
+ const clusterName = cluster ? cluster : 'default';
563
+ const shouldRunTaskInput = core.getInput('run-task', { required: false }) || 'false';
564
+ const shouldRunTask = shouldRunTaskInput.toLowerCase() === 'true';
565
+ core.debug(`shouldRunTask: ${shouldRunTask}`);
566
+ if (shouldRunTask) {
567
+ core.debug("Running ad-hoc task...");
568
+ await runTask(ecs, clusterName, taskDefArn, waitForMinutes);
569
+ }
570
+
460
571
// Update the service with the new task definition
461
572
if (service) {
462
- const clusterName = cluster ? cluster : 'default';
463
-
464
573
// Determine the deployment controller
465
574
const describeResponse = await ecs.describeServices({
466
575
services: [service],
0 commit comments