diff --git a/gitlab-runner-mock/src/lib.rs b/gitlab-runner-mock/src/lib.rs index 1aff6f6..b0e25e6 100644 --- a/gitlab-runner-mock/src/lib.rs +++ b/gitlab-runner-mock/src/lib.rs @@ -63,7 +63,7 @@ impl GitlabRunnerMock { }; let inner = Inner { server: m, - runner_token: "fakerunnertoken".to_string(), + runner_token: "glrt-fakerunnertoken".to_string(), jobs: Mutex::new(jobs), update_interval: Mutex::new(3), expected_metadata: Mutex::new(ExpectedMetadata::default()), diff --git a/gitlab-runner/src/client.rs b/gitlab-runner/src/client.rs index 365583d..5c71e19 100644 --- a/gitlab-runner/src/client.rs +++ b/gitlab-runner/src/client.rs @@ -22,6 +22,7 @@ where const GITLAB_TRACE_UPDATE_INTERVAL: &str = "X-GitLab-Trace-Update-Interval"; const JOB_STATUS: &str = "Job-Status"; +const SHORT_TOKEN_LENGTH: usize = 9; #[derive(Debug, Default, Clone, Serialize)] struct FeaturesInfo { @@ -290,6 +291,28 @@ pub(crate) struct Client { metadata: ClientMetadata, } +// Reimplementation of gitlab-runners `ShortenToken` function. +// This returns a view into the token without the standard prefixes, and shortened to 9 characters. +// See: https://gitlab.com/gitlab-org/gitlab-runner/-/blob/654132dc91a40f80a4fd5bb290a18a13b7064aa0/helpers/shorten_token.go +fn shorten_token + ?Sized>(token: &T) -> &str { + let token = token.as_ref(); + + // Match and remove ^glrt- + let token = token.strip_prefix("glrt-").unwrap_or(token); + + // Match and remove ^t[123]_ + let token = token + .strip_prefix('t') + .and_then(|t| t.strip_prefix(|c| ('1'..='3').contains(&c))) + .and_then(|t| t.strip_prefix('_')) + .unwrap_or(token); + + // Match and remove ^glrtr- + let token = token.strip_prefix("glrtr-").unwrap_or(token); + + token.get(..SHORT_TOKEN_LENGTH).unwrap_or(token) +} + impl Client { pub fn new(url: Url, token: String, system_id: String, metadata: ClientMetadata) -> Self { Self { @@ -301,6 +324,33 @@ impl Client { } } + pub fn get_app_version_line(&self) -> String { + format!( + "{} {} ({})", + self.metadata + .platform + .as_deref() + .unwrap_or("unknown gitlab-runner-rs runner"), + self.metadata.version.as_deref().unwrap_or("?.?.?"), + self.metadata + .revision + .as_deref() + .unwrap_or("unknown revision") + ) + } + + /// Get the runners identifier as displayed in brackets on the gitlab frontend. + /// + /// The value is the first 8 or 9 characters of the runners after stripping it's prefix, + /// it is alphanumeric and may include hyphens and underscores. + pub fn get_short_description(&self) -> &str { + shorten_token(&self.token) + } + + pub fn system_id(&self) -> &str { + &self.system_id + } + pub async fn request_job(&self) -> Result, Error> { let request = JobRequest { token: &self.token, @@ -539,6 +589,28 @@ mod test { ); } + #[test] + fn short_description_test() { + for (token, expected) in [ + // no prefix + ("short", "short"), + ("veryverylongtoken", "veryveryl"), + // partition prefix only + ("t1_t9Wkyj-HGRkqQ-VWTGAr", "t9Wkyj-HG"), + ("t2_t9Wkyj-HGRkqQ-VWTGAr", "t9Wkyj-HG"), + ("t3_t9Wkyj-HGRkqQ-VWTGAr", "t9Wkyj-HG"), + ("t4_t9Wkyj-HGRkqQ-VWTGAr", "t4_t9Wkyj"), + // glrt prefix, with and without partition prefix + ("glrt-t9Wkyj-HGRkqQ-VWTGAr", "t9Wkyj-HG"), + ("glrt-t1_t9Wkyj-HGRkqQ-VWTGAr", "t9Wkyj-HG"), + // glrtr prefix, with and without partition prefix, though the latter should never happen + ("glrtr-t9Wkyj-HGRkqQ-VWTGAr", "t9Wkyj-HG"), + ("glrtr-t1_t9Wkyj-HGRkqQ-VWTGAr", "t1_t9Wkyj"), + ] { + assert_eq!(shorten_token(token), expected); + } + } + #[tokio::test] async fn no_job() { let mock = GitlabRunnerMock::start().await; diff --git a/gitlab-runner/src/job.rs b/gitlab-runner/src/job.rs index 67114f4..df6f51c 100644 --- a/gitlab-runner/src/job.rs +++ b/gitlab-runner/src/job.rs @@ -312,6 +312,24 @@ impl Job { self.log.trace(data.as_ref()); } + /// Print the runner information to the gitlab log + pub(crate) fn output_runner_header(&self) { + outputln!("Running with {}", self.client.get_app_version_line()); + let short_description = self.client.get_short_description(); + + if !short_description.is_empty() { + let name = self + .variable("CI_RUNNER_DESCRIPTION") + .map_or_else(|| "unnamed".to_string(), |v| format!("{v}")); + outputln!( + " on {} {}, system ID: {}", + name, + short_description, + self.client.system_id() + ); + } + } + /// Get the variable matching the given key pub fn variable(&self, key: &str) -> Option> { self.response.variables.get(key).map(|v| Variable { v }) diff --git a/gitlab-runner/src/run.rs b/gitlab-runner/src/run.rs index 4cbdb2a..f17689e 100644 --- a/gitlab-runner/src/run.rs +++ b/gitlab-runner/src/run.rs @@ -30,6 +30,8 @@ where U: UploadableFile + Send + 'static, Ret: Future>, { + job.output_runner_header(); + if let Err(e) = tokio::fs::create_dir(&build_dir).await { job.trace(format!("Failed to create build dir: {e}")); return Err(()); diff --git a/gitlab-runner/tests/integration.rs b/gitlab-runner/tests/integration.rs index d160de1..1852fe1 100644 --- a/gitlab-runner/tests/integration.rs +++ b/gitlab-runner/tests/integration.rs @@ -419,7 +419,16 @@ async fn job_log() { assert!(got_job); runner.wait_for_space(1).await; assert_eq!(MockJobState::Success, job.state()); - assert_eq!(b"aa\nbb\ncc\n", job.log().as_slice()); + + let log = job.log(); + + assert!(log.ends_with(b"aa\nbb\ncc\n")); + + let header = + str::from_utf8(log.strip_suffix(b"aa\nbb\ncc\n").unwrap()).expect("Log wasn't utf8"); + + assert!(header.starts_with("Running with ")); + assert!(header.contains("on Rust runner test fakerunne,")); } .with_subscriber(subscriber) .await; diff --git a/gitlab-runner/tests/runhandler.rs b/gitlab-runner/tests/runhandler.rs index d560e94..e426225 100644 --- a/gitlab-runner/tests/runhandler.rs +++ b/gitlab-runner/tests/runhandler.rs @@ -135,7 +135,10 @@ async fn log_ping(job: &MockJob, control: &LoggerControl, mut patches: u32) -> u patches += 1; assert_eq!(job.log_patches(), patches); - assert_eq!(job.log_last(), Some(ping.as_bytes().to_vec())); + assert!( + job.log_last() + .is_some_and(|log| log.ends_with(ping.as_bytes())) + ); patches }