Skip to content

Commit

Permalink
Add pyproject.toml to configure ruff
Browse files Browse the repository at this point in the history
  • Loading branch information
mateoflorido committed Oct 27, 2023
1 parent b3f3c4d commit e6ce3ea
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 94 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributor Guide

This Juju charm is open source ([Apache License 2.0](./LICENSE)) and we actively seek any community contibutions
This Juju charm is open source ([Apache License 2.0](./LICENSE)) and we actively seek any community contributions
for code, suggestions and documentation.
This page details a few notes, workflows and suggestions for how to make contributions most effective and help us
all build a better charm - please give them a read before working on any contributions.
Expand Down
43 changes: 43 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Testing tools configuration
[tool.coverage.run]
branch = true

[tool.coverage.report]
show_missing = true

[tool.pytest.ini_options]
minversion = "6.0"
log_cli_level = "INFO"

# Formatting tools configuration
[tool.black]
line-length = 99
target-version = ["py38"]

# Linting tools configuration
[tool.ruff]
line-length = 99
select = ["E", "W", "F", "C", "N", "D", "I001"]
extend-ignore = [
"D203",
"D204",
"D213",
"D215",
"D400",
"D404",
"D406",
"D407",
"D408",
"D409",
"D413",
]
ignore = ["E501", "D107"]
extend-exclude = ["__pycache__", "*.egg_info"]
per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]}

[tool.ruff.mccabe]
max-complexity = 10

[tool.codespell]
skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage"

38 changes: 13 additions & 25 deletions src/charm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3

"""Charmed Operator for CoreDNS."""
import logging
from pathlib import Path
from string import Template
Expand All @@ -12,21 +12,15 @@
from ops.charm import CharmBase
from ops.framework import StoredState
from ops.main import main
from ops.model import (
ActiveStatus,
BlockedStatus,
MaintenanceStatus,
ModelError,
WaitingStatus,
)
from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, ModelError, WaitingStatus
from ops.pebble import Error as PebbleError
from ops.pebble import ServiceStatus

logger = logging.getLogger(__name__)


class CoreDNSCharm(CharmBase):
"""CoreDNS Sidecar Charm"""
"""CoreDNS Sidecar Charm."""

_stored = StoredState()

Expand All @@ -39,9 +33,7 @@ def __init__(self, *args):
metrics = ServicePort(9153, protocol="TCP", name="metrics")
self.service_patcher = KubernetesServicePatch(self, [dns_udp, dns_tcp, metrics])

