Skip to content

RHIDP-12635: Flavours for Operator #9925

RHIDP-12635: Flavours for Operator

RHIDP-12635: Flavours for Operator #9925

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.sha }}
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 }}
whitelisted_authors: '["openshift-cherrypick-robot"]'
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
sparse-checkout: build/scripts
- name: Checkout PR branch for content to build
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
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: Install Vale
run: |
wget -q https://github.com/errata-ai/vale/releases/download/v3.9.5/vale_3.9.5_Linux_64-bit.tar.gz
tar -xzf vale_3.9.5_Linux_64-bit.tar.gz -C /usr/local/bin vale
vale --version
- name: Sync Vale styles
run: |
cd pr-content
vale sync
- 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 }}
# Used by CQA-15 (redirects check) to diff against the base branch
CQA_BASE_REF: base/${{ github.event.pull_request.base.ref }}
run: |
echo "Building PR ${{ github.event.pull_request.number }}"
rm -rf pr-content/build/scripts
rsync -az trusted-scripts/build/scripts pr-content/build/
# some files are new in main/1.10, so check if they exist before copying
for f in trusted-scripts/.lycheeignore trusted-scripts/lychee.toml; do
if [[ -f $f ]]; then rsync -az $f pr-content/; fi
done
touch pr-content/.lycheecache
cd pr-content
# Add base branch as remote so CQA checks can diff PR content against it
git remote add base https://github.com/${{ github.event.pull_request.base.repo.full_name }}.git
git fetch base ${{ github.event.pull_request.base.ref }}
# Orchestrator runs ccutil + lychee + CQA; older branches fall back to ccutil only
if [[ -f "build/scripts/build-orchestrator.js" ]]; then
node build/scripts/build-orchestrator.js -b "pr-${{ github.event.number }}"
else
build/scripts/build-ccutil.sh -b "pr-${{ github.event.number }}"
fi
# Determine if HTML was generated, so deploy runs even when only CQA/lychee fail.
# Sets html_built=true when all titles built, false when any title failed or no report exists.
- name: Check if HTML was built
id: check_html
if: always() && steps.build.outcome != 'skipped'
run: |
# Older branches (release-1.8/1.9) use build-ccutil.sh and produce no report;
# fall back to the build step outcome
if [[ ! -f "pr-content/build-report.json" ]]; then
if [[ "${{ steps.build.outcome }}" == "success" ]]; then
echo "html_built=true" >> "$GITHUB_OUTPUT"
else
echo "html_built=false" >> "$GITHUB_OUTPUT"
fi
exit 0
fi
# Parse the report to check for title build failures specifically;
# CQA/lychee failures do NOT prevent deployment
title_failures=$(node -e "
const r = JSON.parse(require('fs').readFileSync('pr-content/build-report.json','utf8'));
console.log(r.results.some(t => t.status === 'failed') ? 'true' : 'false');
" 2>&1) || { echo "html_built=false" >> "$GITHUB_OUTPUT"; exit 0; }
if [[ "$title_failures" == "true" ]]; then
echo "html_built=false" >> "$GITHUB_OUTPUT"
else
echo "html_built=true" >> "$GITHUB_OUTPUT"
fi
- name: Deploy to the gh-pages branch
if: steps.check_html.outputs.html_built == 'true'
env:
GITHUB_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: bash trusted-scripts/build/scripts/deploy-gh-pages.sh ./pr-content/titles-generated --message "Deploy PR ${{ github.event.number }} preview"
# Post one consolidated PR comment with build results, preview link, and CQA checklist.
# Preview link is always shown; marked stale when title build failed (HTML not generated).
# CQA section is absent on older branches without CQA.
# Detects existing comments from both old (two-comment) and new (consolidated) formats.
- 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().toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
let report;
try {
report = JSON.parse(fs.readFileSync('pr-content/build-report.json', 'utf8'));
} catch { report = null; }
// ── Section 1: Build status + preview ──
let body = '## PR Build Results\n\n';
if (report) {
const titlesFailed = report.results.filter(r => r.status === 'failed');
const hasTitleFailure = titlesFailed.length > 0;
const overallFailed = hasTitleFailure
|| (report.lychee && report.lychee.status === 'failed')
|| (report.cqa && report.cqa.status === 'failed');
if (overallFailed) {
body += `**Build failed** -- ${report.titles.passed}/${report.titles.total} titles | ${report.duration}s\n`;
} else {
body += `**Build passed** -- ${report.titles.passed}/${report.titles.total} titles | ${report.duration}s\n`;
}
if (hasTitleFailure) {
body += `Preview: ${previewUrl} (stale -- title build failed, showing previous version)\n\n`;
} else {
body += `Preview: ${previewUrl}\n\n`;
}
// ── Section 2: Build error details (title failures only) ──
if (hasTitleFailure) {
const details = titlesFailed.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');
body += `${details}\n\n`;
}
// ── Section 2b: Lychee link validation errors ──
if (report.lychee && report.lychee.status === 'failed' && report.lychee.errors && report.lychee.errors.length > 0) {
const s = report.lychee.stats || {};
body += `### Link Validation (lychee)\n\n`;
body += `Total: ${s.total || '?'} | OK: ${s.successful || 0} | Errors: ${s.errors || 0} | Excluded: ${s.excludes || 0} | Timeouts: ${s.timeouts || 0}\n\n`;
for (const e of report.lychee.errors) {
if (e.sources && e.sources.length > 0) {
for (const src of e.sources) {
body += `- [ ] \`${src}\` \u2192 ${e.line}\n`;
}
} else {
body += `- [ ] ${e.line}\n`;
}
}
body += '\n';
}
if (overallFailed) {
body += `[View full logs](${runUrl})\n\n`;
}
body += `---\n\n`;
// ── Section 3: CQA checklist ──
if (report.cqa && report.cqa.output) {
body += `## Content Quality Assessment\n\n`;
body += report.cqa.output + '\n\n';
const s = report.cqa.stats || {};
if (s.total) {
body += `${s.total} checks: ${s.pass} pass, ${s.fail} fail\n\n`;
}
body += `Run \`node build/scripts/cqa/index.js --all --fix\` locally to review and auto-fix issues.\n\n`;
body += `---\n\n`;
}
} else {
// No report -- early crash
body += `**Build failed** -- build crashed before producing a report.\n`;
body += `Preview: ${previewUrl} (stale -- build crashed, showing previous version)\n\n`;
body += `[View full logs](${runUrl})\n\n`;
body += `---\n\n`;
}
body += `*Updated ${now}*`;
// ── Upsert comment ──
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('## PR Build Results') ||
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
});
}
// ── Clean up old standalone CQA comment ──
const oldCqa = comments.find(c =>
c.body.includes('Content Quality Assessment Results') &&
(!existing || c.id !== existing.id)
);
if (oldCqa) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: oldCqa.id,
});
}
- name: Fail job if build failed
if: steps.build.outcome == 'failure'
run: exit 1