diff --git a/README.md b/README.md
index 9a4adf5f..e93128c4 100644
--- a/README.md
+++ b/README.md
@@ -21,19 +21,20 @@ See [below](#example) the YAML code of the depicted workflow.
**Table of Contents**
-- [Use cases](#use-cases)
- - [Access private resources in your VPC](#access-private-resources-in-your-vpc)
- - [Customize hardware configuration](#customize-hardware-configuration)
- - [Save costs](#save-costs)
-- [Usage](#usage)
- - [How to start](#how-to-start)
- - [Inputs](#inputs)
- - [Environment variables](#environment-variables)
- - [Outputs](#outputs)
- - [Example](#example)
- - [Real user examples](#real-user-examples)
-- [Self-hosted runner security with public repositories](#self-hosted-runner-security-with-public-repositories)
-- [License Summary](#license-summary)
+- [On-demand self-hosted AWS EC2 runner for GitHub Actions](#on-demand-self-hosted-aws-ec2-runner-for-github-actions)
+ - [Use cases](#use-cases)
+ - [Access private resources in your VPC](#access-private-resources-in-your-vpc)
+ - [Customize hardware configuration](#customize-hardware-configuration)
+ - [Save costs](#save-costs)
+ - [Usage](#usage)
+ - [How to start](#how-to-start)
+ - [Inputs](#inputs)
+ - [Environment variables](#environment-variables)
+ - [Outputs](#outputs)
+ - [Example](#example)
+ - [Real user examples](#real-user-examples)
+ - [Self-hosted runner security with public repositories](#self-hosted-runner-security-with-public-repositories)
+ - [License Summary](#license-summary)
## Use cases
@@ -203,6 +204,7 @@ Now you're ready to go!
| `iam-role-name` | Optional. Used only with the `start` mode. | IAM role name to attach to the created EC2 runner.
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.
Setting this requires additional AWS permissions for the role launching the instance (see above). |
| `aws-resource-tags` | Optional. Used only with the `start` mode. | Specifies tags to add to the EC2 instance and any attached storage.
This field is a stringified JSON array of tag objects, each containing a `Key` and `Value` field (see example below).
Setting this requires additional AWS permissions for the role launching the instance (see above). |
| `runner-home-dir` | Optional. Used only with the `start` mode. | Specifies a directory where pre-installed actions-runner software and scripts are located.
|
+|``
### Environment variables
@@ -219,8 +221,11 @@ We recommend using [aws-actions/configure-aws-credentials](https://github.com/aw
| Name | Description |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `label` | Name of the unique label assigned to the runner.
The label is used in two cases:
- to use as the input of `runs-on` property for the following jobs;
- to remove the runner from GitHub when it is not needed anymore. |
+| `label` | Name of the unique label assigned to the runner. If it is not informed, the plugin will generate a random name.
The label is used in two cases:
- to use as the input of `runs-on` property for the following jobs;
- to remove the runner from GitHub when it is not needed anymore. |
| `ec2-instance-id` | EC2 Instance Id of the created runner.
The id is used to terminate the EC2 instance when the runner is not needed anymore. |
+|`scope`| The scope of yours runner it can be `repository`or `organization`. You need to have a Github Token with the correct permissions to create the org runner |
+| `host-id` | Created to support macOS Dedicated Hosts. You need to allocate the Dedicated Host and to inform its hostId to use this feature. |
+| `timeout` | The timeout to wait for the runner to be ready. The default value is 5 minutes|.
### Example
diff --git a/action.yml b/action.yml
index 39185eb0..8cb57bb7 100644
--- a/action.yml
+++ b/action.yml
@@ -39,10 +39,15 @@ inputs:
required: false
label:
description: >-
- Name of the unique label assigned to the runner.
+ Defines the runner's label name when set. If this value is not defined, the runner will generate a random label.
The label is used to remove the runner from GitHub when the runner is not needed anymore.
This input is required if you use the 'stop' mode.
required: false
+ scope:
+ description: >-
+ The runner's scope. The runner scope must be "repository", "organization" or "enterprise".
+ required: true
+ options: ["repository", "organization", "enterprise"]
ec2-instance-id:
description: >-
EC2 Instance Id of the created runner.
@@ -65,6 +70,15 @@ inputs:
description: >-
Directory that contains actions-runner software and scripts. E.g. /home/runner/actions-runner.
required: false
+ host-id:
+ description: >-
+ The host id to provide a dedicated host in your account. It is necessary for macos instances.
+ required: false
+ timeout:
+ description:
+ The time (in minutes) that this actions will wait the the runner be created. The default value is 5 minutes.
+ required: false
+ default: 5
outputs:
label:
description: >-
diff --git a/dist/index.js b/dist/index.js
index c213fdae..7d416a93 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -62811,18 +62811,22 @@ function buildUserDataScript(githubRegistrationToken, label) {
'#!/bin/bash',
`cd "${config.input.runnerHomeDir}"`,
'export RUNNER_ALLOW_RUNASROOT=1',
- `./config.sh --url https://github.com/${config.githubContext.owner}/${config.githubContext.repo} --token ${githubRegistrationToken} --labels ${label}`,
+ `./config.sh --url ${config.github.url} --token ${githubRegistrationToken} --labels ${label} --name ${label} --runnergroup default --work "${config.input.runnerHomeDir}" --replace`,
'./run.sh',
];
} else {
return [
'#!/bin/bash',
- 'mkdir actions-runner && cd actions-runner',
- 'case $(uname -m) in aarch64) ARCH="arm64" ;; amd64|x86_64) ARCH="x64" ;; esac && export RUNNER_ARCH=${ARCH}',
- 'curl -O -L https://github.com/actions/runner/releases/download/v2.299.1/actions-runner-linux-${RUNNER_ARCH}-2.299.1.tar.gz',
- 'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-2.299.1.tar.gz',
+ 'cd /opt && mkdir actions-runner && cd actions-runner',
+ 'case $(uname -m) in aarch64|arm64) ARCH="arm64" ;; amd64|x86_64) ARCH="x64" ;; esac && export RUNNER_ARCH=${ARCH}',
+ 'case $(uname -a) in Darwin*) OS="osx" ;; Linux*) OS="linux" ;; esac && export RUNNER_OS=${OS}',
+ 'export VERSION="2.303.0"',
+ 'curl -O -L https://github.com/actions/runner/releases/download/v${VERSION}/actions-runner-${RUNNER_OS}-${RUNNER_ARCH}-${VERSION}.tar.gz',
+ 'export LC_ALL=en_US.UTF-8',
+ 'export LANG=en_EN.UTF-8',
+ 'tar xzf ./actions-runner-${RUNNER_OS}-${RUNNER_ARCH}-${VERSION}.tar.gz',
'export RUNNER_ALLOW_RUNASROOT=1',
- `./config.sh --url https://github.com/${config.githubContext.owner}/${config.githubContext.repo} --token ${githubRegistrationToken} --labels ${label}`,
+ `./config.sh --url ${config.github.url} --token ${githubRegistrationToken} --labels ${label} --name ${label} --runnergroup default --work $(pwd) --replace`,
'./run.sh',
];
}
@@ -62845,6 +62849,13 @@ async function startEc2Instance(label, githubRegistrationToken) {
TagSpecifications: config.tagSpecifications,
};
+ if (config.input.hostId) {
+ params.Placement = {
+ Tenancy: 'host',
+ HostId: config.input.hostId
+ }
+ }
+
try {
const result = await ec2.runInstances(params).promise();
const ec2InstanceId = result.Instances[0].InstanceId;
@@ -62918,6 +62929,24 @@ class Config {
ec2InstanceId: core.getInput('ec2-instance-id'),
iamRoleName: core.getInput('iam-role-name'),
runnerHomeDir: core.getInput('runner-home-dir'),
+ scope: core.getInput('scope'),
+ hostId: core.getInput('host-id'),
+ };
+
+ this.GITHUB_SCOPES = {
+ organization: {
+ url: `https://github.com/${github.context.repo.owner}`,
+ context: { owner: github.context.repo.owner },
+ apiPath: `/orgs/${github.context.repo.owner}`
+ },
+ repository: {
+ url: `https://github.com/${github.context.repo.owner}/${github.context.repo.repo}`,
+ apiPath: `/repos/${github.context.repo.owner}/${github.context.repo.repo}`,
+ context: {
+ owner: github.context.repo.owner,
+ repo: github.context.repo.repo
+ }
+ }
};
const tags = JSON.parse(core.getInput('aws-resource-tags'));
@@ -62926,13 +62955,10 @@ class Config {
this.tagSpecifications = [{ResourceType: 'instance', Tags: tags}, {ResourceType: 'volume', Tags: tags}];
}
- // the values of github.context.repo.owner and github.context.repo.repo are taken from
- // the environment variable GITHUB_REPOSITORY specified in "owner/repo" format and
- // provided by the GitHub Action on the runtime
- this.githubContext = {
- owner: github.context.repo.owner,
- repo: github.context.repo.repo,
- };
+ this.github = this.GITHUB_SCOPES[this.input.scope];
+ if (!this.github) {
+ throw new Error(`The 'scope' input is not valid`);
+ }
//
// validate input
@@ -62959,8 +62985,12 @@ class Config {
}
}
- generateUniqueLabel() {
- return Math.random().toString(36).substr(2, 5);
+ generateLabel() {
+ if (!this.input.label) {
+ return Math.random().toString(36).substr(2, 5);
+ }
+
+ return this.input.label
}
}
@@ -62988,7 +63018,7 @@ async function getRunner(label) {
const octokit = github.getOctokit(config.input.githubToken);
try {
- const runners = await octokit.paginate('GET /repos/{owner}/{repo}/actions/runners', config.githubContext);
+ const runners = await octokit.paginate(`GET ${config.github.apiPath}/actions/runners`);
const foundRunners = _.filter(runners, { labels: [{ name: label }] });
return foundRunners.length > 0 ? foundRunners[0] : null;
} catch (error) {
@@ -63001,7 +63031,7 @@ async function getRegistrationToken() {
const octokit = github.getOctokit(config.input.githubToken);
try {
- const response = await octokit.request('POST /repos/{owner}/{repo}/actions/runners/registration-token', config.githubContext);
+ const response = await octokit.request(`POST ${config.github.apiPath}/actions/runners/registration-token`);
core.info('GitHub Registration Token is received');
return response.data.token;
} catch (error) {
@@ -63021,7 +63051,7 @@ async function removeRunner() {
}
try {
- await octokit.request('DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}', _.merge(config.githubContext, { runner_id: runner.id }));
+ await octokit.request(`DELETE ${config.github.apiPath}/actions/runners/{runner_id}`, { runner_id: runner.id });
core.info(`GitHub self-hosted runner ${runner.name} is removed`);
return;
} catch (error) {
@@ -63031,7 +63061,7 @@ async function removeRunner() {
}
async function waitForRunnerRegistered(label) {
- const timeoutMinutes = 5;
+ const timeoutMinutes = core.getInput('timeout')
const retryIntervalSeconds = 10;
const quietPeriodSeconds = 30;
let waitSeconds = 0;
@@ -63085,7 +63115,7 @@ function setOutput(label, ec2InstanceId) {
}
async function start() {
- const label = config.generateUniqueLabel();
+ const label = config.generateLabel();
const githubRegistrationToken = await gh.getRegistrationToken();
const ec2InstanceId = await aws.startEc2Instance(label, githubRegistrationToken);
setOutput(label, ec2InstanceId);
diff --git a/src/aws.js b/src/aws.js
index c6bd8c9d..25cd0095 100644
--- a/src/aws.js
+++ b/src/aws.js
@@ -11,18 +11,22 @@ function buildUserDataScript(githubRegistrationToken, label) {
'#!/bin/bash',
`cd "${config.input.runnerHomeDir}"`,
'export RUNNER_ALLOW_RUNASROOT=1',
- `./config.sh --url https://github.com/${config.githubContext.owner}/${config.githubContext.repo} --token ${githubRegistrationToken} --labels ${label}`,
+ `./config.sh --url ${config.github.url} --token ${githubRegistrationToken} --labels ${label} --name ${label} --runnergroup default --work "${config.input.runnerHomeDir}" --replace`,
'./run.sh',
];
} else {
return [
'#!/bin/bash',
- 'mkdir actions-runner && cd actions-runner',
- 'case $(uname -m) in aarch64) ARCH="arm64" ;; amd64|x86_64) ARCH="x64" ;; esac && export RUNNER_ARCH=${ARCH}',
- 'curl -O -L https://github.com/actions/runner/releases/download/v2.299.1/actions-runner-linux-${RUNNER_ARCH}-2.299.1.tar.gz',
- 'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-2.299.1.tar.gz',
+ 'cd /opt && mkdir actions-runner && cd actions-runner',
+ 'case $(uname -m) in aarch64|arm64) ARCH="arm64" ;; amd64|x86_64) ARCH="x64" ;; esac && export RUNNER_ARCH=${ARCH}',
+ 'case $(uname -a) in Darwin*) OS="osx" ;; Linux*) OS="linux" ;; esac && export RUNNER_OS=${OS}',
+ 'export VERSION="2.303.0"',
+ 'curl -O -L https://github.com/actions/runner/releases/download/v${VERSION}/actions-runner-${RUNNER_OS}-${RUNNER_ARCH}-${VERSION}.tar.gz',
+ 'export LC_ALL=en_US.UTF-8',
+ 'export LANG=en_EN.UTF-8',
+ 'tar xzf ./actions-runner-${RUNNER_OS}-${RUNNER_ARCH}-${VERSION}.tar.gz',
'export RUNNER_ALLOW_RUNASROOT=1',
- `./config.sh --url https://github.com/${config.githubContext.owner}/${config.githubContext.repo} --token ${githubRegistrationToken} --labels ${label}`,
+ `./config.sh --url ${config.github.url} --token ${githubRegistrationToken} --labels ${label} --name ${label} --runnergroup default --work $(pwd) --replace`,
'./run.sh',
];
}
@@ -45,6 +49,13 @@ async function startEc2Instance(label, githubRegistrationToken) {
TagSpecifications: config.tagSpecifications,
};
+ if (config.input.hostId) {
+ params.Placement = {
+ Tenancy: 'host',
+ HostId: config.input.hostId
+ }
+ }
+
try {
const result = await ec2.runInstances(params).promise();
const ec2InstanceId = result.Instances[0].InstanceId;
diff --git a/src/config.js b/src/config.js
index 13bf86a1..d16a7f61 100644
--- a/src/config.js
+++ b/src/config.js
@@ -14,6 +14,24 @@ class Config {
ec2InstanceId: core.getInput('ec2-instance-id'),
iamRoleName: core.getInput('iam-role-name'),
runnerHomeDir: core.getInput('runner-home-dir'),
+ scope: core.getInput('scope'),
+ hostId: core.getInput('host-id'),
+ };
+
+ this.GITHUB_SCOPES = {
+ organization: {
+ url: `https://github.com/${github.context.repo.owner}`,
+ context: { owner: github.context.repo.owner },
+ apiPath: `/orgs/${github.context.repo.owner}`
+ },
+ repository: {
+ url: `https://github.com/${github.context.repo.owner}/${github.context.repo.repo}`,
+ apiPath: `/repos/${github.context.repo.owner}/${github.context.repo.repo}`,
+ context: {
+ owner: github.context.repo.owner,
+ repo: github.context.repo.repo
+ }
+ }
};
const tags = JSON.parse(core.getInput('aws-resource-tags'));
@@ -22,13 +40,10 @@ class Config {
this.tagSpecifications = [{ResourceType: 'instance', Tags: tags}, {ResourceType: 'volume', Tags: tags}];
}
- // the values of github.context.repo.owner and github.context.repo.repo are taken from
- // the environment variable GITHUB_REPOSITORY specified in "owner/repo" format and
- // provided by the GitHub Action on the runtime
- this.githubContext = {
- owner: github.context.repo.owner,
- repo: github.context.repo.repo,
- };
+ this.github = this.GITHUB_SCOPES[this.input.scope];
+ if (!this.github) {
+ throw new Error(`The 'scope' input is not valid`);
+ }
//
// validate input
@@ -55,8 +70,12 @@ class Config {
}
}
- generateUniqueLabel() {
- return Math.random().toString(36).substr(2, 5);
+ generateLabel() {
+ if (!this.input.label) {
+ return Math.random().toString(36).substr(2, 5);
+ }
+
+ return this.input.label
}
}
diff --git a/src/gh.js b/src/gh.js
index abf9af94..a8da2ff6 100644
--- a/src/gh.js
+++ b/src/gh.js
@@ -9,7 +9,7 @@ async function getRunner(label) {
const octokit = github.getOctokit(config.input.githubToken);
try {
- const runners = await octokit.paginate('GET /repos/{owner}/{repo}/actions/runners', config.githubContext);
+ const runners = await octokit.paginate(`GET ${config.github.apiPath}/actions/runners`);
const foundRunners = _.filter(runners, { labels: [{ name: label }] });
return foundRunners.length > 0 ? foundRunners[0] : null;
} catch (error) {
@@ -22,7 +22,7 @@ async function getRegistrationToken() {
const octokit = github.getOctokit(config.input.githubToken);
try {
- const response = await octokit.request('POST /repos/{owner}/{repo}/actions/runners/registration-token', config.githubContext);
+ const response = await octokit.request(`POST ${config.github.apiPath}/actions/runners/registration-token`);
core.info('GitHub Registration Token is received');
return response.data.token;
} catch (error) {
@@ -42,7 +42,7 @@ async function removeRunner() {
}
try {
- await octokit.request('DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}', _.merge(config.githubContext, { runner_id: runner.id }));
+ await octokit.request(`DELETE ${config.github.apiPath}/actions/runners/{runner_id}`, { runner_id: runner.id });
core.info(`GitHub self-hosted runner ${runner.name} is removed`);
return;
} catch (error) {
@@ -52,7 +52,7 @@ async function removeRunner() {
}
async function waitForRunnerRegistered(label) {
- const timeoutMinutes = 5;
+ const timeoutMinutes = core.getInput('timeout')
const retryIntervalSeconds = 10;
const quietPeriodSeconds = 30;
let waitSeconds = 0;
diff --git a/src/index.js b/src/index.js
index 00bc5152..1684909e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,7 +9,7 @@ function setOutput(label, ec2InstanceId) {
}
async function start() {
- const label = config.generateUniqueLabel();
+ const label = config.generateLabel();
const githubRegistrationToken = await gh.getRegistrationToken();
const ec2InstanceId = await aws.startEc2Instance(label, githubRegistrationToken);
setOutput(label, ec2InstanceId);