Skip to content

Commit 46f3d3d

Browse files
committed
circus-evaluator: pin flake evaluation to checkout commit
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I9c1f5447d4865b245b0b85fd1fde3d096a6a6964
1 parent 8b6aae3 commit 46f3d3d

5 files changed

Lines changed: 66 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/evaluator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ tokio-util.workspace = true
2525
toml.workspace = true
2626
tracing.workspace = true
2727
tracing-subscriber.workspace = true
28+
url.workspace = true
2829
uuid.workspace = true
2930

3031
# Our crates

crates/evaluator/src/eval_loop.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ async fn run_nix_and_record_builds(
420420
));
421421
let result = crate::nix::evaluate(
422422
repo_path,
423+
&eval.commit_hash,
423424
&jobset.nix_expression,
424425
jobset.flake_mode,
425426
nix_timeout,

crates/evaluator/src/nix.rs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use circus_common::{CiError, InputType, error::Result, models::JobsetInput};
44
use circus_config::EvaluatorConfig;
55
use tokio::process::Command;
66
use tokio_util::sync::CancellationToken;
7+
use url::Url;
78

89
mod eval_command;
910
mod flake_lock;
@@ -176,6 +177,7 @@ pub struct EvalResult {
176177
#[tracing::instrument(skip(config, inputs), fields(flake_mode, nix_expression))]
177178
pub async fn evaluate(
178179
repo_path: &Path,
180+
commit_hash: &str,
179181
nix_expression: &str,
180182
flake_mode: bool,
181183
timeout: Duration,
@@ -201,8 +203,16 @@ pub async fn evaluate(
201203
};
202204

203205
if flake_mode {
204-
evaluate_flake(repo_path, nix_expression, timeout, config, inputs, cancel)
205-
.await
206+
evaluate_flake(
207+
repo_path,
208+
commit_hash,
209+
nix_expression,
210+
timeout,
211+
config,
212+
inputs,
213+
cancel,
214+
)
215+
.await
206216
} else {
207217
evaluate_legacy(repo_path, nix_expression, timeout, config, inputs, cancel)
208218
.await
@@ -270,6 +280,7 @@ fn lock_derived_allowed_uris(
270280
#[tracing::instrument(skip(config, inputs))]
271281
async fn evaluate_flake(
272282
repo_path: &Path,
283+
commit_hash: &str,
273284
nix_expression: &str,
274285
timeout: Duration,
275286
config: &EvaluatorConfig,
@@ -294,7 +305,7 @@ async fn evaluate_flake(
294305
);
295306
}
296307

297-
let flake_ref = format!("{}#{effective_expr}", repo_path.display());
308+
let flake_ref = git_flake_ref(repo_path, commit_hash, &effective_expr)?;
298309

299310
tracing::debug!(flake_ref = %flake_ref, "Running evix evaluation");
300311

@@ -333,6 +344,21 @@ async fn evaluate_flake(
333344
eval_command::run_eval(evix_config, timeout, "flake", cancel).await
334345
}
335346

347+
fn git_flake_ref(
348+
repo_path: &Path,
349+
commit_hash: &str,
350+
expression: &str,
351+
) -> Result<String> {
352+
let mut url = Url::from_file_path(repo_path).map_err(|()| {
353+
CiError::NixEval(format!(
354+
"Failed to convert repository path to a file URL: {}",
355+
repo_path.display()
356+
))
357+
})?;
358+
url.query_pairs_mut().append_pair("rev", commit_hash);
359+
Ok(format!("git+{url}#{expression}"))
360+
}
361+
336362
/// Resolve all toplevels in one nix eval.
337363
async fn evaluate_all_nixos_configs(
338364
repo_path: &Path,
@@ -891,6 +917,19 @@ mod meta_tests {
891917
assert!(rewrite_nixos_config_expr("packages").is_none());
892918
}
893919

920+
#[test]
921+
fn git_flake_ref_pins_the_checked_out_commit() {
922+
assert_eq!(
923+
git_flake_ref(
924+
Path::new("/tmp/circus fixture"),
925+
"0123456789abcdef",
926+
"packages",
927+
)
928+
.unwrap(),
929+
"git+file:///tmp/circus%20fixture?rev=0123456789abcdef#packages",
930+
);
931+
}
932+
894933
#[test]
895934
fn parse_derivation_show_legacy_uses_top_level_drv_key() {
896935
let v = serde_json::json!({

crates/evaluator/tests/eval_tests.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,31 @@ use circus_config::EvaluatorConfig;
1212
use tempfile::TempDir;
1313
use tokio_util::sync::CancellationToken;
1414

15-
fn git_stage(dir: &Path) {
15+
fn git_stage(dir: &Path) -> String {
1616
for args in [
1717
&["init", "-q"][..],
1818
&["config", "user.email", "test@circus"],
1919
&["config", "user.name", "Circus Test"],
2020
&["add", "."],
21+
&["commit", "-qm", "fixture"],
2122
] {
2223
Command::new("git")
2324
.args(args)
2425
.current_dir(dir)
2526
.status()
2627
.expect("git command failed");
2728
}
29+
String::from_utf8(
30+
Command::new("git")
31+
.args(["rev-parse", "HEAD"])
32+
.current_dir(dir)
33+
.output()
34+
.expect("read fixture commit")
35+
.stdout,
36+
)
37+
.expect("fixture commit is UTF-8")
38+
.trim()
39+
.to_owned()
2840
}
2941

3042
fn permissive_config() -> EvaluatorConfig {
@@ -54,10 +66,11 @@ async fn eval_minimal_flake_returns_one_job() {
5466
}"#,
5567
)
5668
.unwrap();
57-
git_stage(dir.path());
69+
let commit = git_stage(dir.path());
5870

5971
let result = circus_evaluator::nix::evaluate(
6072
dir.path(),
73+
&commit,
6174
"packages",
6275
true,
6376
Duration::from_mins(2),
@@ -99,10 +112,11 @@ async fn eval_captures_per_attribute_errors_without_failing_fatally() {
99112
}"#,
100113
)
101114
.unwrap();
102-
git_stage(dir.path());
115+
let commit = git_stage(dir.path());
103116

104117
let result = circus_evaluator::nix::evaluate(
105118
dir.path(),
119+
&commit,
106120
"packages",
107121
true,
108122
Duration::from_mins(2),
@@ -130,10 +144,11 @@ async fn eval_fatal_parse_error_returns_cierror_nixeval() {
130144
let dir = TempDir::new().unwrap();
131145
fs::write(dir.path().join("flake.nix"), "not valid nix syntax at all")
132146
.unwrap();
133-
git_stage(dir.path());
147+
let commit = git_stage(dir.path());
134148

135149
let result = circus_evaluator::nix::evaluate(
136150
dir.path(),
151+
&commit,
137152
"packages",
138153
true,
139154
Duration::from_secs(30),
@@ -171,10 +186,11 @@ async fn eval_timeout_returns_cierror_timeout() {
171186
}",
172187
)
173188
.unwrap();
174-
git_stage(dir.path());
189+
let commit = git_stage(dir.path());
175190

176191
let result = circus_evaluator::nix::evaluate(
177192
dir.path(),
193+
&commit,
178194
"packages",
179195
true,
180196
Duration::from_millis(500),

0 commit comments

Comments
 (0)