Skip to content

Commit 4efaa2c

Browse files
authored
feat: support spot instances (#1)
* feat: support for defining market type for the allocated instance * docs: polish README.md * build: package with market-type feature * fix: fix validation for marketType as undefined can still be returned it seems * fix: config stores inputs in the input object * fix: set spot options correctly
1 parent 2c4d1dc commit 4efaa2c

File tree

5 files changed

+54
-4
lines changed

5 files changed

+54
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Now you're ready to go!
205205
| `iam-role-name` | Optional. Used only with the `start` mode. | IAM role name to attach to the created EC2 runner. <br><br> This allows the runner to have permissions to run additional actions within the AWS account, without having to manage additional GitHub secrets and AWS users. <br><br> Setting this requires additional AWS permissions for the role launching the instance (see above). |
206206
| `aws-resource-tags` | Optional. Used only with the `start` mode. | Specifies tags to add to the EC2 instance and any attached storage. <br><br> This field is a stringified JSON array of tag objects, each containing a `Key` and `Value` field (see example below). <br><br> Setting this requires additional AWS permissions for the role launching the instance (see above). |
207207
| `runner-home-dir` | Optional. Used only with the `start` mode. | Specifies a directory where pre-installed actions-runner software and scripts are located.<br><br> |
208+
| `market-type` | Optional. Used only with the `start` mode. | Specifies the market (purchasing) option for the instance. Allowed values: `spot`. The default is to use an on-demand instance.
208209
| `pre-runner-script` | Optional. Used only with the `start` mode. | Specifies bash commands to run before the runner starts. It's useful for installing dependencies with apt-get, yum, dnf, etc. For example:<pre> - name: Start EC2 runner<br> with:<br> mode: start<br> ...<br> pre-runner-script: \|<br> sudo yum update -y && \ <br> sudo yum install docker git libicu -y<br> sudo systemctl enable docker</pre>
209210
<br><br> |
210211

action.yml

+7-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ inputs:
2222
required: false
2323
ec2-instance-type:
2424
description: >-
25-
EC2 Instance Type.
25+
EC2 Instance Type.
2626
This input is required if you use the 'start' mode.
2727
required: false
2828
subnet-id:
@@ -32,7 +32,7 @@ inputs:
3232
required: false
3333
security-group-id:
3434
description: >-
35-
EC2 Security Group Id.
35+
EC2 Security Group Id.
3636
The security group should belong to the same VPC as the specified subnet.
3737
The runner doesn't require any inbound traffic. However, outbound traffic should be allowed.
3838
This input is required if you use the 'start' mode.
@@ -69,6 +69,11 @@ inputs:
6969
description: >-
7070
Specifies bash commands to run before the runner starts. It's useful for installing dependencies with apt-get, yum, dnf, etc.
7171
required: false
72+
market-type:
73+
description: >-
74+
Specifies the market (purchasing) option for the instance:
75+
- 'spot' - Use a spot instance
76+
required: false
7277

7378
outputs:
7479
label:

dist/index.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -62832,6 +62832,19 @@ function buildUserDataScript(githubRegistrationToken, label) {
6283262832
}
6283362833
}
6283462834

