Skip to content
Open
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
64 changes: 64 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions gitlab-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ rand = "0.10.1"
tokio-util = { version = "0.7.18", features = [ "io" ] }
tokio-retry2 = { version = "0.9.1", features = ["jitter"] }
normalize-path = "0.2.1"
shellexpand = "3.1.2"
shell-words = "1.1.1"

[dev-dependencies]
tokio = { version = "1.50.0", features = [ "full", "test-util" ] }
Expand Down
10 changes: 10 additions & 0 deletions gitlab-runner/examples/demo-runner.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::io::{IsTerminal, Read};

use anyhow::{Context, Result};
Expand Down Expand Up @@ -123,6 +124,15 @@ impl Run {
}
Ok(())
}
"echo" => {
let mut expanding = HashSet::new();
let expanded = p
.map(move |l| self.job.expand_vars_inner(l, true, true, &mut expanding))
.collect::<Vec<_>>()
.join(" ");
outputln!("{}", expanded);
Ok(())
}
_ => {
outputln!("Unknown command\n");
Err(())
Expand Down
56 changes: 55 additions & 1 deletion gitlab-runner/src/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use crate::artifact::Artifact;
use crate::client::{Client, JobArtifactFile, JobDependency, JobResponse, JobVariable};
use crate::outputln;
use bytes::{Bytes, BytesMut};
use std::collections::HashMap;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use tokio::io::AsyncWrite;
Expand Down Expand Up @@ -34,6 +35,15 @@ impl<'a> Variable<'a> {
&self.v.value
}

/// Return the value of the variable or "<MASKED>"
pub fn masked_value(&self) -> &'a str {
if self.masked() {
"<MASKED>"
} else {
&self.v.value
}
}

/// Whether or not the variable is masked
pub fn masked(&self) -> bool {
self.v.masked
Expand Down Expand Up @@ -333,6 +343,50 @@ impl Job {
})
}

/// Returns `line` with variables expanded.
///
/// When `quote` is `true` variables are quoted and special characters are escaped to retain their literal meaning in Unix shell syntax.
/// When `mask` is `true` the masked variables are replaced making the output suitable for logging.
///
/// Recursive variable expand to empty strings
pub fn expand_vars<'s>(&self, line: &'s str, quote: bool, mask: bool) -> Cow<'s, str> {
self.expand_vars_inner(line, quote, mask, &mut HashSet::new())
}

/// See [`Job::expand_vars`]
///
/// This function must be supplied with an empty `HashMap<String>` that is used to track variables to avoid infinite loops.
pub fn expand_vars_inner<'s>(
&self,
line: &'s str,
quote: bool,
mask: bool,
expanding: &mut HashSet<String>,
) -> Cow<'s, str> {
shellexpand::env_with_context_no_errors(line, |var| {
if !expanding.insert(var.to_owned()) {
return Some("".into());
}

let value =
self.variable(var).map_or(
"",
|v: Variable<'_>| if mask { v.masked_value() } else { v.value() },
);

let expanded = self.expand_vars_inner(value, false, mask, expanding);
expanding.remove(var);
Some(
if quote {
shell_words::quote(expanded.as_ref())
} else {
expanded
}
.into_owned(),
)
})
}

