flowey_lib_common/
gh_workflow_id.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Gets the Github workflow id for a given commit hash
5
6use flowey::node::prelude::*;
7
8flowey_request! {
9    pub struct Request {
10        pub github_commit_hash: ReadVar<String>,
11        pub repo_path: ReadVar<PathBuf>,
12        pub pipeline_name: String,
13        pub gh_token: ReadVar<String>,
14        pub gh_workflow: WriteVar<GithubWorkflow>,
15    }
16}
17
18#[derive(Serialize, Deserialize, Clone)]
19pub struct GithubWorkflow {
20    pub id: String,
21    pub commit: String,
22}
23
24new_simple_flow_node!(struct Node);
25
26impl SimpleFlowNode for Node {
27    type Request = Request;
28
29    fn imports(ctx: &mut ImportCtx<'_>) {
30        ctx.import::<crate::use_gh_cli::Node>();
31    }
32
33    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
34        let Request {
35            repo_path,
36            github_commit_hash,
37            gh_workflow,
38            pipeline_name,
39            gh_token,
40        } = request;
41
42        let pipeline_name = pipeline_name.clone();
43
44        ctx.req(crate::use_gh_cli::Request::WithAuth(
45            crate::use_gh_cli::GhCliAuth::AuthToken(gh_token.clone()),
46        ));
47        let gh_cli = ctx.reqv(crate::use_gh_cli::Request::Get);
48
49        ctx.emit_rust_step("get action id", |ctx| {
50            let gh_workflow = gh_workflow.claim(ctx);
51            let github_commit_hash = github_commit_hash.claim(ctx);
52            let repo_path = repo_path.claim(ctx);
53            let pipeline_name = pipeline_name.clone();
54            let gh_cli = gh_cli.claim(ctx);
55
56            move |rt| {
57                let github_commit_hash = rt.read(github_commit_hash);
58                let sh = xshell::Shell::new()?;
59                let repo_path = rt.read(repo_path);
60                let gh_cli = rt.read(gh_cli);
61
62                sh.change_dir(repo_path);
63
64                // Fetches the CI build workflow id for a given commit hash
65                let get_action_id = |commit: String| -> Option<String> {
66                    let output = xshell::cmd!(
67                        sh,
68                        "{gh_cli} run list
69                        --commit {commit}
70                        -w {pipeline_name}
71                        -s completed
72                        -L 1
73                        --json databaseId
74                        --jq .[].databaseId"
75                    )
76                    .read();
77
78                    match output {
79                        Ok(output) if output.trim().is_empty() => None,
80                        Ok(output) => Some(output),
81                        Err(e) => {
82                            println!("Failed to get action id for commit {}: {}", commit, e);
83                            None
84                        }
85                    }
86                };
87
88                let mut github_commit_hash = github_commit_hash.clone();
89                let mut action_id = get_action_id(github_commit_hash.clone());
90                let mut loop_count = 0;
91
92                // CI may not have finished the build for the merge base, so loop through commits
93                // until we find a finished build or fail after 5 attempts
94                while action_id.is_none() {
95                    println!(
96                        "Unable to get action id for commit {}, trying again",
97                        github_commit_hash
98                    );
99
100                    if loop_count > 4 {
101                        anyhow::bail!("Failed to get action id after 5 attempts");
102                    }
103
104                    github_commit_hash =
105                        xshell::cmd!(sh, "git rev-parse {github_commit_hash}^").read()?;
106                    action_id = get_action_id(github_commit_hash.clone());
107
108                    loop_count += 1;
109                }
110
111                // We have an action id or we would've bailed in the loop above
112                let id = action_id.context("failed to get action id")?;
113
114                println!("Got action id {id}, commit {github_commit_hash}");
115                rt.write(
116                    gh_workflow,
117                    &GithubWorkflow {
118                        id,
119                        commit: github_commit_hash,
120                    },
121                );
122
123                Ok(())
124            }
125        });
126
127        Ok(())
128    }
129}