-
Notifications
You must be signed in to change notification settings - Fork 1k
Enforce rollback safety PR checks #5731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
973339e
0272d77
2c10c76
b4533da
5b30bff
7166640
22a3096
ffcaa08
7da2130
0848cd3
7447514
39db97d
f2c8936
cedce19
f61a431
84e12a0
f74f418
539edb5
430a678
07cf1ed
c2ba677
d02e783
3d5d8fa
c1e9dbd
323cd2a
a7dc141
cb1cdf5
40bac67
a841ee0
2a76f40
577f1f3
ccad6be
accb6d4
0115f5f
14fa25e
cdae211
710611c
ef43489
5406bb6
845d3ab
1e61512
54dbda8
9db6e33
24c240e
05dcba4
57440f0
369f3c0
4398621
c9da972
0e1843e
5a60058
e39e5ab
b3f0234
7a482a9
ff77c53
c5e3793
b34df94
078bbda
b8ce588
7696021
2337fee
e2921dc
6ff1639
8c1acb1
7c22ca3
c2b0dd2
6b13c76
1feb6ea
e2bae30
6b72266
9dca2d8
fa906e5
8c92bce
a6cb90e
cfdcb07
f46f1d7
220164a
a329317
16ffa54
06b79e8
7a24736
6547245
b0f7a25
357b607
1c13996
63846b3
521eeeb
be1fc56
d7e000a
94e8851
c982950
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,15 @@ name: Git tree checks | |
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, edited] | ||
| types: [opened, reopened, edited] | ||
| # TODO: This isn't used, probably remove it? | ||
| merge_group: | ||
| permissions: read-all | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.inputs.pr_number || format('sha-{0}', github.sha) }} | ||
| cancel-in-progress: true | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unrelated fix: have later jobs cancel previous ones, e.g. if you edit the PR description several times quickly |
||
|
|
||
| jobs: | ||
| check_base_ref: | ||
| name: Based on `master` | ||
|
|
@@ -18,3 +23,27 @@ jobs: | |
| run: | | ||
| echo "This PR is not based on master. Please wait until the base PR merges." | ||
| exit 1 | ||
|
|
||
| release_dependencies: | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is an odd place to add this job (this file as opposed to ci.yml), but this is kind of where we have put job(s) that depend on PR metadata rather than just commit contents. Thoughts? ci.yml doesn't run on PR edit events, but this workflow does |
||
| if: ${{ github.event_name == 'pull_request' }} | ||
| name: Check release dependencies | ||
| runs-on: spacetimedb-new-runner-2 | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| CARGO_TARGET_DIR: ${{ github.workspace }}/target | ||
| RUST_LOG: rollback_coordination=info | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| # We inspect local tags and git commits in order to determine whether things have been released | ||
| fetch-depth: 0 | ||
| - uses: dsherret/rust-toolchain-file@v1 | ||
| - name: Check release dependencies | ||
| run: >- | ||
| cargo ci other-workflows check-release-deps | ||
| --current-repo . | ||
| --allowed-reference-repo . | ||
| --pr-number "${{ github.event.pull_request.number }}" | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [package] | ||
| name = "ci-check-release-deps" | ||
| version = "0.1.0" | ||
| edition.workspace = true | ||
|
|
||
| [dependencies] | ||
| anyhow.workspace = true | ||
| clap.workspace = true | ||
| rollback-coordination = { path = "../../../rollback-coordination" } | ||
| tracing-subscriber = { workspace = true, features = ["env-filter"] } | ||
|
|
||
| [lints] | ||
| workspace = true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| #![allow(clippy::disallowed_macros)] | ||
|
|
||
| use anyhow::{Context, Result}; | ||
| use clap::Parser; | ||
| use rollback_coordination::{earliest_rollback_point, Gh}; | ||
| use std::fs; | ||
| use std::path::PathBuf; | ||
|
|
||
| #[derive(Parser)] | ||
| #[command( | ||
| about = "Checks the `Rollback safety impact` section and verifies that every referenced PR has been released." | ||
| )] | ||
| struct Args { | ||
| #[arg(long)] | ||
| current_repo: PathBuf, | ||
| /// Paths to local clones of the repos that are allowed to be considered as release dependencies in the rollback safety PR section | ||
| #[arg(long = "allowed-reference-repo", required = true)] | ||
| allowed_reference_repos: Vec<PathBuf>, | ||
| #[arg(long)] | ||
| pr_number: u64, | ||
| } | ||
|
|
||
| fn main() -> Result<()> { | ||
| tracing_subscriber::fmt() | ||
| .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) | ||
| .with_writer(std::io::stderr) | ||
| .init(); | ||
| let args = Args::parse(); | ||
| let template = fs::read_to_string(args.current_repo.join(".github/pull_request_template.md")) | ||
| .context("failed to read .github/pull_request_template.md")?; | ||
| let allowed_reference_repos = args | ||
| .allowed_reference_repos | ||
| .iter() | ||
| .map(PathBuf::as_path) | ||
| .collect::<Vec<_>>(); | ||
| let point = earliest_rollback_point( | ||
| &Gh, | ||
| &args.current_repo, | ||
| &allowed_reference_repos, | ||
| Some(&template), | ||
| false, | ||
| &[args.pr_number], | ||
| )?; | ||
| match point { | ||
| Some(release) => println!("All mentioned PRs have been released. Earliest rollback point: {release}"), | ||
| None => println!("No PR mentions found, so trivially succeeding."), | ||
| } | ||
| Ok(()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [package] | ||
| name = "rollback-coordination" | ||
| version = "0.1.0" | ||
| edition.workspace = true | ||
|
|
||
| [dependencies] | ||
| anyhow.workspace = true | ||
| duct.workspace = true | ||
| regex.workspace = true | ||
| semver.workspace = true | ||
| serde.workspace = true | ||
| serde_json.workspace = true | ||
| tracing.workspace = true | ||
|
|
||
| [dev-dependencies] | ||
| tempfile.workspace = true | ||
|
|
||
| [lints] | ||
| workspace = true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| //! Thin wrappers around GitHub API requests. Business logic belongs in the | ||
| //! calling coordination modules, not here. | ||
|
|
||
| use anyhow::{Context, Result}; | ||
| use duct::cmd; | ||
| use serde::de::DeserializeOwned; | ||
| use serde::Deserialize; | ||
| use std::path::Path; | ||
|
|
||
| #[derive(Deserialize)] | ||
| pub struct PullRequest { | ||
| pub body: Option<String>, | ||
| } | ||
|
|
||
| #[derive(Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct RepositoryInfo { | ||
| pub name_with_owner: String, | ||
| } | ||
|
|
||
| pub trait Github { | ||
| fn get<T: DeserializeOwned>(&self, endpoint: &str) -> Result<T>; | ||
| fn repository_info(&self, path: &Path) -> Result<RepositoryInfo>; | ||
|
|
||
| fn pull_request(&self, repo: &str, number: u64) -> Result<PullRequest> { | ||
| self.get(&format!("repos/{repo}/pulls/{number}")) | ||
| } | ||
| } | ||
|
|
||
| pub struct Gh; | ||
|
|
||
| impl Github for Gh { | ||
| fn get<T: DeserializeOwned>(&self, endpoint: &str) -> Result<T> { | ||
| let output = cmd!("gh", "api", endpoint) | ||
| .read() | ||
| .with_context(|| format!("failed to run `gh api {endpoint}`"))?; | ||
| serde_json::from_str(&output).with_context(|| format!("invalid response from {endpoint}")) | ||
| } | ||
|
|
||
| fn repository_info(&self, path: &Path) -> Result<RepositoryInfo> { | ||
| let output = cmd!("gh", "repo", "view", "--json", "nameWithOwner") | ||
| .dir(path) | ||
| .read() | ||
| .with_context(|| format!("failed to run `gh repo view` in {}", path.display()))?; | ||
| serde_json::from_str(&output).context("invalid response from `gh repo view`") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unrelated fix: run properly on reopened PRs