Skip to content

Fix exporting

Fix exporting #14

Workflow file for this run

# 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
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: "Export DISCOURSE_API_USERNAME and DISCOURSE_API_KEY"
- name: Publish documentation
if: ${{ steps.docs-exist.outputs.docs_exist == 'True' && !github.event.pull_request.head.repo.fork }}
uses: canonical/discourse-gatekeeper@stable && ${{ env.discourse_api_username != '' }} && ${{ env.discourse_api_key != '' }}
env:
discourse_api_username: ${{ secrets.DISCOURSE_API_USERNAME }}
discourse_api_key: ${{ secrets.DISCOURSE_API_KEY }}
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)