From 7636195380b5db3b70d3b6eaf75ffc56c9f690de Mon Sep 17 00:00:00 2001 From: bahdotsh Date: Wed, 13 Aug 2025 13:21:58 +0530 Subject: [PATCH] fix: Support array format for runs-on field in GitHub Actions workflows - Add custom deserializer for runs-on field to handle both string and array formats - Update Job struct to use Vec instead of String for runs-on field - Modify executor to extract first element from runs-on array for runner selection - Add test workflow to verify both string and array formats work correctly - Maintain backwards compatibility with existing string-based workflows Fixes issue where workflows with runs-on: [self-hosted, ubuntu, small] format would fail with 'invalid type: sequence, expected a string' error. This change aligns with GitHub Actions specification which supports: - String format: runs-on: ubuntu-latest - Array format: runs-on: [self-hosted, ubuntu, small] --- crates/executor/src/engine.rs | 8 ++++++-- crates/parser/src/gitlab.rs | 2 +- crates/parser/src/workflow.rs | 24 ++++++++++++++++++++++-- tests/workflows/runs-on-array-test.yml | 18 ++++++++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 tests/workflows/runs-on-array-test.yml diff --git a/crates/executor/src/engine.rs b/crates/executor/src/engine.rs index e7a5866..1e1435f 100644 --- a/crates/executor/src/engine.rs +++ b/crates/executor/src/engine.rs @@ -1762,9 +1762,13 @@ fn get_runner_image(runs_on: &str) -> String { .to_string() } -fn get_runner_image_from_opt(runs_on: &Option) -> String { +fn get_runner_image_from_opt(runs_on: &Option>) -> String { let default = "ubuntu-latest"; - let ro = runs_on.as_deref().unwrap_or(default); + let ro = runs_on + .as_ref() + .and_then(|vec| vec.first()) + .map(|s| s.as_str()) + .unwrap_or(default); get_runner_image(ro) } diff --git a/crates/parser/src/gitlab.rs b/crates/parser/src/gitlab.rs index a2f8fb1..7bd238c 100644 --- a/crates/parser/src/gitlab.rs +++ b/crates/parser/src/gitlab.rs @@ -130,7 +130,7 @@ pub fn convert_to_workflow_format(pipeline: &Pipeline) -> workflow::WorkflowDefi // Create a new job let mut job = workflow::Job { - runs_on: Some("ubuntu-latest".to_string()), // Default runner + runs_on: Some(vec!["ubuntu-latest".to_string()]), // Default runner needs: None, steps: Vec::new(), env: HashMap::new(), diff --git a/crates/parser/src/workflow.rs b/crates/parser/src/workflow.rs index bbb4a86..58a29cb 100644 --- a/crates/parser/src/workflow.rs +++ b/crates/parser/src/workflow.rs @@ -26,6 +26,26 @@ where } } +// Custom deserializer for runs-on field that handles both string and array formats +fn deserialize_runs_on<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrVec { + String(String), + Vec(Vec), + } + + let value = Option::::deserialize(deserializer)?; + match value { + Some(StringOrVec::String(s)) => Ok(Some(vec![s])), + Some(StringOrVec::Vec(v)) => Ok(Some(v)), + None => Ok(None), + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct WorkflowDefinition { pub name: String, @@ -38,8 +58,8 @@ pub struct WorkflowDefinition { #[derive(Debug, Deserialize, Serialize)] pub struct Job { - #[serde(rename = "runs-on")] - pub runs_on: Option, + #[serde(rename = "runs-on", default, deserialize_with = "deserialize_runs_on")] + pub runs_on: Option>, #[serde(default, deserialize_with = "deserialize_needs")] pub needs: Option>, #[serde(default)] diff --git a/tests/workflows/runs-on-array-test.yml b/tests/workflows/runs-on-array-test.yml new file mode 100644 index 0000000..c3e6e26 --- /dev/null +++ b/tests/workflows/runs-on-array-test.yml @@ -0,0 +1,18 @@ +name: Test Runs-On Array Format + +on: [push] + +jobs: + test-array-runs-on: + timeout-minutes: 15 + runs-on: [self-hosted, ubuntu, small] + steps: + - name: Test step + run: echo "Testing array format for runs-on" + + test-string-runs-on: + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: Test step + run: echo "Testing string format for runs-on"