diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 7f1cd20..e6dccac 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,14 +32,20 @@ jobs: with: submodules: true - - name: Install 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 just - uses: extractions/setup-just@v3 + - 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: just lint + run: tox -e lint unit: name: Unit @@ -48,14 +59,18 @@ jobs: with: submodules: true - - name: Install 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 just - uses: extractions/setup-just@v3 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install 'tox<5' - name: Run unit tests - run: just unit + run: tox -e unit - name: Determine system architecture run: echo "SYSTEM_ARCH=$(uname -m)" >> $GITHUB_ENV @@ -100,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 @@ -130,7 +145,7 @@ jobs: fail-fast: false matrix: runs-on: [['self-hosted', 'jammy', 'amd64', 'two-xlarge']] - test-command: ['TEST_MODEL_SETTINGS="update-status-hook-interval=30s" just func'] + test-command: ['TEST_MODEL_SETTINGS="update-status-hook-interval=30s" tox -e func'] juju-channel: ['3.6/stable'] steps: - uses: actions/checkout@v6 @@ -143,18 +158,17 @@ jobs: sudo apt update sudo apt install -y gcc - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Install just - uses: extractions/setup-just@v3 + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" - 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 @@ -265,12 +279,26 @@ jobs: fetch-depth: 0 # Install everything from all requirements.txt files otherwise TICS errors. - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Install TICS dependencies + - name: Install all charm dependencies run: | - uv sync --group tics + # 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 + # 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 @@ -295,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 1b1172b..2c50826 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/.gitignore b/.gitignore index 0186f21..ae9affa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# 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. + # Python Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/pyproject.toml b/pyproject.toml index 804becc..2464b70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,83 +1,74 @@ -[project] -name = "openstack-exporter-operator" -version = "0.0.0" -requires-python = ">=3.10" -dependencies = [ - "charmlibs-snap~=1.0.1", - "cosl", - "ops ~= 2.5", - "pydantic < 2", - "pyyaml ~= 6.0", -] +# 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. -[tool.uv] -package = false -override-dependencies = [ - "setuptools<82", # setuptools v82.0.0 officially removed the pkg_resources module which python-keystoneclient 3.21.0 depends on - "prettytable>0.7.2" # prettytable v0.7.2 uses .tar.bz2 format for source distribution which is not supported by newer version of uv -] +[tool.setuptools_scm] -[dependency-groups] -unit = [ - "pytest", - "pytest-cov", - "pytest-mock", -] -func = [ - "python-openstackclient", - "pytest", - "pytest-cov", - "pytest-operator", - "zaza @ git+https://github.com/openstack-charmers/zaza.git@master", - "zaza-openstack @ git+https://github.com/openstack-charmers/zaza-openstack-tests.git", -] -lint = [ - "ruff", - "mypy", - "codespell", -] -reformat = ["ruff"] -tics = [ - "coverage[toml]", - "pylint>=4.0.7", -] - -[tool.ruff] -line-length = 99 -preview = true +[tool.flake8] +max-line-length = 99 +max-doc-length = 99 +max-complexity = 10 exclude = [ - ".eggs", ".git", + "__pycache__", ".tox", - ".venv", ".build", "build", - "lib", + "dist", + ".eggs", + "*.egg_info", + "venv", + ".venv", "report", "docs", + "lib", "mod", "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 (conflicts with D211) - "D213", # multi-line-summary-second-line (conflicts with D212) - "D415", # first line punctuation - "N818", # exception name without 'Error' - "D100", "D101", "D102", "D103", -] +[tool.black] +line-length = 99 +exclude = ''' +/( + | .eggs + | .git + | .tox + | .venv + | .build + | build + | lib + | report + | docs + | mod + | hooks/charmhelpers + | tests/charmhelpers +)/ +''' -[tool.ruff.lint.per-file-ignores] -"tests/*" = ["D100", "D101", "D102", "D103", "D104"] +[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 @@ -86,14 +77,19 @@ warn_unreachable = true disallow_untyped_defs = true ignore_missing_imports = true no_namespace_packages = true -exclude = [".eggs", ".git", ".tox", ".venv", ".build", "lib", "report", "tests", "docs", "mod", "hooks/charmhelpers", "tests/charmhelpers"] +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 new file mode 100644 index 0000000..b50d6fb --- /dev/null +++ b/tox.ini @@ -0,0 +1,82 @@ +# 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 +envlist = lint, unit + +[testenv] +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} +passenv = * + +[testenv:lint] +commands = + 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] +commands = + black . + isort . +deps = + black==24.8.0 + isort==5.13.2 + +[testenv:unit] +setenv = + {[testenv]setenv} + COVERAGE_FILE = .coverage-unit +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 +deps = + pytest + pytest-cov + pytest-operator + -r {toxinidir}/requirements.txt + -r {toxinidir}/tests/functional/requirements.txt +changedir = {toxinidir}/tests/functional +commands = functest-run-suite {posargs:--keep-model}