Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to the authentication token-based runner creation workflow #23

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 43 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,56 @@ async fn main() {
let mut runner = Runner::new(
"https://gitlab.example.com".try_into().unwrap(),
"runner token".to_owned(),
"system id".to_owned(),
PathBuf::from("/tmp"));
runner.run(move | _job | async move { Ok(Run{}) }, 16).await.unwrap();
}
```

## Gitlab runner registration
## Gitlab runner creation

This crate does not support registering new runners with the gitlab server, so this has to be
done by hand using the gitlab
[runner registration API](https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner).
This crate does not support creating new runners on the GitLab server. This can
be done using the
[runner creation API](https://docs.gitlab.com/ee/api/users.html#create-a-runner-linked-to-a-user),
or manually in the GitLab
[runner management web interface](https://docs.gitlab.com/ee/ci/runners/runners_scope.html).
Make sure to follow the runner creation with an authentication token workflow,
as the registration token workflow is deprecated.

One key parameter provided when creating the runner is `run_untagged=false` (or
leaving the `Run untagged jobs` box unchecked in the web interface), which will
make the runner *only* pickup jobs which matches its tags. This is important to
prevent the runner from picking up "normal" jobs which it will not be able to
process.

When the runner is created GitLab provides an authentication token starting
with `glrt-`. This token should be provided to the runner for its GitLab
connection, along with a system ID that identifies the machine on which the
runner is executed.

The system ID should be a unique string. GitLab doesn't currently require any
particular formatting, but it is recommended to follow the way the official
`gitlab-runner` creates system IDs:
Comment on lines +65 to +67
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crate should probably be the one creating this system_id rather then requiring the user (either end of runner implementation) to create it themselves;

In our typical setup we run these runners in kubernetes where it should really be unique per container instance.

I do wonder how/if gitlab garbage collects instances

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine creating it in the gitlab-runner-rs crate. That gets beyond my very limited rust capabilities though, would you be able to give it a go ? The logic from the official gitlab-runner can be found in https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/common/system_id_state.go?ref_type=heads#L89

According to https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens/#ci_runner_machines-record-lifetime, entries in the ci_runner_machines table are automatically cleaned 7 days after the last contact from the respective runner. That page also explains how the system ID is stored in a .runner_system_id file, separate from the main runner configuration file.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointers; was just digging through the gitlab code and it's indeed implement as mentioned (once an hour it cleans out all stale machines so 7 days or older). Also as you mentioned gitlab doesn't require a specific format, but it will check the id is less then 64 characters

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw yeah happy to add the autogeneration; should be pretty simple

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pinchartl did the generation in PR #24 as it now happens behind the scenes most of your changes were dropped; but i did pick up your documentation change as a seperate patch with you as the author :)


- Deriving it from the machine ID, found in `/var/lib/dbus/machine-id` or
`/etc/machine-id`, but concatenating the machine ID with the string
"gitlab-runner", taking the first 12 characters of its SHA256 hash in hex
form, and prepending it with `s_`.

- Generating a random 12-character string using letters and digits
(`[a-z][A-Z][0-9]`), and prepending it with `r_`.

In either case the system ID should be recorded in a persistent storage, along
with the authentication token, and be passed to the `Runner::new()` function.

The token can be verified using a curl command like:

The registration token can be retrieved from the runners section in the Gitlab
administration area. With that token the runner can be register using a curl
command like:
```shell
curl --request POST "https://GITLAB_URL/api/v4/runners" \
--form "description=My custom runner" \
--form "run_untagged=false" \
--form "tag_list=custom-gitlab-runner" \
--form "token=REGISTRATION_TOKEN"
curl --request POST "https://GITLAB_URL/api/v4/runners/verify" \
--form "token=AUTHENTICATION_TOKEN" \
--form "system_id=SYSTEM_ID"
```

As a response to this command a new token for the registered runner will be
provided, this token should be provided to the runner for it's gitlab
connection.

One thing to key parameter provided here is `run_untagged=false`, which will
make the runner *only* pickup jobs which matches its tag. This is important to
prevent the runner from picking up "normal" jobs which it will not be able to
process.
This step is optional. If performed, it will pre-register the system ID with
the GitLab server. Otherwise the system ID will be registered the first time
the runner pings for jobs.
6 changes: 6 additions & 0 deletions gitlab-runner-mock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct JobData {
struct Inner {
server: MockServer,
runner_token: String,
system_id: String,
jobs: Mutex<JobData>,
update_interval: Mutex<u32>,
}
Expand All @@ -48,6 +49,7 @@ impl GitlabRunnerMock {
let inner = Inner {
server: m,
runner_token: "fakerunnertoken".to_string(),
system_id: "r_0123456789ab".to_string(),
jobs: Mutex::new(jobs),
update_interval: Mutex::new(3),
};
Expand Down Expand Up @@ -98,6 +100,10 @@ impl GitlabRunnerMock {
&self.inner.runner_token
}

pub fn runner_system_id(&self) -> &str {
&self.inner.system_id
}

pub fn add_dummy_job(&self, name: String) -> MockJob {
let mut jobs = self.inner.jobs.lock().unwrap();
jobs.last_id += 1;
Expand Down
9 changes: 7 additions & 2 deletions gitlab-runner/examples/demo-runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use url::Url;
struct Opts {
server: Url,
token: String,
system_id: String,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -189,8 +190,12 @@ async fn main() {
let opts = Opts::from_args();
let dir = tempfile::tempdir().unwrap();

let (mut runner, layer) =
Runner::new_with_layer(opts.server, opts.token, dir.path().to_path_buf());
let (mut runner, layer) = Runner::new_with_layer(
opts.server,
opts.token,
opts.system_id,
dir.path().to_path_buf(),
);

tracing_subscriber::Registry::default()
.with(
Expand Down
18 changes: 15 additions & 3 deletions gitlab-runner/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ struct VersionInfo {
#[derive(Debug, Clone, Serialize)]
struct JobRequest<'a> {
token: &'a str,
system_id: &'a str,
info: VersionInfo,
}

Expand Down Expand Up @@ -275,20 +276,23 @@ pub(crate) struct Client {
client: reqwest::Client,
url: Url,
token: String,
system_id: String,
}

impl Client {
pub fn new(url: Url, token: String) -> Self {
pub fn new(url: Url, token: String, system_id: String) -> Self {
Self {
client: reqwest::Client::new(),
url,
token,
system_id,
}
}

pub async fn request_job(&self) -> Result<Option<JobResponse>, Error> {
let request = JobRequest {
token: &self.token,
system_id: &self.system_id,
info: VersionInfo {
// Setting `refspecs` is required to run detached MR pipelines.
features: FeaturesInfo {
Expand Down Expand Up @@ -525,7 +529,11 @@ mod test {
async fn no_job() {
let mock = GitlabRunnerMock::start().await;

let client = Client::new(mock.uri(), mock.runner_token().to_string());
let client = Client::new(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
);

let job = client.request_job().await.unwrap();

Expand All @@ -537,7 +545,11 @@ mod test {
let mock = GitlabRunnerMock::start().await;
mock.add_dummy_job("process job".to_string());

let client = Client::new(mock.uri(), mock.runner_token().to_string());
let client = Client::new(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
);

if let Some(job) = client.request_job().await.unwrap() {
client
Expand Down
20 changes: 14 additions & 6 deletions gitlab-runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
//! let mut runner = Runner::new(
//! "https://gitlab.example.com".try_into().unwrap(),
//! "runner token".to_owned(),
//! "runner system id".to_owned(),
//! PathBuf::from("/tmp"));
//! runner.run(move | _job | async move { Ok(Run{}) }, 16).await.unwrap();
//! }
Expand Down Expand Up @@ -275,8 +276,8 @@ pub struct Runner {
}

impl Runner {
/// Create a new Runner for the given server url and runner token, storing (temporary job
/// files) in build_dir
/// Create a new Runner for the given server url, runner token and system ID, storing
/// (temporary job files) in build_dir
///
/// The build_dir is used to store temporary files during a job run. This will also configure a
/// default tracing subscriber if that's not wanted use [`Runner::new_with_layer`] instead.
Expand All @@ -288,14 +289,15 @@ impl Runner {
/// let dir = tempfile::tempdir().unwrap();
/// let runner = Runner::new(Url::parse("https://gitlab.com/").unwrap(),
/// "RunnerToken".to_string(),
/// "RunnerSystemID".to_string(),
/// dir.path().to_path_buf());
/// ```
///
/// # Panics
///
/// Panics if a default subscriber is already setup
pub fn new(server: Url, token: String, build_dir: PathBuf) -> Self {
let (runner, layer) = Self::new_with_layer(server, token, build_dir);
pub fn new(server: Url, token: String, system_id: String, build_dir: PathBuf) -> Self {
let (runner, layer) = Self::new_with_layer(server, token, system_id, build_dir);
Registry::default().with(layer).init();

runner
Expand All @@ -314,11 +316,17 @@ impl Runner {
/// let dir = tempfile::tempdir().unwrap();
/// let (runner, layer) = Runner::new_with_layer(Url::parse("https://gitlab.com/").unwrap(),
/// "RunnerToken".to_string(),
/// "RunnerSystemID".to_string(),
/// dir.path().to_path_buf());
/// let subscriber = Registry::default().with(layer).init();
/// ```
pub fn new_with_layer(server: Url, token: String, build_dir: PathBuf) -> (Self, GitlabLayer) {
let client = Client::new(server, token);
pub fn new_with_layer(
server: Url,
token: String,
system_id: String,
build_dir: PathBuf,
) -> (Self, GitlabLayer) {
let client = Client::new(server, token, system_id);
let run_list = RunList::new();
(
Self {
Expand Down
2 changes: 2 additions & 0 deletions gitlab-runner/tests/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ async fn upload_download() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -214,6 +215,7 @@ async fn multiple_upload() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down
12 changes: 12 additions & 0 deletions gitlab-runner/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ async fn job_success() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand All @@ -300,6 +301,7 @@ async fn job_fail() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand All @@ -326,6 +328,7 @@ async fn job_panic() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -356,6 +359,7 @@ async fn job_cancel_step() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_owned(),
mock.runner_system_id().to_owned(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -386,6 +390,7 @@ async fn job_log() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -432,6 +437,7 @@ async fn job_steps() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -466,6 +472,7 @@ async fn job_parallel() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -523,6 +530,7 @@ async fn runner_run() {
let (mut r, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -582,6 +590,7 @@ async fn runner_limit() {
let (mut r, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -642,6 +651,7 @@ async fn runner_delay() {
let (mut r, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -707,6 +717,7 @@ async fn job_variables() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down Expand Up @@ -748,6 +759,7 @@ async fn job_drain() {
let (mut runner, layer) = Runner::new_with_layer(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down
1 change: 1 addition & 0 deletions gitlab-runner/tests/runhandler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ async fn update_interval() {
let mut runner = Runner::new(
mock.uri(),
mock.runner_token().to_string(),
mock.runner_system_id().to_string(),
dir.path().to_path_buf(),
);

Expand Down
Loading