62835+
function buildMarketOptions() {
62836+
if (config.input.marketType === 'spot') {
62837+
return {
62838+
MarketType: config.input.marketType,
62839+
SpotOptions: {
62840+
SpotInstanceType: 'one-time',
62841+
},
62842+
};
62843+
}
62844+
62845+
return undefined;
62846+
}
62847+
6283562848
async function startEc2Instance(label, githubRegistrationToken) {
6283662849
const ec2 = new AWS.EC2();
6283762850

@@ -62847,6 +62860,7 @@ async function startEc2Instance(label, githubRegistrationToken) {
6284762860
SecurityGroupIds: [config.input.securityGroupId],
6284862861
IamInstanceProfile: { Name: config.input.iamRoleName },
6284962862
TagSpecifications: config.tagSpecifications,
62863+
InstanceMarketOptions: buildMarketOptions(),
6285062864
};
6285162865

6285262866
try {
@@ -62923,12 +62937,16 @@ class Config {
6292362937
iamRoleName: core.getInput('iam-role-name'),
6292462938
runnerHomeDir: core.getInput('runner-home-dir'),
6292562939
preRunnerScript: core.getInput('pre-runner-script'),
62940+
marketType: core.getInput('market-type'),
6292662941
};
6292762942

6292862943
const tags = JSON.parse(core.getInput('aws-resource-tags'));
6292962944
this.tagSpecifications = null;
6293062945
if (tags.length > 0) {
62931-
this.tagSpecifications = [{ResourceType: 'instance', Tags: tags}, {ResourceType: 'volume', Tags: tags}];
62946+
this.tagSpecifications = [
62947+
{ ResourceType: 'instance', Tags: tags },
62948+
{ ResourceType: 'volume', Tags: tags },
62949+
];
6293262950
}
6293362951

6293462952
// the values of github.context.repo.owner and github.context.repo.repo are taken from
@@ -62955,6 +62973,10 @@ class Config {
6295562973
if (!this.input.ec2ImageId || !this.input.ec2InstanceType || !this.input.subnetId || !this.input.securityGroupId) {
6295662974
throw new Error(`Not all the required inputs are provided for the 'start' mode`);
6295762975
}
62976+
62977+
if (this.marketType?.length > 0 && this.input.marketType !== 'spot') {
62978+
throw new Error(`Invalid 'market-type' input. Allowed values: spot.`);
62979+
}
6295862980
} else if (this.input.mode === 'stop') {
6295962981
if (!this.input.label || !this.input.ec2InstanceId) {
6296062982
throw new Error(`Not all the required inputs are provided for the 'stop' mode`);

src/aws.js

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ function buildUserDataScript(githubRegistrationToken, label) {
3232
}
3333
}
3434

35+
function buildMarketOptions() {
36+
if (config.input.marketType === 'spot') {
37+
return {
38+
MarketType: config.input.marketType,
39+
SpotOptions: {
40+
SpotInstanceType: 'one-time',
41+
},
42+
};
43+
}
44+
45+
return undefined;
46+
}
47+
3548
async function startEc2Instance(label, githubRegistrationToken) {
3649
const ec2 = new AWS.EC2();
3750

@@ -47,6 +60,7 @@ async function startEc2Instance(label, githubRegistrationToken) {
4760
SecurityGroupIds: [config.input.securityGroupId],
4861
IamInstanceProfile: { Name: config.input.iamRoleName },
4962
TagSpecifications: config.tagSpecifications,
63+
InstanceMarketOptions: buildMarketOptions(),
5064
};
5165

5266
try {

src/config.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ class Config {
1515
iamRoleName: core.getInput('iam-role-name'),
1616
runnerHomeDir: core.getInput('runner-home-dir'),
1717
preRunnerScript: core.getInput('pre-runner-script'),
18+
marketType: core.getInput('market-type'),
1819
};
1920

2021
const tags = JSON.parse(core.getInput('aws-resource-tags'));
2122
this.tagSpecifications = null;
2223
if (tags.length > 0) {
23-
this.tagSpecifications = [{ResourceType: 'instance', Tags: tags}, {ResourceType: 'volume', Tags: tags}];
24+
this.tagSpecifications = [
25+
{ ResourceType: 'instance', Tags: tags },
26+
{ ResourceType: 'volume', Tags: tags },
27+
];
2428
}
2529

2630
// the values of github.context.repo.owner and github.context.repo.repo are taken from
@@ -47,6 +51,10 @@ class Config {
4751
if (!this.input.ec2ImageId || !this.input.ec2InstanceType || !this.input.subnetId || !this.input.securityGroupId) {
4852
throw new Error(`Not all the required inputs are provided for the 'start' mode`);
4953
}
54+
55+
if (this.marketType?.length > 0 && this.input.marketType !== 'spot') {
56+
throw new Error(`Invalid 'market-type' input. Allowed values: spot.`);
57+
}
5058
} else if (this.input.mode === 'stop') {
5159
if (!this.input.label || !this.input.ec2InstanceId) {
5260
throw new Error(`Not all the required inputs are provided for the 'stop' mode`);

0 commit comments

Comments
 (0)