diff --git a/action.yml b/action.yml index 39185eb0..89cda846 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,8 @@ inputs: Specify here which mode you want to use: - 'start' - to start a new runner; - 'stop' - to stop the previously created runner. + - 'resume' - to start an existing instance. + - 'pause' - to stop the previously created runner. required: true github-token: description: >- diff --git a/dist/index.js b/dist/index.js index c213fdae..2513b939 100644 --- a/dist/index.js +++ b/dist/index.js @@ -62856,6 +62856,38 @@ async function startEc2Instance(label, githubRegistrationToken) { } } +async function resumeEc2Instance(ec2InstanceId) { + const ec2 = new AWS.EC2(); + + try { + const result = await ec2.startInstances({InstanceIds: [ec2InstanceId]}).promise(); + core.info(`AWS EC2 instance ${ec2InstanceId} is started`); + return ec2InstanceId; + } catch (error) { + core.error('AWS EC2 instance starting error'); + throw error; + } + +} + +async function stopEc2Instance() { + const ec2 = new AWS.EC2(); + + const params = { + InstanceIds: [config.input.ec2InstanceId], + }; + + try { + core.info(`AWS EC2 instance ${config.input.ec2InstanceId} stopping`); + await ec2.stopInstances(params).promise(); + core.info(`AWS EC2 instance ${config.input.ec2InstanceId} is stopped`); + return; + } catch (error) { + core.error(`AWS EC2 instance ${config.input.ec2InstanceId} stop error`); + throw error; + } +} + async function terminateEc2Instance() { const ec2 = new AWS.EC2(); @@ -62890,10 +62922,51 @@ async function waitForInstanceRunning(ec2InstanceId) { } } +async function waitForInstanceStopped(ec2InstanceId) { + const ec2 = new AWS.EC2(); + + const params = { + InstanceIds: [ec2InstanceId], + }; + + try { + await ec2.waitFor('instanceStopped', params).promise(); + } catch (error) { + core.error(`AWS EC2 instance ${ec2InstanceId} stopped error`); + throw error; + } +} + +async function startRunner(ec2InstanceId) { + const ssm = new AWS.SSM(); + + const commands = [ + 'cd ~/actions-runner/', + './run.sh', + ]; + + const params = { + DocumentName: 'AWS-RunShellScript', + Targets: [{'Key':'InstanceIds', 'Values':[ec2InstanceId]}], + Parameters: {'commands': [commands]}, + } + + try { + core.info('Sending command to start GitHub runner') + ssm.sendCommand(params); + } catch (error) { + core.error('Could not send command to instance'); + } +} + module.exports = { startEc2Instance, + resumeEc2Instance, + stopEc2Instance, terminateEc2Instance, waitForInstanceRunning, + waitForInstanceStopped, + startRunner, }; @@ -62954,6 +63027,14 @@ class Config { if (!this.input.label || !this.input.ec2InstanceId) { throw new Error(`Not all the required inputs are provided for the 'stop' mode`); } + } else if (this.input.mode === 'resume') { + if (!this.input.ec2InstanceId || !this.input.label || !this.input.githubToken) { + throw new Error(`Not all the required inputs are provided for the 'resume' mode`); + } + } else if (this.input.mode === 'pause') { + if (!this.input.label || !this.input.ec2InstanceId) { + throw new Error(`Not all the required inputs are provided for the 'pause' mode`); + } } else { throw new Error('Wrong mode. Allowed values: start, stop.'); } @@ -63098,9 +63179,32 @@ async function stop() { await gh.removeRunner(); } +async function resume() { + const label = config.input.label; + const githubRegistrationToken = await gh.getRegistrationToken(); + const ec2InstanceId = config.input.ec2InstanceId; + await aws.waitForInstanceStopped(ec2InstanceId); + await aws.resumeEc2Instance(ec2InstanceId); + setOutput(label, ec2InstanceId); + await aws.waitForInstanceRunning(ec2InstanceId); + await gh.waitForRunnerRegistered(label); +} + +async function pause() { + await aws.stopEc2Instance(); +} + (async function () { try { - config.input.mode === 'start' ? await start() : await stop(); + if (config.input.mode === 'start') { + await start(); + } else if (config.input.mode === 'stop') { + await stop(); + } else if (config.input.mode === 'resume') { + await resume(); + } else if (config.input.mode === 'pause') { + await pause(); + } } catch (error) { core.error(error); core.setFailed(error.message); diff --git a/src/aws.js b/src/aws.js index c6bd8c9d..c65ca40d 100644 --- a/src/aws.js +++ b/src/aws.js @@ -56,6 +56,38 @@ async function startEc2Instance(label, githubRegistrationToken) { } } +async function resumeEc2Instance(ec2InstanceId) { + const ec2 = new AWS.EC2(); + + try { + const result = await ec2.startInstances({InstanceIds: [ec2InstanceId]}).promise(); + core.info(`AWS EC2 instance ${ec2InstanceId} is started`); + return ec2InstanceId; + } catch (error) { + core.error('AWS EC2 instance starting error'); + throw error; + } + +} + +async function stopEc2Instance() { + const ec2 = new AWS.EC2(); + + const params = { + InstanceIds: [config.input.ec2InstanceId], + }; + + try { + core.info(`AWS EC2 instance ${config.input.ec2InstanceId} stopping`); + await ec2.stopInstances(params).promise(); + core.info(`AWS EC2 instance ${config.input.ec2InstanceId} is stopped`); + return; + } catch (error) { + core.error(`AWS EC2 instance ${config.input.ec2InstanceId} stop error`); + throw error; + } +} + async function terminateEc2Instance() { const ec2 = new AWS.EC2(); @@ -90,8 +122,49 @@ async function waitForInstanceRunning(ec2InstanceId) { } } +async function waitForInstanceStopped(ec2InstanceId) { + const ec2 = new AWS.EC2(); + + const params = { + InstanceIds: [ec2InstanceId], + }; + + try { + await ec2.waitFor('instanceStopped', params).promise(); + } catch (error) { + core.error(`AWS EC2 instance ${ec2InstanceId} stopped error`); + throw error; + } +} + +async function startRunner(ec2InstanceId) { + const ssm = new AWS.SSM(); + + const commands = [ + 'cd ~/actions-runner/', + './run.sh', + ]; + + const params = { + DocumentName: 'AWS-RunShellScript', + Targets: [{'Key':'InstanceIds', 'Values':[ec2InstanceId]}], + Parameters: {'commands': [commands]}, + } + + try { + core.info('Sending command to start GitHub runner') + ssm.sendCommand(params); + } catch (error) { + core.error('Could not send command to instance'); + } +} + module.exports = { startEc2Instance, + resumeEc2Instance, + stopEc2Instance, terminateEc2Instance, waitForInstanceRunning, + waitForInstanceStopped, + startRunner, }; diff --git a/src/config.js b/src/config.js index 13bf86a1..3b26ec90 100644 --- a/src/config.js +++ b/src/config.js @@ -50,6 +50,14 @@ class Config { if (!this.input.label || !this.input.ec2InstanceId) { throw new Error(`Not all the required inputs are provided for the 'stop' mode`); } + } else if (this.input.mode === 'resume') { + if (!this.input.ec2InstanceId || !this.input.label || !this.input.githubToken) { + throw new Error(`Not all the required inputs are provided for the 'resume' mode`); + } + } else if (this.input.mode === 'pause') { + if (!this.input.label || !this.input.ec2InstanceId) { + throw new Error(`Not all the required inputs are provided for the 'pause' mode`); + } } else { throw new Error('Wrong mode. Allowed values: start, stop.'); } diff --git a/src/index.js b/src/index.js index 00bc5152..c6653d76 100644 --- a/src/index.js +++ b/src/index.js @@ -22,9 +22,32 @@ async function stop() { await gh.removeRunner(); } +async function resume() { + const label = config.input.label; + const githubRegistrationToken = await gh.getRegistrationToken(); + const ec2InstanceId = config.input.ec2InstanceId; + await aws.waitForInstanceStopped(ec2InstanceId); + await aws.resumeEc2Instance(ec2InstanceId); + setOutput(label, ec2InstanceId); + await aws.waitForInstanceRunning(ec2InstanceId); + await gh.waitForRunnerRegistered(label); +} + +async function pause() { + await aws.stopEc2Instance(); +} + (async function () { try { - config.input.mode === 'start' ? await start() : await stop(); + if (config.input.mode === 'start') { + await start(); + } else if (config.input.mode === 'stop') { + await stop(); + } else if (config.input.mode === 'resume') { + await resume(); + } else if (config.input.mode === 'pause') { + await pause(); + } } catch (error) { core.error(error); core.setFailed(error.message);