Skip to content

Commit

Permalink
Auto merge of #20 - troy/cancel-actions, r=TroyKomodo
Browse files Browse the repository at this point in the history
cancel workflow runs when brawl cancel
when brawl cancel is triggers, we cancel any github action workflows on the branch where the commit is the commit of the run.

BRAWL-10

Requested-by: TroyKomodo <[email protected]>
  • Loading branch information
scuffle-brawl[bot] authored Dec 26, 2024
2 parents 602b966 + fa3b10f commit 3b1e022
Show file tree
Hide file tree
Showing 5 changed files with 684 additions and 46 deletions.
237 changes: 194 additions & 43 deletions server/src/github/merge_workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,47 +618,30 @@ impl GitHubMergeWorkflow for DefaultMergeWorkflow {
);

// TODO: cancel runs on github actions
if let Some(_run_commit_sha) = &run.run_commit_sha {
// let page = repo
// .client()
// .workflows(repo.owner.login.clone(), repo.name.clone())
// .list_all_runs()
// .branch(self.ci_branch.clone())
// .per_page(100)
// .page(1u32)
// .send()
// .await?;

// let mut total_workflows = page.items;

// while let Some(page) = client.client().get_page(&page.next).await? {
// total_workflows.extend(page.items);
// }

// for workflow in total_workflows.into_iter().filter(|w| w.head_sha ==
// run_commit_sha) { if workflow.conclusion.is_none() {
// client
// .client()
// .post::<_, serde_json::Value>(
// format!(
// "/repos/{owner}/{repo}/actions/runs/{id}/cancel",
// owner = repo_owner.login,
// repo = repo.name,
// id = workflow.id
// ),
// None::<&()>,
// )
// .await?;
// tracing::info!(
// run_id = %run_id,
// repo_id = %run.github_repo_id,
// "cancelled workflow {id} on https://github.com/{owner}/{repo}/actions/runs/{id}",
// owner = repo_owner.login,
// repo = repo.name,
// id = workflow.id
// );
// }
// }
if let Some(run_commit_sha) = &run.run_commit_sha {
let runs = repo.branch_workflows(&run.ci_branch).await.context("get workflows")?;

for workflow in runs.into_iter().filter(|w| &w.head_sha == run_commit_sha) {
if workflow.conclusion.is_none() {
if let Err(err) = repo.cancel_workflow_run(workflow.id).await {
tracing::error!(
run_id = %run.id,
repo_id = %run.github_repo_id,
url = repo.workflow_run_link(workflow.id),
pr = repo.pr_link(run.github_pr_number as u64),
"failed to cancel workflow: {err:#}",
);
} else {
tracing::info!(
run_id = %run.id,
repo_id = %run.github_repo_id,
url = repo.workflow_run_link(workflow.id),
pr = repo.pr_link(run.github_pr_number as u64),
"cancelled workflow",
);
}
}
}

repo.delete_branch(&run.ci_branch).await?;
}
Expand Down Expand Up @@ -702,14 +685,14 @@ mod tests {
use std::time::Duration;

use chrono::Utc;
use octocrab::models::RepositoryId;
use octocrab::models::{RepositoryId, RunId};
use tokio::sync::mpsc;

use super::*;
use crate::database::ci_run::{InsertCiRun, UpdateCiRun};
use crate::database::enums::{GithubPrMergeStatus, GithubPrStatus};
use crate::github::config::{GitHubBrawlLabelsConfig, GitHubBrawlRepoConfig};
use crate::github::models::{CheckRunConclusion, CheckRunEvent, CheckRunStatus, Commit};
use crate::github::models::{CheckRunConclusion, CheckRunEvent, CheckRunStatus, Commit, WorkflowRun};
use crate::github::repo::test_utils::{MockRepoAction, MockRepoClient};

#[bon::builder]
Expand Down Expand Up @@ -2439,6 +2422,174 @@ mod tests {
conn
});

match rx.recv().await.unwrap() {
MockRepoAction::BranchWorkflows { branch, result } => {
assert_eq!(branch, "ci_branch");
result
.send(Ok(vec![
WorkflowRun {
id: RunId(1),
conclusion: None,
head_sha: "run_commit_sha".to_string(),
..Default::default()
},
WorkflowRun {
id: RunId(2),
conclusion: None,
head_sha: "not_run_commit_sha".to_string(),
..Default::default()
},
]))
.unwrap();
}
r => panic!("unexpected action: {:?} expected branch", r),
}

match rx.recv().await.unwrap() {
MockRepoAction::CancelWorkflowRun { run_id, result } => {
assert_eq!(run_id, RunId(1));
result.send(Ok(())).unwrap();
}
r => panic!("unexpected action: {:?} expected cancel workflow run", r),
}

match rx.recv().await.unwrap() {
MockRepoAction::DeleteBranch { branch, result } => {
assert_eq!(branch, "ci_branch");
result.send(Ok(())).unwrap();
}
r => panic!("unexpected action: {:?} expected delete branch", r),
}

let mut conn = task.await.unwrap();

let run = CiRun::latest(RepositoryId(1), 1).get_result(&mut conn).await.unwrap();
assert_eq!(run.status, GithubCiRunStatus::Cancelled);
assert!(run.completed_at.is_some());
assert!(run.run_commit_sha.is_some());
assert!(run.started_at.is_some());
}