self.framework.observe(
self.on.coredns_pebble_ready, self._on_coredns_pebble_ready
)
self.framework.observe(self.on.coredns_pebble_ready, self._on_coredns_pebble_ready)
self.framework.observe(self.on.config_changed, self._on_config_changed)
self.framework.observe(
self.on.dns_provider_relation_created,
Expand All @@ -52,7 +44,7 @@ def __init__(self, *args):

@property
def is_running(self):
"""Determine if a given service is running in a given container"""
"""Determine if a given service is running in a given container."""
try:
container = self.unit.get_container(self.meta.name)
service = container.get_service(self.meta.name)
Expand All @@ -61,7 +53,7 @@ def is_running(self):
return service.current == ServiceStatus.ACTIVE

def _on_coredns_pebble_ready(self, event):
"""Define and start CoreDNS workload"""
"""Define and start CoreDNS workload."""
container = event.workload
if self.is_running:
logger.info("CoreDNS already started")
Expand All @@ -76,7 +68,7 @@ def _on_coredns_pebble_ready(self, event):
self._on_update_status(event)

def _on_config_changed(self, event):
"""Process charm config changes and restart CoreDNS workload"""
"""Process charm config changes and restart CoreDNS workload."""
container = self.unit.get_container(self.meta.name)
if not self.is_running:
logger.info("CoreDNS is not running")
Expand All @@ -101,13 +93,11 @@ def _on_config_changed(self, event):
self._on_update_status(event)

def _on_dns_provider_relation_created(self, event):
"""Provide relation data on dns-provider relation created"""
"""Provide relation data on dns-provider relation created."""
if self.unit.is_leader():
ingress_address = event.relation.data[self.unit].get("ingress-address")
if not ingress_address:
logger.info(
"ingress-address is not present in relation data, deferring"
)
logger.info("ingress-address is not present in relation data, deferring")
self.unit.status = MaintenanceStatus("Waiting on ingress-address")
event.defer()
return
Expand All @@ -122,7 +112,7 @@ def _on_dns_provider_relation_created(self, event):
self._on_update_status(event)

def _on_update_status(self, event):
"""Update Juju status"""
"""Update Juju status."""
if not self.is_running:
self.unit.status = WaitingStatus("CoreDNS is not running")
elif self._stored.forbidden:
Expand All @@ -131,7 +121,7 @@ def _on_update_status(self, event):
self.unit.status = ActiveStatus()

def _coredns_layer(self):
"""Pebble config layer for CoreDNS"""
"""Pebble config layer for CoreDNS."""
return {
"summary": "CoreDNS layer",
"description": "pebble config layer for CoreDNS",
Expand All @@ -146,7 +136,7 @@ def _coredns_layer(self):
}

def _push_corefile_config(self, event):
"""Push corefile config to CoreDNS container"""
"""Push corefile config to CoreDNS container."""
container = self.unit.get_container(self.meta.name)
corefile = Template(self.model.config["corefile"])
corefile = corefile.safe_substitute(self.model.config)
Expand Down Expand Up @@ -174,9 +164,7 @@ def _patch_statefulset(self):
return
logger.info(f"Patching Default dnsPolicy for {self.meta.name} statefulset")
patch = {"spec": {"template": {"spec": {"dnsPolicy": "Default"}}}}
self.client.patch(
StatefulSet, name=self.meta.name, namespace=self.model.name, obj=patch
)
self.client.patch(StatefulSet, name=self.meta.name, namespace=self.model.name, obj=patch)


if __name__ == "__main__":
Expand Down
15 changes: 4 additions & 11 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ async def charmed_kubernetes(ops_test):
if "kubernetes-control-plane" in app.charm_url
]
if not control_plane_apps:
pytest.fail(
f"Model {current_model} doesn't contain {control_plane_app} charm"
)
pytest.fail(f"Model {current_model} doesn't contain {control_plane_app} charm")
deploy, control_plane_app = False, control_plane_apps[0]

if deploy:
Expand Down Expand Up @@ -74,8 +72,7 @@ def module_name(request):

@pytest.fixture(scope="module")
async def k8s_cloud(charmed_kubernetes, ops_test, request, module_name):
"""Use an existing k8s-cloud or create a k8s-cloud
for deploying a new k8s model into"""
"""Use an existing k8s-cloud or create a k8s-cloud for deploying a new k8s model into."""
cloud_name = request.config.option.k8s_cloud or f"{module_name}-k8s-cloud"
controller = await ops_test.model.get_controller()
current_clouds = await controller.clouds()
Expand Down Expand Up @@ -176,9 +173,7 @@ async def related(ops_test, coredns_model):

log.info("Consuming CMR offer")
log.info(f"{machine_model_name} consuming CMR offer from {coredns_model_name}")
saas = await ops_test.model.consume(
f"{model_owner}/{coredns_model_name}.{app_name}"
)
saas = await ops_test.model.consume(f"{model_owner}/{coredns_model_name}.{app_name}")
log.info("Relating ...")
await ops_test.model.add_relation(k8s_cp.name, f"{app_name}:dns-provider")
with ops_test.model_context(k8s_alias) as coredns_model:
Expand Down Expand Up @@ -212,9 +207,7 @@ def validate_dns_pod(ops_test, k8s_client):
for obj in spec:
client.create(obj)

client.wait(
Pod, "validate-dns", namespace=namespace, for_conditions=("ContainersReady",)
)
client.wait(Pod, "validate-dns", namespace=namespace, for_conditions=("ContainersReady",))
yield
log.info("Removing DNS validation pod ...")
for obj in spec:
Expand Down
12 changes: 3 additions & 9 deletions tests/integration/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ async def test_internal_resolution(self, ops_test, k8s_client, coredns_ip):
"nslookup",
"kubernetes.default.svc.cluster.local",
)
assert (
f"Server:\t\t{coredns_ip}" in stdout
), f"stdout: {stdout}\n stderr: {stderr}"
assert f"Server:\t\t{coredns_ip}" in stdout, f"stdout: {stdout}\n stderr: {stderr}"
assert (
"kubernetes.default.svc.cluster.local" in stdout
), f"stdout: {stdout}\n stderr: {stderr}"
Expand All @@ -75,10 +73,6 @@ async def test_external_resolution(self, ops_test, k8s_client, coredns_ip):
"nslookup",
"www.ubuntu.com",
)
assert (
f"Server:\t\t{coredns_ip}" in stdout
), f"stdout: {stdout}\n stderr: {stderr}"
assert (
"Non-authoritative answer" in stdout
), f"stdout: {stdout}\n stderr: {stderr}"
assert f"Server:\t\t{coredns_ip}" in stdout, f"stdout: {stdout}\n stderr: {stderr}"
assert "Non-authoritative answer" in stdout, f"stdout: {stdout}\n stderr: {stderr}"
assert rc == 0
3 changes: 1 addition & 2 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from unittest.mock import patch

