Skip to content

chore: only run CQA analysis for main and future branches, not the older ones where we didn't implement CQA scripts; also checkout build scripts from the PR's base branch, not main, so we get the correct tooling #8887

chore: only run CQA analysis for main and future branches, not the older ones where we didn't implement CQA scripts; also checkout build scripts from the PR's base branch, not main, so we get the correct tooling

chore: only run CQA analysis for main and future branches, not the older ones where we didn't implement CQA scripts; also checkout build scripts from the PR's base branch, not main, so we get the correct tooling #8887

Workflow file for this run

# Copyright (c) 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
name: Build HTML preview of PR
on:
# /!\ Warning: using the pull_request_target event to be able to read secrets. But using this event without the cautionary measures described below
# may allow unauthorized GitHub users to open a “pwn request” and exfiltrate secrets.
# As recommended in https://iterative.ai/blog/testing-external-contributions-using-github-actions-secrets,
# we are adding an 'authorize' job that checks if the workflow was triggered from a fork PR. In that case, the "external" environment
# will prevent the job from running until it's approved manually by human intervention.
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
- release-1.**
- release-2.**
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.event.pull_request.head.ref }}
cancel-in-progress: true
env:
GH_TEAM: rhdh
GH_ORGANIZATION: redhat-developer
jobs:
check-commit-author:
runs-on: ubuntu-latest
outputs:
is_authorized: ${{ steps.check-team-membership.outputs.is_active_member }}
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.RHDH_GITHUB_APP_ID }}
private-key: ${{ secrets.RHDH_GITHUB_APP_PRIVATE_KEY }}
- name: Check team membership
uses: redhat-developer/rhdh/.github/actions/check-author@main
id: check-team-membership
with:
team: ${{ env.GH_TEAM }}
organization: ${{ env.GH_ORGANIZATION }}
gh_token: ${{ steps.app-token.outputs.token }}
author: ${{ github.event.pull_request.user.login }}
authorize:
# The 'external' environment is configured with the rhdh-content team as required reviewers.
# All the subsequent jobs in this workflow 'need' this job, which will require manual approval for PRs coming from external forks outside of the rhdh team
needs: check-commit-author
environment:
${{ (needs.check-commit-author.outputs.is_authorized == 'true' || github.event.pull_request.head.repo.full_name == github.repository) && 'internal' || 'external' }}
runs-on: ubuntu-latest
steps:
- name: Check if internal PR
id: check
run: |
if [[ "${{ needs.check-commit-author.outputs.is_authorized }}" == "true" ]]; then
echo "✓ Commit author is in rhdh team - using internal environment"
elif [[ "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]]; then
echo "✓ Internal PR (not from fork) - using internal environment"
else
echo "✓ External PR from fork from non-rhdh team member - using external environment for security"
fi
adoc_build:
name: Ccutil Build For PR branch preview
runs-on: ubuntu-latest
needs: authorize
permissions:
contents: read
packages: write
pull-requests: write
steps:
- name: Checkout trusted build scripts from ${{ github.event.pull_request.base.ref }} branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
path: trusted-scripts
- name: Checkout PR branch for content to build
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
path: pr-content
- name: Setup environment
run: |
# update
sudo apt-get update -y || true
# install
sudo apt-get -y -q install podman rsync && podman --version
echo "GIT_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_ENV
- name: Install lychee
run: |
LYCHEE_VERSION="v0.23.0"
curl -sSfL "https://github.com/lycheeverse/lychee/releases/download/lychee-${LYCHEE_VERSION}/lychee-x86_64-unknown-linux-gnu.tar.gz" \
| sudo tar xz -C /usr/local/bin lychee
lychee --version
- name: Restore lychee cache
uses: actions/cache@v4
with:
path: pr-content/.lycheecache
key: lychee-${{ github.event.number }}-${{ github.sha }}
restore-keys: |
lychee-${{ github.event.number }}-
lychee-
- name: Build guides and indexes
id: build
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }}
run: |
echo "Building PR ${{ github.event.pull_request.number }}"
rsync -az trusted-scripts/* .lycheeignore lychee.toml pr-content/
touch pr-content/.lycheecache
cd pr-content
build/scripts/build-ccutil.sh -b "pr-${{ github.event.number }}"
- name: Pull from origin before pushing (if possible)
if: steps.build.outcome == 'success'
run: |
cd pr-content
/usr/bin/git pull origin gh-pages || true
# repo must be public for this to work
- name: Deploy
if: steps.build.outcome == 'success'
uses: peaceiris/actions-gh-pages@v4
# if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.RHDH_BOT_TOKEN }}
publish_branch: gh-pages
keep_files: true
publish_dir: ./pr-content/titles-generated
- name: Post or update PR comment with doc preview link
if: always() && steps.build.outcome != 'skipped'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.RHDH_BOT_TOKEN }}
script: |
const fs = require('fs');
const prNum = context.issue.number;
const previewUrl = `https://redhat-developer.github.io/red-hat-developers-documentation-rhdh/pr-${prNum}/`;
const now = new Date().toLocaleString('en-US', { timeZone: 'UTC' });
const buildOutcome = '${{ steps.build.outcome }}';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
let body;
if (buildOutcome === 'success') {
body = `Updated preview: ${previewUrl} @ ${now}`;
} else {
let report;
try {
report = JSON.parse(fs.readFileSync('pr-content/build-report.json', 'utf8'));
} catch { report = null; }
if (report) {
const failed = report.results.filter(r => r.status === 'failed');
const details = failed.map(r => {
const errs = r.errors.map(e =>
` **Error:** \`${e.line}\`\n **Cause:** ${e.cause}\n **Fix:** ${e.fix}`
).join('\n\n');
return `### ${r.title}\n${errs}`;
}).join('\n\n');
let cqaDetails = '';
if (report.cqa && report.cqa.status === 'failed') {
const s = report.cqa.stats || {};
cqaDetails = `### CQA (Content Quality Assessment)\n` +
`${s.total} checks: ${s.pass} pass, ${s.fail} fail\n\n`;
if (report.cqa.output) {
cqaDetails += `<details>\n<summary>CQA checklist</summary>\n\n` +
`${report.cqa.output}\n</details>\n\n`;
}
cqaDetails += `Run \`node build/scripts/cqa/index.js --all\` locally for details.\n\n`;
}
body = `## :x: Build failed\n\n` +
`${report.titles.passed}/${report.titles.total} titles built successfully | ` +
`${report.titles.failed} failed | ${report.duration}s\n\n` +
`${details}\n\n` +
`${cqaDetails}` +
`[View full logs](${runUrl}) | ${now}`;
} else {
body = `## :x: Build failed\n\n` +
`The documentation build failed before producing a report.\n` +
`[View full logs](${runUrl}) | ${now}`;
}
}
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNum,
});
const existing = comments.find(c =>
c.body.includes('preview: https://redhat-developer.github.io/') ||
c.body.includes('Build failed')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNum,
body: body
});
}
- name: Fail job if build failed
if: steps.build.outcome == 'failure'
run: exit 1