/// Get a reference to the jobs build dir.
pub fn build_dir(&self) -> &Path {
&self.build_dir
Expand Down
95 changes: 90 additions & 5 deletions gitlab-runner/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use gitlab_runner::{GitlabLayer, JobHandler, JobResult, Phase, Runner, RunnerBui
use gitlab_runner_mock::{
GitlabRunnerMock, MockJob, MockJobState, MockJobStepName, MockJobStepWhen,
};
use std::collections::HashSet;
use std::fmt::Debug;
use std::future::Future;
use std::sync::{Arc, Mutex};
Expand Down Expand Up @@ -670,8 +671,12 @@ async fn runner_delay() {

#[tokio::test]
async fn job_variables() {
const TEST_VARIABLE: &str = "TEST_VARIABLE";
const TEST_VALUE: &str = "a testing value";
const TEST_VARIABLE: (&str, &str) = ("TEST_VARIABLE", "a testing value");
const TEST_EXPAND_VARIABLE: (&str, &str) = ("TEST_EXPAND_VARIABLE", "$TEST_VARIABLE");
const TEST_EXPAND_TWICE_VARIABLE: (&str, &str) =
("TEST_EXPAND_TWICE_VARIABLE", "$TEST_EXPAND_VARIABLE");
const TEST_RECURSIVE_VARIABLE: (&str, &str) =
("TEST_RECURSIVE_VARIABLE", "$TEST_RECURSIVE_VARIABLE");

let mock = GitlabRunnerMock::start().await;
let mut builder = mock.job_builder("variables".to_string());
Expand All @@ -683,7 +688,30 @@ async fn job_variables() {
MockJobStepWhen::OnSuccess,
false,
);
builder.add_variable(TEST_VARIABLE.to_owned(), TEST_VALUE.to_owned(), false, true);
builder.add_variable(
TEST_VARIABLE.0.to_owned(),
TEST_VARIABLE.1.to_owned(),
false,
true,
);
builder.add_variable(
TEST_EXPAND_VARIABLE.0.to_owned(),
TEST_EXPAND_VARIABLE.1.to_owned(),
false,
true,
);
builder.add_variable(
TEST_EXPAND_TWICE_VARIABLE.0.to_owned(),
TEST_EXPAND_TWICE_VARIABLE.1.to_owned(),
false,
false,
);
builder.add_variable(
TEST_RECURSIVE_VARIABLE.0.to_owned(),
TEST_RECURSIVE_VARIABLE.1.to_owned(),
false,
false,
);

let job = builder.build();
mock.enqueue_job(job.clone());
Expand All @@ -697,11 +725,68 @@ async fn job_variables() {
assert!(id.public());
assert!(!id.masked());

let test = job.variable(TEST_VARIABLE).unwrap();
assert_eq!(test.value(), TEST_VALUE);
let test = job.variable(TEST_VARIABLE.0).unwrap();
assert_eq!(test.value(), TEST_VARIABLE.1);
assert!(!test.public());
assert!(test.masked());

assert_eq!(
job.expand_vars(
format!("${}", TEST_EXPAND_VARIABLE.0).as_str(),
false,
false
),
TEST_VARIABLE.1,
);
assert_eq!(
job.expand_vars(format!("${}", TEST_EXPAND_VARIABLE.0).as_str(), true, false),
format!("'{}'", TEST_VARIABLE.1),
);
assert_eq!(
job.expand_vars(
format!("${{{}}}", TEST_EXPAND_VARIABLE.0).as_str(),
false,
false
),
TEST_VARIABLE.1,
);
assert_eq!(
job.expand_vars(
format!("${}", TEST_EXPAND_TWICE_VARIABLE.0).as_str(),
false,
false,
),
TEST_VARIABLE.1,
);
assert_eq!(
job.expand_vars(
format!("prefix-${}-suffix", TEST_EXPAND_TWICE_VARIABLE.0).as_str(),
false,
true,
),
"prefix-<MASKED>-suffix",
);
assert_eq!(
job.expand_vars(
format!("${}", TEST_RECURSIVE_VARIABLE.0).as_str(),
false,
true,
),
"",
);
let mut expanding = HashSet::new();
assert_eq!(
format!(
"${} ${}",
TEST_EXPAND_TWICE_VARIABLE.0, TEST_EXPAND_VARIABLE.0
)
.split_whitespace()
.map(|l| job.expand_vars_inner(l, true, false, &mut expanding,))
.collect::<Vec<_>>()
.join(" "),
"'a testing value' 'a testing value'",
);

SimpleRun::dummy(Ok(())).await
})
.await
Expand Down