diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 9ed08c6f..12563d70 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -1,3 +1,8 @@ +# This file is centrally managed as a template file in https://github.com/canonical/solutions-engineering-automation +# To update the file: +# - Edit it in the canonical/solutions-engineering-automation repository. +# - Open a PR with the changes. +# - When the PR merges, the soleng-terraform bot will open a PR to the target repositories with the changes. name: Tests on: @@ -27,13 +32,17 @@ jobs: with: submodules: true - - name: Set up uv - uses: astral-sh/setup-uv@v7 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - name: Install tox - run: uv tool install tox --with tox-uv + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # pin tox to the current major version to avoid + # workflows breaking all at once when a new major version is released. + python -m pip install 'tox<5' - name: Run linters run: tox -e lint @@ -50,13 +59,15 @@ jobs: with: submodules: true - - name: Set up uv - uses: astral-sh/setup-uv@v7 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - name: Install tox - run: uv tool install tox --with tox-uv + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install 'tox<5' - name: Run unit tests run: tox -e unit @@ -104,7 +115,7 @@ jobs: - uses: canonical/craft-actions/charmcraft/setup@main with: - channel: "latest/stable" + channel: "3.x/stable" - name: Pack the charms id: build @@ -133,12 +144,9 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [[ubuntu-24.04], [ubuntu-24.04-arm]] + runs-on: [[ubuntu-24.04], [ubuntu-22.04-arm]] + test-command: ['tox -e func -- -v --base ubuntu@20.04 --keep-models', 'tox -e func -- -v --base ubuntu@22.04 --keep-models', 'tox -e func -- -v --base ubuntu@24.04 --keep-models' ] juju-channel: ["3.6/stable"] - test-command: - - "tox -e func -- -v --base ubuntu@20.04 --keep-models" - - "tox -e func -- -v --base ubuntu@22.04 --keep-models" - - "tox -e func -- -v --base ubuntu@24.04 --keep-models" steps: - uses: actions/checkout@v6 with: @@ -150,20 +158,17 @@ jobs: sudo apt update sudo apt install -y gcc - - name: Set up uv - uses: astral-sh/setup-uv@v7 + - name: Setup Python + uses: actions/setup-python@v6 with: python-version: "3.12" - - name: Install tox - run: uv tool install tox --with tox-uv - - name: Setup Juju environment uses: charmed-kubernetes/actions-operator@main with: provider: "lxd" juju-channel: ${{ matrix.juju-channel }} - charmcraft-channel: "latest/stable" + charmcraft-channel: "3.x/stable" - name: Determine system architecture run: echo "SYSTEM_ARCH=$(uname -m)" >> $GITHUB_ENV @@ -273,12 +278,30 @@ jobs: with: fetch-depth: 0 - # Install dependencies for TICS analysis (coverage + pylint) - - name: Set up uv - uses: astral-sh/setup-uv@v7 + # Install everything from all requirements.txt files otherwise TICS errors. + - name: Install all charm dependencies + run: | + # run in a virtualenv to ensure dependencies will not conflict with the system python packages + sudo apt update + sudo apt install python3-venv -y + python3 -m venv .venv + .venv/bin/python -m pip install --upgrade pip + .venv/bin/python -m pip install coverage[toml] pylint flake8 + + for f in $(find -name '*requirements.txt'); do + echo "${f}" + .venv/bin/python -m pip install --requirement "${f}" + done + + # For reactive charms + for f in $(find -name 'wheelhouse.txt'); do + echo "${f}" + .venv/bin/python -m pip install --requirement "${f}" + done - - name: Install charm dependencies - run: uv sync --group tics + # Symlink venv to a short path so TICS version string stays under 31 chars + sudo ln -s "$GITHUB_WORKSPACE/.venv" /opt/venv + echo "/opt/venv/bin" >> $GITHUB_PATH - name: Determine system architecture run: echo "SYSTEM_ARCH=$(uname -m)" >> $GITHUB_ENV @@ -300,12 +323,12 @@ jobs: if [ -e "${coverage_files[0]}" ]; then echo "Merging coverage files: ${coverage_files[*]}" - uv run coverage combine "${coverage_files[@]}" + coverage combine "${coverage_files[@]}" # Check if there is actual data to report before generating XML with merged reports - if uv run coverage report > /dev/null 2>&1; then - uv run coverage report --show-missing - uv run coverage xml -o tests/report/coverage.xml + if coverage report > /dev/null 2>&1; then + coverage report --show-missing + coverage xml -o tests/report/coverage.xml fi fi diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1b1172be..2c508268 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,6 @@ on: branches: [ main ] release: types: [ published ] - workflow_dispatch: jobs: check: @@ -20,6 +19,8 @@ jobs: release: needs: check runs-on: ubuntu-24.04 + permissions: + contents: write steps: - name: Checkout uses: actions/checkout@v6 diff --git a/pyproject.toml b/pyproject.toml index 4ea3927e..2464b708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,61 +1,15 @@ -[project] -name = "hardware-observer-operator" -version = "0.0.0" -requires-python = ">=3.8" -dependencies = [ - "cosl", - "distro", - "ops>=2.2.0", - "jinja2", - "redfish", - "pydantic<2", - "prometheus-hardware-exporter", -] - -[tool.uv] -package = false - -[tool.uv.sources] -prometheus-hardware-exporter = {git = "https://github.com/canonical/prometheus-hardware-exporter.git", tag = "v1.2.1"} +# This file is centrally managed as a template file in https://github.com/canonical/solutions-engineering-automation +# To update the file: +# - Edit it in the canonical/solutions-engineering-automation repository. +# - Open a PR with the changes. +# - When the PR merges, the soleng-terraform bot will open a PR to the target repositories with the changes. -[dependency-groups] -reformat = [ - "ruff", -] -lint = [ - "mypy", - "ruff", - "codespell", - "types-requests>=2.32.0.20241016", -] -unit = [ - "coverage", - "parameterized", - "pytest", - "pytest-cov", -] -func = [ - "pytest", - "pytest-cov", - "pytest-operator", - "async-lru", - "protobuf", - "tenacity", -] -integration = [ - "juju~=3.5.0", - "pytest", - "pytest-operator", - "prometheus-client", - "pyinstaller", - "tenacity", -] -tics = [ - "coverage[toml]", -] +[tool.setuptools_scm] -[tool.ruff] -line-length = 99 +[tool.flake8] +max-line-length = 99 +max-doc-length = 99 +max-complexity = 10 exclude = [ ".git", "__pycache__", @@ -74,34 +28,47 @@ exclude = [ "hooks/charmhelpers", "tests/charmhelpers", ] +select = ["E", "W", "F", "C", "N", "R", "D", "H"] +# Ignore W503, E501 because using black creates errors with this +# Ignore D107 Missing docstring in __init__ +# Ignore D415 Docstring first line punctuation (doesn't make sense for properties) +# Ignore N818 Exceptions end with "Error" (not all exceptions are errors) +# D100, D101, D102, D103: Ignore missing docstrings in tests +ignore = ["C901", "W503", "E501", "D107", "D415", "N818", "D100", "D101", "D102", "D103", "W504"] +per-file-ignores = ["tests/*:D100,D101,D102,D103,D104"] +# Check for properly formatted copyright header in each file +copyright-check = "True" +copyright-author = "Canonical Ltd." +copyright-regexp = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+%(author)s" -[tool.ruff.lint] -select = ["E", "W", "F", "C90", "N", "D", "I", "CPY"] -ignore = [ - "E501", # line too long (handled by formatter) - "D107", # missing docstring in __init__ - "D203", # one-blank-line-before-class (incompatible with D211) - "D213", # multi-line-summary-second-line (incompatible with D212) - "D415", # first line should end with punctuation - "D100", # missing docstring in public module - "D101", # missing docstring in public class - "D102", # missing docstring in public method - "D103", # missing docstring in public function - "N818", # exception name should end with Error -] - -[tool.ruff.lint.per-file-ignores] -"tests/*" = ["D100", "D101", "D102", "D103", "D104"] - -[tool.ruff.lint.mccabe] -max-complexity = 10 +[tool.black] +line-length = 99 +exclude = ''' +/( + | .eggs + | .git + | .tox + | .venv + | .build + | build + | lib + | report + | docs + | mod + | hooks/charmhelpers + | tests/charmhelpers +)/ +''' -[tool.ruff.lint.pycodestyle] -max-doc-length = 99 +[tool.isort] +profile = "black" +line_length = 99 +skip_glob = [".eggs", ".git", ".tox", ".venv", ".build", "build", "lib", "report", "mod/*", "hooks/charmhelpers", "tests/charmhelpers"] -[tool.ruff.lint.flake8-copyright] -notice-rgx = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+Canonical Ltd\\." -min-file-size = 1 +[tool.pylint] +max-line-length = 99 +disable = ["E1102"] +ignore = ['.eggs', '.git', '.tox', '.venv', '.build', 'lib', 'report', 'tests', 'docs', "mod", "hooks/charmhelpers", "tests/charmhelpers"] [tool.mypy] warn_unused_ignores = true @@ -113,11 +80,16 @@ no_namespace_packages = true exclude = ['.eggs', '.git', '.tox', '.venv', '.build', 'lib', 'report', 'tests', 'docs', "mod", "hooks/charmhelpers", "tests/charmhelpers"] [tool.codespell] -skip = ".eggs,.tox,.git,.venv,venv,build,.build,lib,report,docs,htmlcov,mod,hooks/charmhelpers,tests/charmhelpers,uv.lock" +skip = ".eggs,.tox,.git,.venv,venv,build,.build,lib,report,docs,poetry.lock,htmlcov,mod,hooks/charmhelpers,tests/charmhelpers" quiet-level = 3 check-filenames = true ignore-words-list = "assertIn" +## Ignore unsupported imports +[[tool.mypy.overrides]] +module = ["charmhelpers.*", "setuptools"] +ignore_missing_imports = true + [tool.coverage.run] relative_files = true source = ["."] diff --git a/tox.ini b/tox.ini index 01f1d80c..a58ff772 100644 --- a/tox.ini +++ b/tox.ini @@ -1,85 +1,88 @@ +# This file is centrally managed as a template file in https://github.com/canonical/solutions-engineering-automation +# To update the file: +# - Edit it in the canonical/solutions-engineering-automation repository. +# - Open a PR with the changes. +# - When the PR merges, the soleng-terraform bot will open a PR to the target repositories with the changes. + [tox] -skipsdist = true -skip_missing_interpreters = true +skipsdist=True +skip_missing_interpreters = True envlist = lint, unit -requires = tox>=4.21, tox-uv -no_package = true [testenv] -runner = uv-venv-lock-runner +basepython = python3 setenv = - PYTHONPATH = {toxinidir}:{toxinidir}/src/:{toxinidir}/reactive/:{toxinidir}/hooks/:{toxinidir}/lib/:{toxinidir}/actions:{toxinidir}/files/:{toxinidir}/files/plugins/ - # avoid state written to file during tests - see https://github.com/juju/charm-helpers/blob/85dcbeaf63b0d0f38e8cb17825985460dc2cd02d/charmhelpers/core/unitdata.py#L179-L184 - UNIT_STATE_DB = :memory: - # Default to juju 3, but don't overwrite it if already set in the environment. - # This allows us to still test with juju2.9 for some projects by updating the env externally. - TEST_JUJU3 = {env:TEST_JUJU3:1} + PYTHONPATH = {toxinidir}:{toxinidir}/src/:{toxinidir}/reactive/:{toxinidir}/hooks/:{toxinidir}/lib/:{toxinidir}/actions:{toxinidir}/files/:{toxinidir}/files/plugins/ + # avoid state written to file during tests - see https://github.com/juju/charm-helpers/blob/85dcbeaf63b0d0f38e8cb17825985460dc2cd02d/charmhelpers/core/unitdata.py#L179-L184 + UNIT_STATE_DB = :memory: + # Default to juju 3, but don't overwrite it if already set in the environment. + # This allows us to still test with juju2.9 for some projects by updating the env externally. + TEST_JUJU3 = {env:TEST_JUJU3:1} passenv = * [testenv:lint] -dependency_groups = lint commands = - # Workaround for https://github.com/tox-dev/tox-uv/issues/209 - # `tox-uv<1.16.0` does not recognize `dependency_groups` in Python 3.8 - uv sync --group lint - ruff check {toxinidir} - ruff format --check --diff {toxinidir} - codespell {toxinidir} -I {toxinidir}/.codespell-ignore-words.txt - mypy {toxinidir} + black --check --diff --color . + isort --check --diff --color . + flake8 +deps = + # Pin black and isort because newer versions of black no longer support Python 3.8 and 3.9. + # This ensures consistent formatting and linting across all CI-tested Python versions + # (3.8, 3.10, and 3.12). + black==24.8.0 + isort==5.13.2 + colorama + flake8 + flake8-colors + flake8-docstrings + flake8-import-order + flake8-pyproject + pep8-naming + # so pylint and mypy can reason about the code + {[testenv:unit]deps} + {[testenv:func]deps} [testenv:reformat] -dependency_groups = reformat commands = - # Workaround for https://github.com/tox-dev/tox-uv/issues/209 - # `tox-uv<1.16.0` does not recognize `dependency_groups` in Python 3.8 - uv sync --group lint - ruff check --fix {toxinidir} - ruff format {toxinidir} + black . + isort . +deps = + black==24.8.0 + isort==5.13.2 [testenv:unit] setenv = {[testenv]setenv} COVERAGE_FILE = .coverage-unit -dependency_groups = unit -commands = - # Workaround for https://github.com/tox-dev/tox-uv/issues/209 - # `tox-uv<1.16.0` does not recognize `dependency_groups` in Python 3.8 - uv sync --group unit - pytest {toxinidir}/tests/unit \ - -v \ - --cov \ - --cov-report=term-missing \ - --cov-report=html \ - --cov-report=xml \ - {posargs} +commands = pytest {toxinidir}/tests/unit \ + -v \ + --cov \ + --cov-report=term-missing \ + --cov-report=html \ + --cov-report=xml \ + {posargs} + +deps = + pytest + pytest-cov + -r {toxinidir}/requirements.txt + -r {toxinidir}/tests/unit/requirements.txt [testenv:func] setenv = {[testenv]setenv} COVERAGE_FILE = .coverage-func -dependency_groups = func -commands = - # Workaround for https://github.com/tox-dev/tox-uv/issues/209 - # `tox-uv<1.16.0` does not recognize `dependency_groups` in Python 3.8 - uv sync --group func - pytest {toxinidir}/tests/functional \ - -v \ - --cov \ - --cov-report=term-missing \ - --cov-report=html \ - --cov-report=xml \ - --cov-fail-under=0 \ - {posargs} - -[testenv:integration] -dependency_groups = integration -commands = - # Workaround for https://github.com/tox-dev/tox-uv/issues/209 - # `tox-uv<1.16.0` does not recognize `dependency_groups` in Python 3.8 - uv sync --group integration - pytest {toxinidir}/tests/integration \ - -v \ - --tb native \ - --log-cli-level=INFO \ - -s \ - {posargs} +deps = + pytest + pytest-cov + pytest-operator + -r {toxinidir}/requirements.txt + -r {toxinidir}/tests/functional/requirements.txt +commands = pytest {toxinidir}/tests/functional \ + -v \ + --cov \ + --cov-report=term-missing \ + --cov-report=html \ + --cov-report=xml \ + --cov-fail-under=0 \ + {posargs}