From b050603343454524e405da352ea6aaaa90d41e86 Mon Sep 17 00:00:00 2001 From: Nicolas Moutschen Date: Wed, 10 Jul 2024 09:05:35 +0200 Subject: [PATCH] feat: trigger Github actions from CircleCI (#1959) This triggers Github Actions from CircleCi, so we can continue to use CircleCI as the workflow orchestrator, but run x86 macOS jobs on Github. --------- Co-authored-by: jonathanrainer --- .circleci/config.yml | 28 +++++- .github/workflows/tests-mac-x86.yml | 2 +- Cargo.lock | 7 +- xtask/Cargo.toml | 3 +- xtask/src/commands/github_action.rs | 131 ++++++++++++++++++++++++++++ xtask/src/commands/mod.rs | 2 + xtask/src/main.rs | 6 ++ 7 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 xtask/src/commands/github_action.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 704922041..84721b5e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -145,6 +145,19 @@ workflows: rust_channel: [stable] command: [test] + - xtask: + name: Run studio integration tests on GitHub (<< matrix.rust_channel >> rust on << matrix.platform >>) + context: + - github-orb + matrix: + parameters: + platform: [amd_ubuntu] + rust_channel: [stable] + command: [github-actions] + options: + - | + --workflow-name 'tests-mac-x86.yml' --branch "<< pipeline.git.branch >>" --commit-id "<< pipeline.git.revision >>" --inputs '{"composition-versions": "[\"2.8.2\"]", "router-versions": "[\"1.50.0\"]"}' + - xtask: name: Run supergraph-demo tests (<< matrix.rust_channel >> rust on << matrix.platform >>) matrix: @@ -247,7 +260,7 @@ jobs: type: executor command: type: enum - enum: [lint, unit-test, integration-test, test, package, security-checks] + enum: [lint, unit-test, integration-test, test, package, security-checks, github-actions] options: type: string default: "" @@ -508,14 +521,21 @@ commands: parameters: command: type: enum - enum: [lint, integration-test, unit-test, test, package, security-checks] + enum: [lint, integration-test, unit-test, test, package, security-checks, github-actions] options: type: string platform: type: executor steps: - run: - command: cargo xtask << parameters.command >> << parameters.options >> + command: | + export GITHUB_ACTIONS_TOKEN="$(echo "$GH_BOT_KEY_B64" | base64 -d)" + curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_ACTIONS_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/user + cargo xtask << parameters.command >> << parameters.options >> - unless: condition: @@ -663,4 +683,4 @@ commands: steps: - run: name: Publish to npm (beta) - command: cd << parameters.npm_dir >> && npm publish --tag beta << parameters.options >> + command: cd << parameters.npm_dir >> && npm publish --tag beta << parameters.options >> \ No newline at end of file diff --git a/.github/workflows/tests-mac-x86.yml b/.github/workflows/tests-mac-x86.yml index 90ae61df3..35d69f070 100644 --- a/.github/workflows/tests-mac-x86.yml +++ b/.github/workflows/tests-mac-x86.yml @@ -52,7 +52,7 @@ jobs: matrix: composition-version: ${{ fromJSON(inputs.composition-versions) }} # x86-64 runner - runs-on: macos-13 + runs-on: macos-14-large env: TEST_COMPOSITION_VERSION: "=${{ matrix.composition-version }}" ROVER_BINARY: ../../artifact/rover diff --git a/Cargo.lock b/Cargo.lock index 2efb9b1df..4eef9ea66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2492,12 +2492,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -6306,6 +6306,7 @@ dependencies = [ "lazy_static", "lychee-lib", "mockito", + "octocrab", "regex", "reqwest 0.12.4", "rover-client", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index a68789ea8..a16c2fbac 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -36,6 +36,7 @@ tokio-stream = { workspace = true } uuid = { workspace = true, features = ["v4"] } which = { workspace = true } zip = { workspace = true } +octocrab = "0.38.0" [target.'cfg(not(windows))'.dependencies] lychee-lib = { version = "0.15", features = ["vendored-openssl"] } @@ -43,4 +44,4 @@ lychee-lib = { version = "0.15", features = ["vendored-openssl"] } [dev-dependencies] mockito = "1.4.0" rstest = { workspace = true } -speculoos = { workspace = true } \ No newline at end of file +speculoos = { workspace = true } diff --git a/xtask/src/commands/github_action.rs b/xtask/src/commands/github_action.rs new file mode 100644 index 000000000..b22f8880b --- /dev/null +++ b/xtask/src/commands/github_action.rs @@ -0,0 +1,131 @@ +use std::time::Duration; + +use anyhow::{anyhow, Result}; +use clap::Parser; +use octocrab::{models::RunId, Octocrab, OctocrabBuilder}; +use serde_json::json; + +const WORKFLOW_GET_ID_TIMEOUT: Duration = Duration::from_secs(30); +const WORKFLOW_RUN_TIMEOUT: Duration = Duration::from_secs(600); +const WORKFLOW_WAIT_TIME: Duration = Duration::from_secs(2); + +#[derive(Debug, Parser)] +pub struct GithubActions { + /// The GitHub workflow name + #[arg(long = "workflow-name", env = "WORKFLOW_NAME")] + pub(crate) workflow_name: String, + + /// GitHub organization name + #[arg(long = "organization", default_value = "apollographql")] + pub(crate) organization: String, + + /// GitHub repository name + #[arg(long = "repository", default_value = "rover")] + pub(crate) repository: String, + + /// The repository branch to use + #[arg(long = "branch")] + pub(crate) branch: String, + + /// The commit ID for this run + #[arg(long = "commit-id")] + pub(crate) commit_id: String, + + /// A JSON document to use as inputs for GitHub Actions + #[arg(long = "inputs")] + pub(crate) inputs: String, +} + +impl GithubActions { + pub async fn run(&self) -> Result<()> { + let token = std::env::var("GITHUB_ACTIONS_TOKEN") + .map_err(|_err| anyhow!("$GITHUB_ACTIONS_TOKEN is not set or is not valid UTF-8."))?; + let octocrab = OctocrabBuilder::new() + .personal_token(token.clone()) + .build()?; + + // Find information about the current user + let user = octocrab.current().user().await?.login; + + // Trigger GitHub workflow by sending a workflow dispatch event + // See + let inputs: serde_json::Value = serde_json::from_str(&self.inputs)?; + let res = octocrab + ._post( + format!( + "https://api.github.com/repos/{}/{}/actions/workflows/{}/dispatches", + self.organization, self.repository, self.workflow_name + ), + Some(&json!({ + "ref": self.branch, + "inputs": inputs, + })), + ) + .await?; + + if !res.status().is_success() { + return Err(anyhow!( + "failed to start workflow, got status code {}", + res.status() + )); + } + + // Find the corresponding workflow run ID + let fut = async { + loop { + match self.get_run_id(&octocrab, &user).await { + Ok(run_id) => return run_id, + Err(_err) => { + tokio::time::sleep(WORKFLOW_WAIT_TIME).await; + } + } + } + }; + let run_id = tokio::time::timeout(WORKFLOW_GET_ID_TIMEOUT, fut).await?; + + crate::info!("monitoring run {}", run_id); + + match self.check_run(&octocrab, run_id).await { + Ok(()) => crate::info!("run {} completed successfully", run_id), + Err(_err) => crate::info!("run {} failed or did not complete in time", run_id), + } + + Ok(()) + } + + async fn get_run_id(&self, octocrab: &Octocrab, login: &str) -> Result { + Ok(octocrab + .workflows(&self.organization, &self.repository) + .list_runs(&self.workflow_name) + .branch(&self.branch) + .event("workflow_dispatch") + .actor(login) + .send() + .await? + .into_iter() + .find(|run| run.head_commit.id == self.commit_id) + .ok_or_else(|| anyhow!("could not find a matching run on GitHub"))? + .id) + } + + async fn check_run(&self, octocrab: &Octocrab, run_id: RunId) -> Result<()> { + let fut = async { + loop { + let run = octocrab + .workflows(&self.organization, &self.repository) + .get(run_id) + .await?; + + match run.status.as_str() { + "completed" => return Ok(()), + "failure" => return Err(anyhow!("GitHub workflow run failed")), + _ => { + tokio::time::sleep(WORKFLOW_WAIT_TIME).await; + } + } + } + }; + + tokio::time::timeout(WORKFLOW_RUN_TIMEOUT, fut).await? + } +} diff --git a/xtask/src/commands/mod.rs b/xtask/src/commands/mod.rs index 63b950b2f..fa309fb8d 100644 --- a/xtask/src/commands/mod.rs +++ b/xtask/src/commands/mod.rs @@ -1,5 +1,6 @@ pub(crate) use dist::Dist; pub(crate) use docs::Docs; +pub(crate) use github_action::GithubActions; pub(crate) use integration_test::IntegrationTest; pub(crate) use lint::Lint; pub(crate) use package::Package; @@ -11,6 +12,7 @@ pub(crate) use unit_test::UnitTest; pub(crate) mod dist; pub(crate) mod docs; +pub(crate) mod github_action; pub(crate) mod integration_test; pub(crate) mod lint; pub(crate) mod package; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9975d7ab7..8aa683981 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -51,6 +51,9 @@ pub enum Command { /// Run supergraph-demo with a local Rover build IntegrationTest(commands::IntegrationTest), + /// Trigger Github actions and wait for their completion + GithubActions(commands::GithubActions), + /// Run a basic smoke test for rover dev Smoke(commands::Smoke), } @@ -67,6 +70,9 @@ impl Xtask { Command::Prep(command) => command.run(), Command::Package(command) => command.run(), Command::SecurityChecks(command) => command.run(), + Command::GithubActions(command) => { + tokio::runtime::Runtime::new()?.block_on(command.run()) + } Command::Smoke(command) => tokio::runtime::Runtime::new()?.block_on(command.run()), }?; eprintln!("{}", style("Success!").green().bold());