#[tokio::test]
async fn test_ci_run_cancel_started_no_runs() {
let (mut conn, client, pr, run, mut rx) = ci_run_test_boilerplate(
InsertCiRun::builder(1, 1)
.base_ref(Base::Commit(Cow::Borrowed("sha")))
.head_commit_sha(Cow::Borrowed("head_commit_sha"))
.ci_branch(Cow::Borrowed("ci_branch"))
.is_dry_run(false)
.requested_by_id(1)
.approved_by_ids(vec![1])
.build(),
)
.await;

UpdateCiRun::builder(run.id)
.status(GithubCiRunStatus::InProgress)
.started_at(Utc::now())
.run_commit_sha(Cow::Borrowed("run_commit_sha"))
.build()
.query()
.execute(&mut conn)
.await
.unwrap();

let run = CiRun::latest(RepositoryId(1), 1).get_result(&mut conn).await.unwrap();

let task = tokio::spawn(async move {
client.merge_workflow().cancel(&run, &client, &mut conn, &pr).await.unwrap();

conn
});

match rx.recv().await.unwrap() {
MockRepoAction::BranchWorkflows { branch, result } => {
assert_eq!(branch, "ci_branch");
result.send(Ok(vec![])).unwrap();
}
r => panic!("unexpected action: {:?} expected branch", r),
}

match rx.recv().await.unwrap() {
MockRepoAction::DeleteBranch { branch, result } => {
assert_eq!(branch, "ci_branch");
result.send(Ok(())).unwrap();
}
r => panic!("unexpected action: {:?} expected delete branch", r),
}

let mut conn = task.await.unwrap();

let run = CiRun::latest(RepositoryId(1), 1).get_result(&mut conn).await.unwrap();
assert_eq!(run.status, GithubCiRunStatus::Cancelled);
assert!(run.completed_at.is_some());
assert!(run.run_commit_sha.is_some());
assert!(run.started_at.is_some());
}

#[tokio::test]
async fn test_ci_run_cancel_cancel_fail() {
let (mut conn, client, pr, run, mut rx) = ci_run_test_boilerplate(
InsertCiRun::builder(1, 1)
.base_ref(Base::Commit(Cow::Borrowed("sha")))
.head_commit_sha(Cow::Borrowed("head_commit_sha"))
.ci_branch(Cow::Borrowed("ci_branch"))
.is_dry_run(false)
.requested_by_id(1)
.approved_by_ids(vec![1])
.build(),
)
.await;

UpdateCiRun::builder(run.id)
.status(GithubCiRunStatus::InProgress)
.started_at(Utc::now())
.run_commit_sha(Cow::Borrowed("run_commit_sha"))
.build()
.query()
.execute(&mut conn)
.await
.unwrap();

let run = CiRun::latest(RepositoryId(1), 1).get_result(&mut conn).await.unwrap();

let task = tokio::spawn(async move {
client.merge_workflow().cancel(&run, &client, &mut conn, &pr).await.unwrap();

conn
});

match rx.recv().await.unwrap() {
MockRepoAction::BranchWorkflows { branch, result } => {
assert_eq!(branch, "ci_branch");
result
.send(Ok(vec![
WorkflowRun {
id: RunId(1),
conclusion: None,
head_sha: "run_commit_sha".to_string(),
..Default::default()
},
WorkflowRun {
id: RunId(2),
conclusion: None,
head_sha: "not_run_commit_sha".to_string(),
..Default::default()
},
]))
.unwrap();
}
r => panic!("unexpected action: {:?} expected branch", r),
}

match rx.recv().await.unwrap() {
MockRepoAction::CancelWorkflowRun { run_id, result } => {
assert_eq!(run_id, RunId(1));
result.send(Err(anyhow::anyhow!("failed to cancel"))).unwrap();
}
r => panic!("unexpected action: {:?} expected cancel workflow run", r),
}

match rx.recv().await.unwrap() {
MockRepoAction::DeleteBranch { branch, result } => {
assert_eq!(branch, "ci_branch");
Expand Down
1 change: 1 addition & 0 deletions server/src/github/mock/cancel_workflow_run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit 3b1e022

Please sign in to comment.