import pytest
from charm import CoreDNSCharm
from ops.pebble import ServiceStatus
from ops.testing import Harness

from charm import CoreDNSCharm


# Autouse to prevent calling out to the k8s API via lightkube
@pytest.fixture(autouse=True)
Expand Down
4 changes: 1 addition & 3 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ def test_dns_provider_relation_created_not_running(
assert relation_harness.model.unit.status.name == "waiting"


def test_domain_changed(
relation_harness, relation_with_ingress, active_service, mocker
):
def test_domain_changed(relation_harness, relation_with_ingress, active_service, mocker):
container = relation_harness.model.unit.get_container("coredns")
container.get_service = mocker.MagicMock(return_value=active_service)
container.push = mocker.MagicMock()
Expand Down
77 changes: 34 additions & 43 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,68 +1,59 @@
[flake8]
max-line-length = 88

[isort]
profile = black

# Copyright 2023 Canonical
# See LICENSE file for licensing details.

[tox]
skipsdist = True
envlist = lint,unit
no_package = True
skip_missing_interpreters = True
env_list = format, lint, unit
min_version = 4.0.0

[vars]
cov_path = {toxinidir}/htmlcov
src_path = {toxinidir}/src/
tst_path = {toxinidir}/tests/
upstream_path = {toxinidir}/upstream/
tst_data_path = {toxinidir}/tests/data/
all_path = {[vars]src_path} {[vars]tst_path} {[vars]upstream_path}


[pytest]
log_cli = 1
log_cli_level = CRITICAL
log_cli_format = %(message)s
log_file_level = INFO
log_file_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_file_date_format=%Y-%m-%d %H:%M:%S

src_path = {tox_root}/src
tests_path = {tox_root}/tests
all_path = {[vars]src_path} {[vars]tests_path}

[testenv]
basepython = python3
setenv =
PYTHONPATH={toxinidir}/src
PYTHONBREAKPOINT=ipdb.set_trace
passenv = HOME,KUBECONFIG

[testenv:lint]
deps =
-rrequirements-test.txt
commands =
codespell {[vars]all_path}
ruff {[vars]all_path}
isort --check-only --diff {[vars]all_path}
black --check --diff {[vars]all_path}
set_env =
PYTHONPATH = {tox_root}/lib:{[vars]src_path}
PYTHONBREAKPOINT=pdb.set_trace
PY_COLORS=1
pass_env =
PYTHONPATH
CHARM_BUILD_DIR
MODEL_SETTINGS

[testenv:format]
description = Apply coding style standards to code
deps =
-rrequirements-test.txt
black
ruff
commands =
isort {[vars]all_path}
black {[vars]all_path}
ruff --fix {[vars]all_path}

[testenv:lint]
description = Check code against coding style standards
deps =
black
ruff
codespell
commands =
codespell {tox_root}
ruff {[vars]all_path}
black --check --diff {[vars]all_path}

[testenv:unit]
deps =
-rrequirements-test.txt
-r requirements-test.txt
setenv =
PYTHONPATH={toxinidir}:{toxinidir}/lib:{toxinidir}/src
commands =
pytest --cov=charm --cov-report=term-missing --asyncio-mode=auto --tb native -s {posargs:tests/unit}

[testenv:integration]
deps =
-rrequirements-test.txt
-r requirements-test.txt
setenv =
PYTHONPATH={toxinidir}:{toxinidir}/lib:{toxinidir}/src
commands =
pytest --log-cli-level=INFO --asyncio-mode=auto --tb native -s tests/integration {posargs}
pytest --log-cli-level=INFO --asyncio-mode=auto --tb native -s tests/integration {posargs} {[vars]tests_path}/integration

0 comments on commit e6ce3ea

Please sign in to comment.