diff --git a/.github/workflows/detect-breaking-changes-build.yml b/.github/workflows/detect-breaking-changes-build.yml new file mode 100644 index 00000000000..c846e7d7336 --- /dev/null +++ b/.github/workflows/detect-breaking-changes-build.yml @@ -0,0 +1,49 @@ +name: Levitate / Detect breaking changes + +on: pull_request + +jobs: + build: + name: Detect + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup environment + uses: actions/setup-node@v2 + with: + node-version: '16' + + - name: Get link for the Github Action job + id: job + uses: actions/github-script@v5 + with: + script: | + const script = require('./.github/workflows/scripts/pr-get-job-link.js') + await script({github, context, core}) + + - name: Install dependencies + run: yarn install --immutable + + - name: Build packages + run: yarn packages:build + + - name: Detect breaking changes + id: breaking-changes + run: ./scripts/check-breaking-changes.sh + env: + FORCE_COLOR: 3 + GITHUB_JOB_LINK: ${{ steps.job.outputs.link }} + GITHUB_STEP_NUMBER: 7 + + - name: Persisting the check output + run: | + mkdir -p ./levitate + echo "{ \"exit_code\": ${{ steps.breaking-changes.outputs.is_breaking }}, \"message\": \"${{ steps.breaking-changes.outputs.mesage }}\", \"job_link\": \"${{ steps.job.outputs.link }}\" }" > ./levitate/result.json + + - name: Upload check output as artifact + uses: actions/upload-artifact@v2 + with: + name: levitate + path: levitate/ diff --git a/.github/workflows/detect-breaking-changes.yml b/.github/workflows/detect-breaking-changes-report.yml similarity index 50% rename from .github/workflows/detect-breaking-changes.yml rename to .github/workflows/detect-breaking-changes-report.yml index b4f92c57ca5..5c7c3c3f209 100644 --- a/.github/workflows/detect-breaking-changes.yml +++ b/.github/workflows/detect-breaking-changes-report.yml @@ -1,46 +1,53 @@ -name: Levitate +name: Levitate / Report breaking changes -on: push +on: + workflow_run: + workflows: ["Levitate / Detect breaking changes"] + types: [completed] jobs: - build: - name: Detecting breaking changes + notify: + name: Report runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Install Node.js + - name: Setup environment uses: actions/setup-node@v2 with: - node-version: '16' + node-version: 16 + - run: npm install adm-zip - - name: Install Yarn - run: npm install --global yarn - - # We need this as we can only access the "Run ID" through the context and we need the "Job ID". - - name: Get link for the Github Action job - id: get-job-link + - name: Download artifact uses: actions/github-script@v5 + id: download-artifact + env: + RUN_ID: ${{github.event.workflow_run.id }} with: result-encoding: string script: | - const { owner, repo } = context.repo; - const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${context.runId}/jobs` - const result = await github.request(url) - console.log(result.data) - - return `https://github.com/grafana/grafana/runs/${result.data.jobs[0].id}?check_suite_focus=true`; - - - name: Find current pull request ID - uses: jwalton/gh-find-current-pr@v1 - id: finder + const runId = process.env.RUN_ID; + const artifactName = 'levitate'; + const script = require('./.github/workflows/scripts/get-workflow-run-artifact.js'); + return await script({ github, context, core, runId, artifactName }); + + - name: Parsing levitate result + uses: actions/github-script@v5 + id: levitate-run + env: + ARTIFACT_FOLDER: ${{ steps.download-artifact.outputs.result }} + with: + script: | + const filePath = `${process.env.ARTIFACT_FOLDER}/result.json`; + const script = require('./.github/workflows/scripts/json-file-to-job-output.js'); + await script({ core, filePath }); - name: Check if "breaking change" label exists id: does-label-exist uses: actions/github-script@v5 env: - PR_NUMBER: ${{ steps.finder.outputs.pr }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} with: script: | const { data } = await github.rest.issues.listLabelsOnIssue({ @@ -53,52 +60,36 @@ jobs: return doesExist ? 1 : 0; - - name: Debug - run: echo -e "Job link - ${{steps.get-job-link.outputs.result}} \nPull request - ${{steps.finder.outputs.pr}} \nLabel exists - ${{steps.does-label-exist.outputs.result}}" - - - name: Install dependencies - run: yarn install --immutable - - - name: Build packages - run: yarn packages:build - - - name: Detect breaking changes - id: breaking-changes - run: ./scripts/check-breaking-changes.sh - env: - FORCE_COLOR: 3 - GITHUB_JOB_LINK: ${{steps.get-job-link.outputs.result}} - - name: Comment on PR - if: ${{ steps.breaking-changes.outputs.is_breaking == 1 }} + if: ${{ steps.levitate-run.outputs.exit_code == 1 }} uses: marocchino/sticky-pull-request-comment@v2 with: - number: ${{ steps.finder.outputs.pr }} + number: ${{ github.event.workflow_run.pull_requests[0].number }} message: | ⚠️   **Possible breaking changes** _(Open the links below in a new tab to go to the correct steps)_ - ${{ steps.breaking-changes.outputs.message }} + ${{ steps.levitate-run.outputs.message }} - [Check console output](${{steps.get-job-link.outputs.result}}) + [Check console output](${{ steps.levitate-run.outputs.job_link }}) - name: Remove comment on PR - if: ${{ steps.breaking-changes.outputs.is_breaking == 0 }} + if: ${{ steps.levitate-run.outputs.exit_code == 0 }} uses: marocchino/sticky-pull-request-comment@v2 with: - number: ${{ steps.finder.outputs.pr }} + number: ${{ github.event.workflow_run.pull_requests[0].number }} delete: true - name: Add "breaking change" label - if: ${{ steps.breaking-changes.outputs.is_breaking == 1 && steps.does-label-exist.outputs.result == 0 }} + if: ${{ steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 }} uses: actions/github-script@v5 env: - PR_NUMBER: ${{ steps.finder.outputs.pr }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} with: script: | - github.rest.issues.addLabels({ + await github.rest.issues.addLabels({ issue_number: process.env.PR_NUMBER, owner: context.repo.owner, repo: context.repo.repo, @@ -106,13 +97,13 @@ jobs: }) - name: Remove "breaking change" label - if: ${{ steps.breaking-changes.outputs.is_breaking == 0 && steps.does-label-exist.outputs.result == 1 }} + if: ${{ steps.levitate-run.outputs.exit_code == 0 && steps.does-label-exist.outputs.result == 1 }} uses: actions/github-script@v5 env: - PR_NUMBER: ${{ steps.finder.outputs.pr }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} with: script: | - github.rest.issues.removeLabel({ + await github.rest.issues.removeLabel({ issue_number: process.env.PR_NUMBER, owner: context.repo.owner, repo: context.repo.repo, @@ -122,40 +113,36 @@ jobs: # This is very weird, the actual request goes through (comes back with a 201), but does not assign the team. # Related issue: https://github.com/renovatebot/renovate/issues/1908 - name: Add "grafana/plugins-platform-frontend" as a reviewer - if: ${{ steps.breaking-changes.outputs.is_breaking == 1 }} + if: ${{ steps.levitate-run.outputs.exit_code == 1 }} uses: actions/github-script@v5 env: - PR_NUMBER: ${{ steps.finder.outputs.pr }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} with: script: | - const response = await github.rest.pulls.requestReviewers({ + await github.rest.pulls.requestReviewers({ pull_number: process.env.PR_NUMBER, owner: context.repo.owner, repo: context.repo.repo, reviewers: [], team_reviewers: ['grafana/plugins-platform-frontend'] - }) - - console.log(response) + }); - name: Remove "grafana/plugins-platform-frontend" from the list of reviewers - if: ${{ steps.breaking-changes.outputs.is_breaking == 0 }} + if: ${{ steps.levitate-run.outputs.exit_code == 0 }} uses: actions/github-script@v5 env: - PR_NUMBER: ${{ steps.finder.outputs.pr }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} with: script: | - const response = await github.rest.pulls.removeRequestedReviewers({ + await github.rest.pulls.removeRequestedReviewers({ pull_number: process.env.PR_NUMBER, owner: context.repo.owner, repo: context.repo.repo, reviewers: [], team_reviewers: ['grafana/plugins-platform-frontend'] - }) - - console.log(response) + }); - name: Exit - run: exit ${{ steps.breaking-changes.outputs.is_breaking }} + run: exit ${{ steps.levitate-run.outputs.exit_code }} shell: bash diff --git a/.github/workflows/scripts/get-workflow-run-artifact.js b/.github/workflows/scripts/get-workflow-run-artifact.js new file mode 100644 index 00000000000..a59276ed1d8 --- /dev/null +++ b/.github/workflows/scripts/get-workflow-run-artifact.js @@ -0,0 +1,45 @@ +module.exports = async ({ github, context, core, runId, artifactName }) => { + try { + const AdmZip = require('adm-zip'); + const fs = require('fs'); + + const { owner, repo } = context.repo; + const { data } = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runId, + }); + + const artifact = data.artifacts.find(a => a.name === artifactName); + + if (!artifact) { + throw new Error(`Could not find artifact ${artifactName} in workflow (${runId})`); + } + + const zip = await github.rest.actions.downloadArtifact({ + owner, + repo, + artifact_id: artifact.id, + archive_format: "zip", + }); + + const dir = `./tmp/${artifactName}`; + await mkdirRecursive(fs, dir); + + const admZip = new AdmZip(Buffer.from(zip.data)); + admZip.extractAllTo(dir, true); + + return dir; + } catch (error) { + core.restFailed(error.message); + } +} + +async function mkdirRecursive(fs, path) { + return new Promise((resolve, reject) => { + fs.mkdir(path, { recursive: true }, (error) => { + if (error) return reject(error); + return resolve(); + }); + }); +} \ No newline at end of file diff --git a/.github/workflows/scripts/json-file-to-job-output.js b/.github/workflows/scripts/json-file-to-job-output.js new file mode 100644 index 00000000000..0f6dd1e65ff --- /dev/null +++ b/.github/workflows/scripts/json-file-to-job-output.js @@ -0,0 +1,27 @@ +module.exports = async ({ core, filePath }) => { + try { + const fs = require('fs'); + const content = await readFile(fs, filePath); + const result = JSON.parse(content); + + core.startGroup('Parsing json file...'); + + for (const property in result) { + core.info(`${property} <- ${result[property]}`); + core.setOutput(property, result[property]); + } + + core.endGroup(); + } catch (error) { + core.restFailed(error.message); + } +} + +async function readFile(fs, path) { + return new Promise((resolve, reject) => { + fs.readFile(path, (error, data) => { + if (error) return reject(error); + return resolve(data); + }); + }); +} \ No newline at end of file diff --git a/.github/workflows/scripts/pr-get-job-link.js b/.github/workflows/scripts/pr-get-job-link.js new file mode 100644 index 00000000000..a4e6bc2f645 --- /dev/null +++ b/.github/workflows/scripts/pr-get-job-link.js @@ -0,0 +1,9 @@ + +module.exports = async ({ github, context, core }) => { + const { owner, repo } = context.repo; + const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${context.runId}/jobs` + const result = await github.request(url) + const link = `https://github.com/grafana/grafana/runs/${result.data.jobs[0].id}?check_suite_focus=true`; + + core.setOutput('link', link); +} \ No newline at end of file diff --git a/scripts/check-breaking-changes.sh b/scripts/check-breaking-changes.sh index a57b763f2f8..70460b9be58 100755 --- a/scripts/check-breaking-changes.sh +++ b/scripts/check-breaking-changes.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash +# Find existing packages using Lerna PACKAGES=$(lerna list -p -l) EXIT_CODE=0 GITHUB_MESSAGE="" -# Loop through packages +# Loop through the packages while IFS= read -r line; do # Read package info @@ -16,7 +17,6 @@ while IFS= read -r line; do PREV="$PACKAGE_NAME@canary" CURRENT="$PACKAGE_PATH/dist/" - # Temporarily skipping @grafana/toolkit, as it doesn't have any exposed static typing if [[ "$PACKAGE_NAME" == '@grafana/toolkit' ]]; then continue @@ -39,7 +39,7 @@ while IFS= read -r line; do if [ $STATUS -gt 0 ] then EXIT_CODE=1 - GITHUB_MESSAGE="${GITHUB_MESSAGE}**\`${PACKAGE_NAME}\`** has possible breaking changes ([more info](${GITHUB_JOB_LINK}#step:11:1))
" + GITHUB_MESSAGE="${GITHUB_MESSAGE}**\`${PACKAGE_NAME}\`** has possible breaking changes ([more info](${GITHUB_JOB_LINK}#step:${GITHUB_STEP_NUMBER}:1))
" fi done <<< "$PACKAGES"