Continuous Integration Secure #18810
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Continuous Integration Secure | |
# Secure execution of continuous integration jobs | |
# which are performed upon completion of the | |
# "Continuous Integration" workflow | |
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ | |
on: | |
workflow_run: | |
workflows: [Continuous Integration] | |
types: [completed] | |
env: | |
IMAGE_REPO_PREVIEW: ghcr.io/${{ github.repository }}/storybook-preview | |
IMAGE_REPO_VISUAL_REGRESSION: ghcr.io/${{ github.repository }}/visual-regression | |
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0] != null && github.event.workflow_run.pull_requests[0].number || '' }} | |
VISUAL_REQUIRED: 'pr: visual review required' | |
VISUAL_APPROVED: 'pr: visual review approved' | |
DIFF_URL: https://lyne-visual-regression-diff-pr{}.app.sbb.ch | |
jobs: | |
preview-image: | |
runs-on: ubuntu-latest | |
if: > | |
github.event.workflow_run.conclusion == 'success' && ( | |
github.event.workflow_run.event == 'pull_request' || ( | |
github.event.workflow_run.event == 'push' && | |
github.event.workflow_run.head_branch == 'main' | |
) | |
) | |
permissions: | |
deployments: write | |
packages: write | |
pull-requests: write | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: actions/download-artifact@v4 | |
with: | |
name: storybook | |
path: dist/storybook/ | |
run-id: ${{ github.event.workflow_run.id }} | |
github-token: ${{ secrets.GH_ACTIONS_ARTIFACT_DOWNLOAD }} | |
- name: Remove files with forbidden extensions | |
run: node ./scripts/clean-storybook-files.cjs | |
- name: Create GitHub Deployment | |
id: tag-name | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const environment = process.env.PR_NUMBER ? `pr${process.env.PR_NUMBER}` : 'main'; | |
const payload = { owner: context.repo.owner, repo: context.repo.repo, environment }; | |
const { data: deployment } = await github.rest.repos.createDeployment({ | |
...payload, | |
ref: context.payload.workflow_run.head_sha, | |
auto_merge: false, | |
required_contexts: ['integrity', 'build', 'test', 'lint'] | |
}); | |
await github.rest.repos.createDeploymentStatus({ | |
...payload, | |
deployment_id: deployment.id, | |
state: 'in_progress', | |
environment_url: `https://${context.repo.repo}-${environment}.app.sbb.ch`, | |
}); | |
return environment; | |
result-encoding: string | |
- name: 'Container: Build and preview image' | |
run: | | |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin | |
docker build --tag $IMAGE_REPO_PREVIEW:${{ steps.tag-name.outputs.result }} . | |
docker push $IMAGE_REPO_PREVIEW:${{ steps.tag-name.outputs.result }} | |
env: | |
DOCKER_BUILDKIT: 1 | |
- name: "Add 'preview-available' label" | |
if: env.PR_NUMBER != '' | |
# This label is used for filtering deployments in ArgoCD | |
run: gh issue edit ${{ env.PR_NUMBER }} --repo ${{ github.repository }} --add-label "preview-available" | |
env: | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
codecov: | |
runs-on: ubuntu-latest | |
if: > | |
github.event.workflow_run.event == 'pull_request' && | |
github.event.workflow_run.conclusion == 'success' | |
permissions: | |
pull-requests: write | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: actions/download-artifact@v4 | |
with: | |
name: coverage | |
path: coverage/ | |
run-id: ${{ github.event.workflow_run.id }} | |
github-token: ${{ secrets.GH_ACTIONS_ARTIFACT_DOWNLOAD }} | |
- uses: codecov/codecov-action@v3 | |
with: | |
token: ${{ secrets.CODECOV_TOKEN }} | |
directory: coverage | |
override_branch: ${{ github.event.workflow_run.head_branch }} | |
override_commit: ${{ github.event.workflow_run.head_commit.id }} | |
override_pr: ${{ env.PR_NUMBER }} | |
fail_ci_if_error: true | |
verbose: true | |
visual-regression: | |
runs-on: ubuntu-latest | |
if: > | |
github.event.workflow_run.event == 'pull_request' && | |
github.event.workflow_run.conclusion == 'success' | |
permissions: | |
checks: write | |
packages: write | |
pull-requests: write | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: actions/setup-node@v4 | |
with: | |
node-version-file: .nvmrc | |
cache: yarn | |
- run: yarn install --frozen-lockfile --non-interactive | |
- uses: actions/download-artifact@v4 | |
with: | |
name: visual-regression-screenshots | |
path: dist/screenshots/ | |
run-id: ${{ github.event.workflow_run.id }} | |
github-token: ${{ secrets.GH_ACTIONS_ARTIFACT_DOWNLOAD }} | |
- name: Remove .keep file | |
run: rm dist/screenshots/.keep | |
- name: Build visual-regression-app | |
run: yarn build:visual-regression-app | |
- name: Create check if changed | |
uses: actions/github-script@v7 | |
id: screenshot-check | |
with: | |
script: | | |
const { readdirSync, readFileSync } = await import('fs'); | |
// If we have no screenshots, we do not need to create a check or containers. | |
if (!readdirSync('dist/screenshots').length) { | |
return 'empty'; | |
} | |
const diffUrl = process.env.DIFF_URL.replace('{}', process.env.PR_NUMBER); | |
const diffInfo = JSON.parse(readFileSync('dist/visual-regression-app/diff.json', 'utf8')); | |
let previousDiffInfo = {}; | |
try { | |
const response = await fetch(diffUrl + 'diff.json'); | |
if (response.ok) { | |
previousDiffInfo = await response.json(); | |
} | |
} catch {} | |
// If the diff hash is the same as previously, we do not need to update the state or containers. | |
if (diffInfo.hash === previousDiffInfo.hash) { | |
return 'no-change'; | |
} | |
const { data: deployment } = await github.rest.checks.create({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
name: 'Visual Regression Check', | |
head_sha: context.payload.workflow_run.head_sha, | |
status: 'in_progress', | |
details_url: diffUrl, | |
output: { | |
title: 'Visual Regression Check', | |
summary: diffUrl, | |
text: `Changes: ${diffInfo.changedAmount}\nNew: ${diffInfo.newAmount}`, | |
} | |
}); | |
return 'changed'; | |
result-encoding: string | |
- name: Remove labels when no failed screenshots exist | |
if: steps.screenshot-check.outputs.result == 'empty' | |
run: gh issue edit $PR_NUMBER --remove-label "$VISUAL_REQUIRED" | |
env: | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Add label and create container | |
if: steps.screenshot-check.outputs.result == 'changed' | |
run: | | |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin | |
docker build \ | |
--file tools/visual-regression-testing/visual-regression-app.Dockerfile \ | |
--tag $IMAGE_REPO_VISUAL_REGRESSION:pr$PR_NUMBER \ | |
. | |
docker push $IMAGE_REPO_VISUAL_REGRESSION:pr$PR_NUMBER | |
gh issue edit $PR_NUMBER --remove-label "$VISUAL_APPROVED" | |
gh issue edit $PR_NUMBER --add-label "$VISUAL_REQUIRED" | |
gh issue edit $PR_NUMBER --add-label "visual-regression-diff-available" | |
env: | |
DOCKER_BUILDKIT: 1 | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |