Skip to content

updated python scripts in ci workflows with python files (#1152) #1456

updated python scripts in ci workflows with python files (#1152)

updated python scripts in ci workflows with python files (#1152) #1456

Workflow file for this run

name: CI + CD
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
inputs:
image_tag:
description: "The tag to assign to the images built in the workflow."
type: string
required: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Don't continue building images for a PR if the PR is updated quickly
# For other workflows, allow them to complete and just block on them. This
# ensures deployments in particular to happen in series rather than parallel.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
###########
# Helpers #
###########
get-changes:
name: Get changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
catalog: ${{ steps.paths-filter.outputs.catalog }}
api: ${{ steps.paths-filter.outputs.api }}
ingestion_server: ${{ steps.paths-filter.outputs.ingestion_server }}
frontend: ${{ steps.paths-filter.outputs.frontend }}
documentation: ${{ steps.paths-filter.outputs.documentation }}
changes: ${{ steps.paths-filter.outputs.changes }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Get changes
id: paths-filter
uses: ./.github/actions/get-changes
get-image-tag:
name: Get image tag
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.get-image-tag.outputs.image_tag }}
steps:
- name: Get image tag
id: get-image-tag
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "image_tag=${{ inputs.image_tag }}" >> "$GITHUB_OUTPUT"
else
echo "image_tag=${{ github.sha }}" >> "$GITHUB_OUTPUT"
fi
determine-images:
name: Determine images to build and publish
runs-on: ubuntu-latest
outputs:
do_build: ${{ steps.set-matrix.outputs.do_build }}
build_matrix: ${{ steps.set-matrix.outputs.build_matrix }}
do_publish: ${{ steps.set-matrix.outputs.do_publish }}
publish_matrix: ${{ steps.set-matrix.outputs.publish_matrix }}
needs:
- get-changes
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set matrix images
id: set-matrix
env:
CHANGES: ${{ needs.get-changes.outputs.changes }}
working-directory: automations/python/workflows
run: python set_matrix_images.py
#############
# Universal #
#############
lint: # This includes type-checking of the frontend.
name: Lint files
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
# Python is not needed to run pre-commit.
setup_python: false
# Node.js is needed by lint actions.
install_recipe: "node-install"
- name: Cache pre-commit envs
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: Run pre-commit to lint files
run: |
just precommit
just lint
add-stack-label:
name: Add stack label
if: github.event_name == 'pull_request' && github.repository == 'WordPress/openverse'
runs-on: ubuntu-latest
needs:
- get-changes
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Add API stack label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ needs.get-changes.outputs.api == 'true' }}
with:
labels: "🧱 stack: api"
- name: Add ingestion server stack label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ needs.get-changes.outputs.ingestion_server == 'true' }}
with:
labels: "🧱 stack: ingestion server"
- name: Add frontend stack label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ needs.get-changes.outputs.frontend == 'true' }}
with:
labels: "🧱 stack: frontend"
build-images:
name: Build Docker images
if: needs.determine-images.outputs.do_build == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.determine-images.outputs.build_matrix) }}
needs:
- get-image-tag
- lint
- determine-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
# ℹ️Step only applies for frontend and catalog images.
# Frontend downloads translations and catalog runs `just` recipes.
- name: Setup CI env
if: matrix.image == 'frontend' || matrix.image == 'catalog'
uses: ./.github/actions/setup-env
with:
setup_python: false
# Python is not needed to build the image.
install_recipe: node-install
# ℹ️While this step only applies for catalog image, it is run for all.
# This step
# - sets the Python version used by the catalog image
# - sets the Airflow version used by the catalog image
- name: Prepare catalog for building
id: prepare-catalog
# if: matrix.image == 'catalog'
run: |
echo "python_version=$(just catalog/py-version)" >> "$GITHUB_OUTPUT"
echo "airflow_version=$(just catalog/airflow-version)" >> "$GITHUB_OUTPUT"
# ℹ️Step only applies for frontend image.
# This step
# - downloads translation strings from GlotPress so that they can be
# bundled inside the Docker image
# - copies pnpm config files from the root to the `frontend/` directory
# so that they can be used to mock a workspace inside the Docker image
- name: Prepare frontend for building
if: matrix.image == 'frontend'
run: |
just frontend/run i18n
cp .npmrc .pnpmfile.cjs pnpm-lock.yaml frontend/
env:
GLOTPRESS_USERNAME: ${{ secrets.MAKE_USERNAME }}
GLOTPRESS_PASSWORD: ${{ secrets.MAKE_LOGIN_PASSWORD }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
with:
install: true
- name: Build image `${{ matrix.image }}`
uses: docker/build-push-action@v4
with:
context: ${{ matrix.context }}
target: ${{ matrix.target }}
push: false
tags: openverse-${{ matrix.image }}
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,scope=${{ matrix.image }}
outputs: type=docker,dest=/tmp/${{ matrix.image }}.tar
build-args: |
SEMANTIC_VERSION=${{ needs.get-image-tag.outputs.image_tag }}
PROJECT_PY_VERSION=${{ steps.prepare-catalog.outputs.python_version }}
PROJECT_AIRFLOW_VERSION=${{ steps.prepare-catalog.outputs.airflow_version }}
- name: Upload image `${{ matrix.image }}`
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.image }}
path: /tmp/${{ matrix.image }}.tar
###########
# Catalog #
###########
test-cat:
name: Run tests for the catalog
if: needs.get-changes.outputs.catalog == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
needs:
- get-changes
- build-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
# Python is not needed to run the tests.
setup_nodejs: false
# Node.js is not needed to run API tests.
install_recipe: ""
- name: Load Docker images
uses: ./.github/actions/load-img
with:
setup_api: false
setup_ingestion_server: false
- name: Run tests
run: |
just catalog/test --extended
catalog-checks:
name: Run catalog checks
if: needs.get-changes.outputs.catalog == 'true'
runs-on: ubuntu-latest
needs:
- get-changes
- build-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
# Python is not needed to run pre-commit.
setup_python: false
# Node.js is needed by lint actions.
install_recipe: "node-install"
- name: Load Docker images
uses: ./.github/actions/load-img
with:
setup_api: false
setup_ingestion_server: false
- name: Check DAG documentation
run: |
just catalog/generate-dag-docs true
####################
# Ingestion server #
####################
test-ing:
name: Run tests for ingestion-server
if: needs.get-changes.outputs.ingestion_server == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
needs:
- get-changes
- build-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
# Python is needed to run the test.
setup_nodejs: false
# Node.js is not needed to run ingestion server tests.
install_recipe: ingestion_server/install
- name: Load Docker images
uses: ./.github/actions/load-img
with:
setup_api: false
setup_catalog: false
- name: Run ingestion-server tests
run: just ingestion_server/test-local
- name: Upload ingestion test logs
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ing_logs
path: ingestion_server/test/ingestion_logs.txt
- name: Print ingestion test logs
if: success() || failure()
run: cat ingestion_server/test/ingestion_logs.txt
#######
# API #
#######
test-api:
name: Run tests for the API
if: needs.get-changes.outputs.ingestion_server == 'true' || needs.get-changes.outputs.api == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
needs:
- get-changes
- build-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
# Python is not needed to run the tests.
setup_nodejs: false
# Node.js is not needed to run API tests.
install_recipe: ""
- name: Load Docker images
uses: ./.github/actions/load-img
with:
setup_catalog: false
- name: Start API, ingest and index test data
run: just api/init
- name: Run API tests
run: just api/test
- name: Print API test logs
if: success() || failure()
run: |
just logs > api_logs
cat api_logs
- name: Upload API test logs
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: api_logs
path: api_logs
django-checks:
name: Run Django checks
if: needs.get-changes.outputs.api == 'true'
runs-on: ubuntu-latest
needs:
- get-changes
- build-images
strategy:
fail-fast: false
matrix:
recipe:
- "api/dj check"
- "api/dj validateopenapischema"
- "api/dj makemigrations --check --noinput --merge"
- "api/doc-test"
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
# Python is not needed to run the tests.
setup_nodejs: false
# Node.js is not needed to run API tests.
install_recipe: ""
- name: Load Docker images
uses: ./.github/actions/load-img
with:
setup_catalog: false
- name: Run check recipe
run: just ${{ matrix.recipe }}
env:
DC_USER: root
- name: Upload schema on failure
if: failure() && contains(matrix.recipe, 'validateopenapischema')
uses: actions/upload-artifact@v3
with:
name: openverse-api-openapi-schema.yaml
path: ./api/openapi.yaml
# This job runs when `django-checks` doesn't and always passes, thus allowing
# PRs to meet the required checks criteria and be merged.
bypass-django-checks:
name: Run Django checks
if: needs.get-changes.outputs.api != 'true'
runs-on: ubuntu-latest
needs:
- get-changes
strategy:
matrix:
recipe:
- "api/dj check"
- "api/dj validateopenapischema"
- "api/dj makemigrations --check --noinput --merge"
- "api/doc-test"
steps:
- name: Pass
run: echo 'Django checks are skipped because API is unchanged.'
############
# Frontend #
############
frontend-unit:
name: Run frontend unit tests
if: needs.get-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
needs:
- get-changes
- lint
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
install_recipe: node-install
- name: Run unit tests
run: just frontend/run test:unit
storybook-smoke:
name: Check Storybook smoke test
if: needs.get-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
needs:
- get-changes
- lint
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
install_recipe: node-install
- name: Run Storybook smoke-test
run: just frontend/run storybook:smoke
nuxt-build:
name: Check Nuxt build
if: needs.get-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
needs:
- get-changes
- lint
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
install_recipe: node-install
- name: Run build
run: just frontend/run build
env:
GLOTPRESS_USERNAME: ${{ secrets.MAKE_USERNAME }}
GLOTPRESS_PASSWORD: ${{ secrets.MAKE_LOGIN_PASSWORD }}
playwright:
name: Run Playwright tests
if: needs.get-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
needs:
- get-changes
- lint
strategy:
fail-fast: false
matrix:
name:
- playwright_vr
- playwright_e2e
- storybook_vr
include:
- name: playwright_vr
script: "test:playwright visual-regression"
- name: playwright_e2e
script: "test:playwright e2e"
- name: storybook_vr
script: "test:storybook"
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
with:
setup_python: false
install_recipe: node-install
- name: Run Playwright tests
run: just frontend/run ${{ matrix.script }} --workers=2
- uses: actions/upload-artifact@v3
if: failure()
id: test-results
with:
name: ${{ matrix.name }}_test_results
path: frontend/test-results
# This job runs when `playwright` doesn't and always passes, thus allowing
# PRs to meet the required checks criteria and be merged.
bypass-playwright:
name: Run Playwright tests
if: needs.get-changes.outputs.frontend != 'true'
runs-on: ubuntu-latest
needs:
- get-changes
strategy:
matrix:
name:
- playwright_vr
- playwright_e2e
- storybook_vr
steps:
- name: Pass
run: echo 'Playwright tests are skipped because frontend is unchanged.'
playwright-test-failure-comment:
name: Post Playwright test debugging instructions
if: |
!cancelled() &&
github.event_name == 'pull_request' &&
needs.get-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
needs:
- get-changes
- playwright
steps:
- uses: peter-evans/find-comment@v2
id: test-results-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Playwright failure test results
- uses: actions/github-script@v6
if: steps.test-results-comment.outputs.comment-id != 0
with:
script: |
await github.rest.issues.deleteComment({
repo: context.repo.repo,
owner: context.repo.owner,
comment_id: ${{ steps.test-results-comment.outputs.comment-id }}
})
console.log('Deleted comment with ID ${{ steps.test-results-comment.outputs.comment-id }}')
- name: Build help body
if: needs.playwright.result == 'failure'
id: help-body
run: |
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) # Security hardening: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections
MESSAGE=$(cat <<HEREDOC
help_body<<$EOF
**Playwright failure test results**: <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}>
It looks like some of the Playwright tests failed. You can download the trace output and image diffs for the failed tests under the "Artifacts" section in the above page.
You can read the [docs on how to use this artifact](https://github.com/${{ github.repository }}/blob/main/frontend/test/playwright/README.md#debugging).
$EOF
HEREDOC
)
echo "$MESSAGE"
echo "$MESSAGE" >> "$GITHUB_OUTPUT"
- uses: peter-evans/create-or-update-comment@v3
id: create-comment
# Do not leave a comment on forks
if: |
needs.playwright.result == 'failure' &&
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.owner.login == 'WordPress' &&
github.actor != 'dependabot[bot]'
)
with:
issue-number: ${{ github.event.pull_request.number }}
body: ${{ steps.help-body.outputs.help_body }}
#################
# Documentation #
#################
emit-docs:
name: Emit full-stack docs
# https://github.com/actions/runner/issues/491#issuecomment-850884422
if: |
!failure() && !cancelled() &&
(
(
github.event_name == 'push' &&
github.repository == 'WordPress/openverse'
) ||
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.owner.login == 'WordPress' &&
github.actor != 'dependabot[bot]'
)
) &&
(needs.get-changes.outputs.frontend == 'true' || needs.get-changes.outputs.documentation == 'true') &&
(needs.test-ing.result == 'success' || needs.test-ing.result == 'skipped') &&
(needs.test-api.result == 'success' || needs.test-api.result == 'skipped') &&
(needs.test-cat.result == 'success' || needs.test-cat.result == 'skipped') &&
(needs.nuxt-build.result == 'success' || needs.nuxt-build.result == 'skipped')
runs-on: ubuntu-latest
needs:
- get-changes
- test-cat
- test-ing
- test-api
- nuxt-build
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup CI env
uses: ./.github/actions/setup-env
- name: Compile documentation
uses: ./.github/actions/build-docs
with:
glotpress_username: ${{ secrets.MAKE_USERNAME }}
glotpress_password: ${{ secrets.MAKE_LOGIN_PASSWORD }}
# Docs will be located at `/tmp/docs`.
- name: Recreate working directory # to avoid superfluous files from getting tracked automatically
run: |
cd ..
sudo rm -rf openverse
mkdir openverse
- name: Checkout repository at `gh-pages` branch
uses: actions/checkout@v3
with:
ref: gh-pages
- name: Copy existing previews
if: github.event_name == 'push'
run: |
mv /tmp/docs /tmp/gh-pages
mv _preview /tmp/gh-pages/_preview
- name: Replace preview of current PR
if: github.event_name == 'pull_request'
run: |
cp -r . /tmp/gh-pages
sudo rm -rf /tmp/gh-pages/_preview/${{ github.event.pull_request.number }}
mv /tmp/docs /tmp/gh-pages/_preview/${{ github.event.pull_request.number }}
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: /tmp/gh-pages
force_orphan: true
cname: docs.openverse.org
- uses: peter-evans/find-comment@v2
if: github.event_name == 'pull_request'
id: final-preview-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Full-stack documentation
- uses: peter-evans/create-or-update-comment@v3
if: github.event_name == 'pull_request'
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.final-preview-comment.outputs.comment-id }}
edit-mode: replace
body: |
**Full-stack documentation**: <https://docs.openverse.org/_preview/${{ github.event.pull_request.number }}>
Please note that GitHub pages takes a little time to deploy newly pushed code, if the links above don't work or you see old versions, wait 5 minutes and try again.
You can check [the GitHub pages deployment action list](https://github.com/${{ github.repository }}/actions/workflows/pages/pages-build-deployment) to see the current status of the deployments.
- name: Checkout repository # again, to enable cleaning
uses: actions/checkout@v3
if: success() || failure()
#################
# Docker images #
#################
publish-images:
name: Publish Docker images
runs-on: ubuntu-latest
# prevent running on fork PRs
if: |
!failure() && !cancelled() &&
github.event_name == 'push' &&
github.repository == 'WordPress/openverse' &&
needs.determine-images.outputs.do_publish == 'true' &&
(needs.test-ing.result == 'success' || needs.test-ing.result == 'skipped') &&
(needs.test-api.result == 'success' || needs.test-api.result == 'skipped') &&
(needs.test-cat.result == 'success' || needs.test-cat.result == 'skipped') &&
(needs.nuxt-build.result == 'success' || needs.nuxt-build.result == 'skipped')
needs:
- determine-images
- get-image-tag
- build-images
- test-ing # test for ingestion server
- test-api # test for API
- test-cat # test for catalog
- nuxt-build # test for frontend
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.determine-images.outputs.publish_matrix) }}
steps:
- name: Log in to GitHub Docker Registry
uses: docker/login-action@v2
with:
registry: https://ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Download image `${{ matrix.image }}`
uses: actions/download-artifact@v3
with:
name: ${{ matrix.image }}
path: /tmp
- name: Load and tag image `${{ matrix.image }}` (latest & sha)
run: |
docker load --input /tmp/${{ matrix.image }}.tar
docker tag openverse-${{ matrix.image }} \
ghcr.io/wordpress/openverse-${{ matrix.image }}:latest
docker tag openverse-${{ matrix.image }} \
ghcr.io/wordpress/openverse-${{ matrix.image }}:${{ needs.get-image-tag.outputs.image_tag }}
docker push --all-tags ghcr.io/wordpress/openverse-${{ matrix.image }}
##############
# Deployment #
##############
# See https://github.com/WordPress/openverse/issues/1033 for why
# we don't use the standard reusable workflow approach for these.
deploy-frontend:
name: Deploy staging frontend
runs-on: ubuntu-latest
if: |
!failure() && !cancelled() &&
github.event_name == 'push' &&
needs.get-changes.outputs.frontend == 'true' &&
needs.playwright.result == 'success' &&
needs.publish-images.result == 'success'
needs:
- get-changes
- playwright
- get-image-tag
- publish-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Dispatch workflow
run: |
gh workflow run \
-f tag=${{ needs.get-image-tag.outputs.image_tag }} \
-f actor=${{ github.actor }} \
"Deployment - staging-nuxt"
env:
GITHUB_TOKEN: ${{ github.token }}
deploy-api:
name: Deploy staging API
runs-on: ubuntu-latest
if: |
!failure() && !cancelled() &&
github.event_name == 'push' &&
needs.get-changes.outputs.api == 'true' &&
needs.publish-images.result == 'success'
needs:
- get-changes
- get-image-tag
- publish-images
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Dispatch workflow
run: |
gh workflow run \
-f tag=${{ needs.get-image-tag.outputs.image_tag }} \
-f actor=${{ github.actor }} \
"Deployment - staging-api"
env:
GITHUB_TOKEN: ${{ github.token }}
################
# Notification #
################
send_report:
name: Send Slack report
runs-on: ubuntu-latest
if: |
!cancelled() &&
github.event_name == 'push' &&
github.repository == 'WordPress/openverse' &&
(
((needs.get-changes.outputs.frontend == 'true' || needs.get-changes.outputs.documentation == 'true') && needs.emit-docs.result != 'success') ||
(needs.determine-images.outputs.do_publish == 'true' && needs.publish-images.result != 'success') ||
(needs.get-changes.outputs.frontend == 'true' && needs.deploy-frontend.result != 'success') ||
(needs.get-changes.outputs.api == 'true' && needs.deploy-api.result != 'success')
)
needs: # the end products of the CI + CD workflow
- get-changes
- determine-images
- emit-docs
- publish-images
- deploy-frontend
- deploy-api
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Generate report
id: report
env:
EMIT_DOCS_RESULT: ${{ needs.emit-docs.result }}
PUBLISH_IMAGES_RESULT: ${{ needs.publish-images.result }}
DEPLOY_FRONTEND_RESULT: ${{ needs.deploy-frontend.result }}
DEPLOY_API_RESULT: ${{ needs.deploy-api.result }}
SERVER_URL: ${{ github.server_url }}
REPOSITORY: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
working-directory: automations/python/workflows
run: python generate_report.py
- name: Send report
uses: slackapi/slack-github-action@v1.23.0
with:
payload: ${{ steps.report.outputs.payload }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK