Skip to content

Commit 0f25f06

Browse files
fix(compile): remove empty env block from executor step when no write permissions (#407)
* fix(compile): remove empty env block from executor step when no write permissions Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/e4d2855e-c0f7-4fc9-8214-5b9942c995dd Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> * fix(test): correct misleading comment about serde_yaml and bare env: validity Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/42b1fb99-3470-483e-acc3-99d1f8299528 Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent 8ddfa95 commit 0f25f06

5 files changed

Lines changed: 70 additions & 8 deletions

File tree

docs/template-markers.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,9 @@ If `permissions.write` is not configured, this marker is replaced with an empty
444444

445445
## {{ executor_ado_env }}
446446

447-
Generates environment variable entries for the Stage 3 executor step when `permissions.write` is configured. Sets `SYSTEM_ACCESSTOKEN` to the write service connection token (`SC_WRITE_TOKEN`).
447+
Generates the complete `env:` block (including the `env:` key) for the Stage 3 executor step when `permissions.write` is configured. Sets `SYSTEM_ACCESSTOKEN` to the write service connection token (`SC_WRITE_TOKEN`).
448448

449-
If `permissions.write` is not configured, this marker is replaced with an empty string. Note: `System.AccessToken` is never used directly — all ADO tokens come from explicitly configured service connections.
449+
If `permissions.write` is not configured, this marker is replaced with an empty string so that no `env:` block is emitted at all. Note: `System.AccessToken` is never used directly — all ADO tokens come from explicitly configured service connections.
450450

451451
## {{ compiler_version }}
452452

src/compile/common.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,10 @@ pub fn generate_acquire_ado_token(service_connection: Option<&str>, variable_nam
914914
/// When not configured, omits ADO access tokens entirely.
915915
pub fn generate_executor_ado_env(write_service_connection: Option<&str>) -> String {
916916
match write_service_connection {
917-
Some(_) => "SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)".to_string(),
917+
// The two-space indent on the value line is the YAML relative indent for a
918+
// key nested under `env:`. replace_with_indent prepends the base indentation
919+
// from the marker's position in the template to each subsequent line.
920+
Some(_) => "env:\n SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)".to_string(),
918921
None => String::new(),
919922
}
920923
}
@@ -3741,6 +3744,10 @@ mod tests {
37413744
#[test]
37423745
fn test_generate_executor_ado_env_with_connection() {
37433746
let result = generate_executor_ado_env(Some("my-sc"));
3747+
assert!(
3748+
result.contains("env:"),
3749+
"Executor env block should include the 'env:' key"
3750+
);
37443751
assert!(
37453752
result.contains("SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)"),
37463753
"Executor should use SC_WRITE_TOKEN"
@@ -3756,7 +3763,7 @@ mod tests {
37563763
fn test_generate_executor_ado_env_none_empty() {
37573764
assert!(
37583765
generate_executor_ado_env(None).is_empty(),
3759-
"None service connection should produce empty env block"
3766+
"None service connection should produce empty string (no env block)"
37603767
);
37613768
}
37623769

src/data/1es-base.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,8 +662,7 @@ extends:
662662
exit $EXIT_CODE
663663
displayName: Execute safe outputs (Stage 3)
664664
workingDirectory: {{ working_directory }}
665-
env:
666-
{{ executor_ado_env }}
665+
{{ executor_ado_env }}
667666
668667
- bash: |
669668
# Copy all logs to output directory for artifact upload

src/data/base.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,8 +630,7 @@ jobs:
630630
exit $EXIT_CODE
631631
displayName: Execute safe outputs (Stage 3)
632632
workingDirectory: {{ working_directory }}
633-
env:
634-
{{ executor_ado_env }}
633+
{{ executor_ado_env }}
635634
636635
- bash: |
637636
# Copy all logs to output directory for artifact upload

tests/compiler_tests.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3481,3 +3481,60 @@ fn test_pr_filter_gate_steps_nested_in_setup_job() {
34813481
});
34823482
assert!(has_download, "Download step should be inside Setup job's steps list");
34833483
}
3484+
3485+
/// Test that a pipeline without `permissions.write` does not emit a bare `env:` block
3486+
/// on the "Execute safe outputs" step (i.e. no invalid empty-mapping YAML).
3487+
#[test]
3488+
fn test_executor_step_no_empty_env_block_without_write_permissions() {
3489+
// minimal-agent.md has no permissions.write — executor env must be absent
3490+
let compiled = compile_fixture("minimal-agent.md");
3491+
assert_valid_yaml(&compiled, "minimal-agent.md");
3492+
3493+
// Verify the executor step contains no `env:` key at all.
3494+
// Note: a bare `env:` with no children is valid YAML (parsed as null), so
3495+
// assert_valid_yaml alone would not catch the regression this test guards against.
3496+
let execute_block_start = compiled
3497+
.find("Execute safe outputs (Stage 3)")
3498+
.expect("Should have executor step");
3499+
// Find the next step after the executor step (to bound our search window)
3500+
let after_execute = &compiled[execute_block_start..];
3501+
let next_step_offset = after_execute[1..]
3502+
.find("- bash:")
3503+
.map(|i| i + 1)
3504+
.unwrap_or(after_execute.len());
3505+
let executor_step_text = &after_execute[..next_step_offset];
3506+
3507+
assert!(
3508+
!executor_step_text.contains("env:"),
3509+
"Executor step should not contain an 'env:' block when write permissions are absent: {executor_step_text}"
3510+
);
3511+
}
3512+
3513+
/// Test that a pipeline with `permissions.write` emits a correctly-indented `env:` block
3514+
/// on the "Execute safe outputs" step.
3515+
#[test]
3516+
fn test_executor_step_has_env_block_with_write_permissions() {
3517+
// complete-agent.md has permissions.write configured
3518+
let compiled = compile_fixture("complete-agent.md");
3519+
assert_valid_yaml(&compiled, "complete-agent.md");
3520+
3521+
assert!(
3522+
compiled.contains("SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)"),
3523+
"Executor step should have SYSTEM_ACCESSTOKEN when write permissions are configured: {compiled}"
3524+
);
3525+
3526+
// Verify the env key and value appear in the correct block by checking both
3527+
// are present in the same neighbourhood.
3528+
let execute_block_start = compiled
3529+
.find("Execute safe outputs (Stage 3)")
3530+
.expect("Should have executor step");
3531+
let after_execute = &compiled[execute_block_start..];
3532+
assert!(
3533+
after_execute.contains("env:"),
3534+
"Executor step should contain 'env:' key after the displayName: {after_execute}"
3535+
);
3536+
assert!(
3537+
after_execute.contains("SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)"),
3538+
"Executor step should include SYSTEM_ACCESSTOKEN: {after_execute}"
3539+
);
3540+
}

0 commit comments

Comments
 (0)