chore(): Install tree for github runners #11
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
# Copyright 2024 Canonical Ltd. | |
# See LICENSE file for licensing details. | |
name: Tests | |
on: | |
workflow_call: | |
inputs: | |
charm-directory: | |
type: string | |
description: The working directory for the docs | |
default: "." | |
charmcraft-channel: | |
description: Charmcraft channel to use | |
type: string | |
default: latest/stable | |
working-directory: | |
type: string | |
description: The working directory for jobs | |
default: "./" | |
shellcheck-working-directory: | |
type: string | |
description: The working directory for the shellcheck-lint job | |
self-hosted-runner: | |
type: boolean | |
description: Whether to use self-hosted runners to run the jobs. | |
default: true | |
self-hosted-runner-arch: | |
type: string | |
description: Architecture to use on self-hosted runners to run the jobs. | |
default: "x64" | |
self-hosted-runner-label: | |
type: string | |
description: Label for selecting the self-hosted runners. | |
default: "large" | |
self-hosted-runner-image: | |
type: string | |
description: Image of the requested runner. Supports only 'jammy' or 'noble'. | |
default: "jammy" | |
pre-run-script: | |
description: Path to the bash script to be run before the integration tests | |
type: string | |
concurrency: | |
group: operator-workflows-${{ github.workflow }}-${{ inputs.working-directory }}-tests-${{ github.ref }}-self-hosted-${{ inputs.self-hosted-runner }} | |
cancel-in-progress: true | |
jobs: | |
inclusive-naming-check: | |
name: Inclusive naming | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.working-directory }} | |
steps: | |
- uses: actions/[email protected] | |
with: | |
repository: canonical/Inclusive-naming | |
path: ${{ inputs.working-directory }} | |
- run: mv * /tmp | |
- uses: actions/[email protected] | |
- name: Merge configuration files | |
run: | | |
# Combine all entries and replace matching elements by | |
# .name in .rules for the ones in .woke.yaml | |
woke_file="" | |
if [ -f .woke.yaml ]; then | |
woke_file=".woke.yaml" | |
elif [ -f .woke.yml ]; then | |
woke_file=".woke.yml" | |
fi | |
if [ ! -z "$woke_file" ]; then | |
yq eval-all ' | |
( | |
. as $item ireduce ({}; . *+ $item) | .rules | unique_by(.name) | |
) as $mergedArray | . as $item ireduce ({}; . *+ $item) | .rules = $mergedArray | |
' $woke_file /tmp/config.yml | tee /tmp/merged.yml | |
mv /tmp/merged.yml /tmp/config.yml | |
fi | |
- name: Run inclusive naming check | |
uses: canonical/inclusive-naming@main | |
with: | |
fail-on-error: "true" | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
reporter: github-pr-review | |
woke-args: ". -c /tmp/config.yml" | |
filter-mode: nofilter | |
workdir: ${{ inputs.working-directory }} | |
woke-version: latest | |
vale: | |
name: Style checker | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
shell: bash | |
working-directory: ${{ inputs.working-directory }} | |
steps: | |
- name: Checkout repo to runner | |
uses: actions/[email protected] | |
- name: Install styles | |
uses: canonical/praecepta@main | |
run: | | |
apt-get update -y && apt-get install -y tree | |
- name: Run Vale tests | |
uses: errata-ai/vale-action@reviewdog | |
with: | |
files: ./docs | |
fail_on_error: false | |
shellcheck-lint: | |
name: Shell scripts lint | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.shellcheck-working-directory || inputs.working-directory }} | |
steps: | |
- uses: actions/[email protected] | |
- name: Gather files to scan | |
shell: bash | |
id: gather | |
run: | | |
declare -a filepaths | |
shebangregex="^#! */[^ ]*/(env *)?[abk]*sh" | |
set -f # temporarily disable globbing so that globs in inputs aren't expanded | |
while IFS= read -r -d '' file; do | |
filepaths+=("$file") | |
done < <(find . \ | |
-type f \ | |
'(' \ | |
-name '*.bash' \ | |
-o -name '.bashrc' \ | |
-o -name 'bashrc' \ | |
-o -name '.bash_aliases' \ | |
-o -name '.bash_completion' \ | |
-o -name '.bash_login' \ | |
-o -name '.bash_logout' \ | |
-o -name '.bash_profile' \ | |
-o -name 'bash_profile' \ | |
-o -name '*.ksh' \ | |
-o -name 'suid_profile' \ | |
-o -name '*.zsh' \ | |
-o -name '.zlogin' \ | |
-o -name 'zlogin' \ | |
-o -name '.zlogout' \ | |
-o -name 'zlogout' \ | |
-o -name '.zprofile' \ | |
-o -name 'zprofile' \ | |
-o -name '.zsenv' \ | |
-o -name 'zsenv' \ | |
-o -name '.zshrc' \ | |
-o -name 'zshrc' \ | |
-o -name '*.sh' \ | |
-o -path '*/.profile' \ | |
-o -path '*/profile' \ | |
-o -name '*.shlib' \ | |
')' \ | |
-print0) | |
while IFS= read -r -d '' file; do | |
head -n1 "$file" | grep -Eqs "$shebangregex" || continue | |
filepaths+=("$file") | |
done < <(find . \ | |
-type f ! -name '*.*' -perm /111 \ | |
-print0) | |
echo "filepaths=${filepaths[@]}" >> $GITHUB_OUTPUT | |
set +f # re-enable globbing | |
- if: ${{ steps.gather.outputs.filepaths != '' }} | |
name: Shellcheck Problem Matchers | |
uses: lumaxis/[email protected] | |
- if: ${{ steps.gather.outputs.filepaths != '' }} | |
run: shellcheck -f gcc ${{steps.gather.outputs.filepaths}} | |
docker-lint: | |
name: Dockerfile lint | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.working-directory }} | |
steps: | |
- uses: actions/[email protected] | |
- name: Check for Dockerfiles | |
id: dockerfiles | |
run: echo -n "found=$(find . -name "*Dockerfile" -printf "${{ inputs.working-directory }}%p ")" >> $GITHUB_OUTPUT | |
- if: ${{ steps.dockerfiles.outputs.found != '' }} | |
name: Run HadoLint | |
id: hado-lint | |
uses: jbergstroem/hadolint-gh-action@v1 | |
with: | |
dockerfile: "${{ steps.dockerfiles.outputs.found }}" | |
metadata-lint: | |
name: Lint metadata.yaml | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.working-directory }} | |
steps: | |
- uses: actions/[email protected] | |
- name: Check file existence | |
id: metadata-yaml | |
run: echo "exists=$([ -f metadata.yaml ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT | |
- name: Run lint | |
if: steps.metadata-yaml.outputs.exists == 'true' | |
id: run-lint | |
run: | | |
options="" | |
if [[ "${{ inputs.self-hosted-runner-image }}" == "noble" ]]; then | |
options+=" --break-system-packages" | |
fi | |
python3 -m pip install check-jsonschema $options | |
curl -fLsSo $PWD/metadata.schema https://raw.githubusercontent.com/canonical/operator-workflows/main/.github/files/metadata.schema | |
check-jsonschema metadata.yaml --schemafile metadata.schema | |
lint-and-unit-test: | |
name: Lint and unit tests | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.working-directory }} | |
steps: | |
- uses: actions/[email protected] | |
- name: Pre-run script | |
if: ${{ inputs.pre-run-script != '' }} | |
run: bash -xe ${{ inputs.pre-run-script }} | |
- name: Install tox | |
run: | | |
options="" | |
if [[ "${{ inputs.self-hosted-runner-image }}" == "noble" ]]; then | |
options+=" --break-system-packages" | |
fi | |
python3 -m pip install tox $options | |
- name: Run tests | |
id: run-tests | |
run: | | |
# Ensure that stdout appears as normal and redirect to file and exit depends on exit code of first command | |
STDOUT_LOG=$(mktemp --suffix=stdout.log) | |
echo STDOUT_LOG=$STDOUT_LOG >> $GITHUB_ENV | |
tox --result-json=test-result.json | tee $STDOUT_LOG ; test ${PIPESTATUS[0]} -eq 0 | |
- name: Check lint and test stdout | |
run: | | |
# Check dependencies | |
EXPECTED_LINT_DEPS="\ | |
mypy \ | |
isort \ | |
black \ | |
flake8-docstrings \ | |
flake8-docstrings-complete \ | |
flake8-builtins \ | |
flake8-test-docs \ | |
pep8-naming \ | |
codespell \ | |
pylint \ | |
pydocstyle \ | |
" | |
for EXPECTED_LINT_DEP in $EXPECTED_LINT_DEPS; do | |
# Check that there is a `lint...<dependency>` line for each of the expected dependencies | |
DEP_REGEX="lint.*$EXPECTED_LINT_DEP" | |
if ! grep -q "$DEP_REGEX" $STDOUT_LOG ; then | |
# Write to stderr | |
>&2 echo "$EXPECTED_LINT_DEP should be in deps of [testenv:lint] environment in tox.ini" | |
exit 1 | |
fi | |
done | |
# Check commands | |
EXPECTED_LINT_CMDS="\ | |
pydocstyle \ | |
codespell \ | |
flake8 \ | |
isort \ | |
black \ | |
mypy \ | |
pylint \ | |
" | |
for EXPECTED_LINT_CMD in $EXPECTED_LINT_CMDS; do | |
# Check that there is a `lint...commands...<command>` line for each of the expected commands | |
CMD_REGEX="lint.*commands.*$EXPECTED_LINT_CMD" | |
if ! grep -q "$CMD_REGEX" $STDOUT_LOG ; then | |
# Write to stderr | |
>&2 echo "$EXPECTED_LINT_CMD should be in commands of [testenv:lint] environment in tox.ini" | |
exit 1 | |
fi | |
done | |
- name: Export test report | |
if: always() && !cancelled() | |
uses: actions/[email protected] | |
with: | |
script: | | |
const no_color = (text) => { | |
return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); | |
} | |
const sha = '${{ github.event.pull_request.head.sha }}'; | |
const fs = require('fs'); | |
const result = JSON.parse(fs.readFileSync('${{ inputs.working-directory }}test-result.json')).testenvs; | |
let lint_result = result.lint.test; | |
let lint_success = true; | |
let lint_output = ''; | |
for (let lint_test_result of lint_result) { | |
if (lint_test_result.retcode != 0) { | |
lint_success = false; | |
} | |
if (lint_test_result.output) { | |
lint_output += lint_test_result.output; | |
} | |
} | |
let unit_result = result.unit.test; | |
let unit_success = unit_result[0].retcode == 0; | |
let unit_output = unit_result[0].output; | |
let static_result = result.static.test; | |
let static_output = static_result[0].output; | |
let coverage_result = result["coverage-report"].test; | |
let coverage_output = coverage_result[0].output; | |
let reports = []; | |
if (!lint_success) { | |
reports.push( | |
`Lint checks failed for ${sha}\n | |
\`\`\`\n${no_color(lint_output).trim()}\n\`\`\`` | |
); | |
} | |
if (!unit_success) { | |
reports.push( | |
`Unit tests failed for ${sha}\n | |
\`\`\`\n${no_color(unit_output).trim()}\n\`\`\`` | |
); | |
} | |
reports.push( | |
`Test coverage for ${sha}\n | |
\`\`\`\n${no_color(coverage_output).trim()}\n\`\`\` | |
Static code analysis report\n | |
\`\`\`\n${no_color(static_output).trim()}\n\`\`\`` | |
); | |
let json = JSON.stringify(reports); | |
fs.writeFileSync('report.json', json); | |
- name: Upload coverage report | |
uses: actions/upload-artifact@v4 | |
if: always() && github.event_name == 'pull_request' && !cancelled() | |
with: | |
name: report | |
path: report.json | |
overwrite: true | |
draft-publish-docs: | |
name: Draft publish docs | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} | |
steps: | |
- uses: actions/[email protected] | |
- name: Search for docs folder | |
id: docs-exist | |
run: echo "docs_exist=$([[ -d docs ]] && echo 'True' || echo 'False')" >> $GITHUB_OUTPUT | |
- name: Publish documentation | |
if: ${{ steps.docs-exist.outputs.docs_exist == 'True' && !github.event.pull_request.head.repo.fork }} | |
uses: canonical/discourse-gatekeeper@stable | |
with: | |
discourse_host: discourse.charmhub.io | |
discourse_api_username: ${{ secrets.DISCOURSE_API_USERNAME }} | |
discourse_api_key: ${{ secrets.DISCOURSE_API_KEY }} | |
dry_run: true | |
github_token: ${{ secrets.GITHUB_TOKEN }} | |
charm_dir: ${{ inputs.working-directory }}/${{ inputs.charm-directory }} | |
license-headers-check: | |
name: Check license headers | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
defaults: | |
run: | |
working-directory: ${{ inputs.working-directory }} | |
steps: | |
- uses: actions/[email protected] | |
- name: Check .licenserc.yaml exists | |
id: licenserc-yaml | |
run: echo "exists=$([ -f .licenserc.yaml ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT | |
- name: Get default license configuration | |
if: steps.licenserc-yaml.outputs.exists == 'false' | |
run: | | |
curl -fLsSo .licenserc.yaml https://raw.githubusercontent.com/canonical/operator-workflows/main/.github/files/.licenserc.yaml | |
- name: Check license headers | |
uses: apache/skywalking-eyes/header@main | |
with: | |
config: .licenserc.yaml | |
lib-check: | |
name: Check libraries | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
steps: | |
- uses: actions/[email protected] | |
- name: Check libs | |
if: ${{ !github.event.pull_request.head.repo.fork }} | |
uses: canonical/charming-actions/[email protected] | |
with: | |
credentials: ${{ secrets.CHARMHUB_TOKEN }} | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
charmcraft-channel: ${{ inputs.charmcraft-channel }} | |
required_status_checks: | |
name: Required Test Status Checks | |
needs: | |
- draft-publish-docs | |
- docker-lint | |
- inclusive-naming-check | |
- lib-check | |
- lint-and-unit-test | |
- metadata-lint | |
- shellcheck-lint | |
- license-headers-check | |
runs-on: >- | |
${{ | |
inputs.self-hosted-runner && | |
fromJson(format('[''self-hosted'', ''{0}'', ''{1}'', ''{2}'']', | |
inputs.self-hosted-runner-arch, inputs.self-hosted-runner-label, inputs.self-hosted-runner-image | |
)) || 'ubuntu-22.04' | |
}} | |
if: always() && !cancelled() | |
timeout-minutes: 5 | |
steps: | |
- run: | | |
[ '${{ needs.draft-publish-docs.result }}' = 'success' ] || (echo draft-publish-docs failed && false) | |
[ '${{ needs.docker-lint.result }}' = 'success' ] || (echo docker-lint failed && false) | |
[ '${{ needs.inclusive-naming-check.result }}' = 'success' ] || (echo inclusive-naming-check failed && false) | |
[ '${{ needs.lib-check.result }}' = 'success' ] || (echo lib-check failed && false) | |
[ '${{ needs.lint-and-unit-test.result }}' = 'success' ] || (echo lint-and-unit-test failed && false) | |
[ '${{ needs.metadata-lint.result }}' = 'success' ] || (echo metadata-lint failed && false) | |
[ '${{ needs.shellcheck-lint.result }}' = 'success' ] || (echo shellcheck-lint failed && false) | |
[ '${{ needs.license-headers-check.result }}' = 'success' ] || (echo license-headers-check failed && false) |