diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..5ce31d9
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,32 @@
+Applicable spec:
+
+### Overview
+
+
+
+### Rationale
+
+
+
+### Juju Events Changes
+
+
+
+### Module Changes
+
+
+
+### Library Changes
+
+
+
+### Checklist
+
+- [ ] The [charm style guide](https://juju.is/docs/sdk/styleguide) was applied
+- [ ] The [contributing guide](https://github.com/canonical/is-charms-contributing-guide) was applied
+- [ ] The changes are compliant with [ISD054 - Managing Charm Complexity](https://discourse.charmhub.io/t/specification-isd014-managing-charm-complexity/11619)
+- [ ] The documentation is generated using `src-docs`
+- [ ] The documentation for charmhub is updated
+- [ ] The PR is tagged with appropriate label (`urgent`, `trivial`, `complex`)
+
+
diff --git a/actions.yaml b/actions.yaml
index 45d0228..9e9140f 100644
--- a/actions.yaml
+++ b/actions.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
report-visits-by-ip:
diff --git a/charmcraft.yaml b/charmcraft.yaml
index 9127512..4c562bf 100644
--- a/charmcraft.yaml
+++ b/charmcraft.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
type: charm
diff --git a/config.yaml b/config.yaml
index 424d33d..60e1e83 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
options:
diff --git a/content-cache_rock/entrypoint.sh b/content-cache_rock/entrypoint.sh
index c9f92b4..1bff63b 100755
--- a/content-cache_rock/entrypoint.sh
+++ b/content-cache_rock/entrypoint.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
set -eu
diff --git a/content-cache_rock/rockcraft.yaml b/content-cache_rock/rockcraft.yaml
index 83be8e0..66fce64 100644
--- a/content-cache_rock/rockcraft.yaml
+++ b/content-cache_rock/rockcraft.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
name: content-cache
summary: Content-cache-k8s ROCK image.
diff --git a/docs/how-to/contribute.md b/docs/how-to/contribute.md
index 31861d2..79e0196 100644
--- a/docs/how-to/contribute.md
+++ b/docs/how-to/contribute.md
@@ -56,6 +56,15 @@ Note that the [content-cache-image](content-cache.Dockerfile) image needs to be
* `tox -e integration`: Runs the integration tests.
+### Generating src docs for every commit
+
+Run the following command:
+
+```bash
+echo -e "tox -e src-docs\ngit add src-docs\n" >> .git/hooks/pre-commit
+chmod +x .git/hooks/pre-commit
+```
+
## Build charm
Build the charm in this git repository using:
diff --git a/generate-src-docs.sh b/generate-src-docs.sh
new file mode 100644
index 0000000..d13066a
--- /dev/null
+++ b/generate-src-docs.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+# Copyright 2024 Canonical Ltd.
+# See LICENSE file for licensing details.
+
+lazydocs --no-watermark --output-path src-docs src/*
diff --git a/lib/charms/loki_k8s/v0/loki_push_api.py b/lib/charms/loki_k8s/v0/loki_push_api.py
index 9f9372d..0df057c 100644
--- a/lib/charms/loki_k8s/v0/loki_push_api.py
+++ b/lib/charms/loki_k8s/v0/loki_push_api.py
@@ -12,9 +12,9 @@
implement the provider side of the `loki_push_api` relation interface. For instance, a Loki charm.
The provider side of the relation represents the server side, to which logs are being pushed.
-- `LokiPushApiConsumer`: This object is meant to be used by any Charmed Operator that needs to
-send log to Loki by implementing the consumer side of the `loki_push_api` relation interface.
-For instance, a Promtail or Grafana agent charm which needs to send logs to Loki.
+- `LokiPushApiConsumer`: Used to obtain the loki api endpoint. This is useful for configuring
+ applications such as pebble, or charmed operators of workloads such as grafana-agent or promtail,
+ that can communicate with loki directly.
- `LogProxyConsumer`: This object can be used by any Charmed Operator which needs to
send telemetry, such as logs, to Loki through a Log Proxy by implementing the consumer side of the
@@ -480,7 +480,7 @@ def _alert_rules_error(self, event):
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
-LIBPATCH = 22
+LIBPATCH = 24
logger = logging.getLogger(__name__)
@@ -604,7 +604,9 @@ def _validate_relation_by_interface_and_direction(
actual_relation_interface = relation.interface_name
if actual_relation_interface != expected_relation_interface:
raise RelationInterfaceMismatchError(
- relation_name, expected_relation_interface, actual_relation_interface
+ relation_name,
+ expected_relation_interface,
+ actual_relation_interface, # pyright: ignore
)
if expected_relation_role == RelationRole.provides:
@@ -866,20 +868,20 @@ def _from_dir(self, dir_path: Path, recursive: bool) -> List[dict]:
return alert_groups
- def add_path(self, path: str, *, recursive: bool = False):
+ def add_path(self, path_str: str, *, recursive: bool = False):
"""Add rules from a dir path.
All rules from files are aggregated into a data structure representing a single rule file.
All group names are augmented with juju topology.
Args:
- path: either a rules file or a dir of rules files.
+ path_str: either a rules file or a dir of rules files.
recursive: whether to read files recursively or not (no impact if `path` is a file).
Raises:
InvalidAlertRulePathError: if the provided path is invalid.
"""
- path = Path(path) # type: Path
+ path = Path(path_str) # type: Path
if path.is_dir():
self.alert_groups.extend(self._from_dir(path, recursive))
elif path.is_file():
@@ -992,6 +994,8 @@ def __init__(self, handle, relation, relation_id, app=None, unit=None):
def snapshot(self) -> Dict:
"""Save event information."""
+ if not self.relation:
+ return {}
snapshot = {"relation_name": self.relation.name, "relation_id": self.relation.id}
if self.app:
snapshot["app_name"] = self.app.name
@@ -1052,7 +1056,7 @@ class LokiPushApiEvents(ObjectEvents):
class LokiPushApiProvider(Object):
"""A LokiPushApiProvider class."""
- on = LokiPushApiEvents()
+ on = LokiPushApiEvents() # pyright: ignore
def __init__(
self,
@@ -1146,11 +1150,11 @@ def _on_logging_relation_changed(self, event: HookEvent):
event: a `CharmEvent` in response to which the consumer
charm must update its relation data.
"""
- should_update = self._process_logging_relation_changed(event.relation)
+ should_update = self._process_logging_relation_changed(event.relation) # pyright: ignore
if should_update:
self.on.loki_push_api_alert_rules_changed.emit(
- relation=event.relation,
- relation_id=event.relation.id,
+ relation=event.relation, # pyright: ignore
+ relation_id=event.relation.id, # pyright: ignore
app=self._charm.app,
unit=self._charm.unit,
)
@@ -1517,7 +1521,7 @@ def loki_endpoints(self) -> List[dict]:
class LokiPushApiConsumer(ConsumerBase):
"""Loki Consumer class."""
- on = LokiPushApiEvents()
+ on = LokiPushApiEvents() # pyright: ignore
def __init__(
self,
@@ -1760,7 +1764,7 @@ class LogProxyConsumer(ConsumerBase):
role.
"""
- on = LogProxyEvents()
+ on = LogProxyEvents() # pyright: ignore
def __init__(
self,
@@ -1885,7 +1889,7 @@ def _on_relation_departed(self, _: RelationEvent) -> None:
self._container.stop(WORKLOAD_SERVICE_NAME)
self.on.log_proxy_endpoint_departed.emit()
- def _get_container(self, container_name: str = "") -> Container:
+ def _get_container(self, container_name: str = "") -> Container: # pyright: ignore
"""Gets a single container by name or using the only container running in the Pod.
If there is more than one container in the Pod a `PromtailDigestError` is emitted.
@@ -1959,7 +1963,9 @@ def _add_pebble_layer(self, workload_binary_path: str) -> None:
}
},
}
- self._container.add_layer(self._container_name, pebble_layer, combine=True)
+ self._container.add_layer(
+ self._container_name, pebble_layer, combine=True # pyright: ignore
+ )
def _create_directories(self) -> None:
"""Creates the directories for Promtail binary and config file."""
@@ -1996,7 +2002,11 @@ def _push_binary_to_workload(self, binary_path: str, workload_binary_path: str)
"""
with open(binary_path, "rb") as f:
self._container.push(
- workload_binary_path, f, permissions=0o755, encoding=None, make_dirs=True
+ workload_binary_path,
+ f,
+ permissions=0o755,
+ encoding=None, # pyright: ignore
+ make_dirs=True,
)
logger.debug("The promtail binary file has been pushed to the workload container.")
diff --git a/lib/charms/nginx_ingress_integrator/v0/ingress.py b/lib/charms/nginx_ingress_integrator/v0/ingress.py
index 08dfe45..84857e7 100644
--- a/lib/charms/nginx_ingress_integrator/v0/ingress.py
+++ b/lib/charms/nginx_ingress_integrator/v0/ingress.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# Licensed under the Apache2.0. See LICENSE file in charm source for details.
"""Library for the ingress relation.
diff --git a/lib/charms/prometheus_k8s/v0/prometheus_scrape.py b/lib/charms/prometheus_k8s/v0/prometheus_scrape.py
index e4297aa..665af88 100644
--- a/lib/charms/prometheus_k8s/v0/prometheus_scrape.py
+++ b/lib/charms/prometheus_k8s/v0/prometheus_scrape.py
@@ -362,7 +362,7 @@ def _on_scrape_targets_changed(self, event):
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
-LIBPATCH = 42
+LIBPATCH = 44
PYDEPS = ["cosl"]
@@ -386,6 +386,7 @@ def _on_scrape_targets_changed(self, event):
"basic_auth",
"tls_config",
"authorization",
+ "params",
}
DEFAULT_JOB = {
"metrics_path": "/metrics",
@@ -764,7 +765,7 @@ def _validate_relation_by_interface_and_direction(
actual_relation_interface = relation.interface_name
if actual_relation_interface != expected_relation_interface:
raise RelationInterfaceMismatchError(
- relation_name, expected_relation_interface, actual_relation_interface
+ relation_name, expected_relation_interface, actual_relation_interface or "None"
)
if expected_relation_role == RelationRole.provides:
@@ -857,7 +858,7 @@ class MonitoringEvents(ObjectEvents):
class MetricsEndpointConsumer(Object):
"""A Prometheus based Monitoring service."""
- on = MonitoringEvents()
+ on = MonitoringEvents() # pyright: ignore
def __init__(self, charm: CharmBase, relation_name: str = DEFAULT_RELATION_NAME):
"""A Prometheus based Monitoring service.
@@ -1014,7 +1015,6 @@ def alerts(self) -> dict:
try:
scrape_metadata = json.loads(relation.data[relation.app]["scrape_metadata"])
identifier = JujuTopology.from_dict(scrape_metadata).identifier
- alerts[identifier] = self._tool.apply_label_matchers(alert_rules) # type: ignore
except KeyError as e:
logger.debug(
@@ -1029,6 +1029,10 @@ def alerts(self) -> dict:
)
continue
+ # We need to append the relation info to the identifier. This is to allow for cases for there are two
+ # relations which eventually scrape the same application. Issue #551.
+ identifier = f"{identifier}_{relation.name}_{relation.id}"
+
alerts[identifier] = alert_rules
_, errmsg = self._tool.validate_alert_rules(alert_rules)
@@ -1294,7 +1298,7 @@ def _resolve_dir_against_charm_path(charm: CharmBase, *path_elements: str) -> st
class MetricsEndpointProvider(Object):
"""A metrics endpoint for Prometheus."""
- on = MetricsEndpointProviderEvents()
+ on = MetricsEndpointProviderEvents() # pyright: ignore
def __init__(
self,
@@ -1836,14 +1840,16 @@ def _set_prometheus_data(self, event):
return
jobs = [] + _type_convert_stored(
- self._stored.jobs
+ self._stored.jobs # pyright: ignore
) # list of scrape jobs, one per relation
for relation in self.model.relations[self._target_relation]:
targets = self._get_targets(relation)
if targets and relation.app:
jobs.append(self._static_scrape_job(targets, relation.app.name))
- groups = [] + _type_convert_stored(self._stored.alert_rules) # list of alert rule groups
+ groups = [] + _type_convert_stored(
+ self._stored.alert_rules # pyright: ignore
+ ) # list of alert rule groups
for relation in self.model.relations[self._alert_rules_relation]:
unit_rules = self._get_alert_rules(relation)
if unit_rules and relation.app:
@@ -1895,7 +1901,7 @@ def set_target_job_data(self, targets: dict, app_name: str, **kwargs) -> None:
jobs.append(updated_job)
relation.data[self._charm.app]["scrape_jobs"] = json.dumps(jobs)
- if not _type_convert_stored(self._stored.jobs) == jobs:
+ if not _type_convert_stored(self._stored.jobs) == jobs: # pyright: ignore
self._stored.jobs = jobs
def _on_prometheus_targets_departed(self, event):
@@ -1947,7 +1953,7 @@ def remove_prometheus_jobs(self, job_name: str, unit_name: Optional[str] = ""):
relation.data[self._charm.app]["scrape_jobs"] = json.dumps(jobs)
- if not _type_convert_stored(self._stored.jobs) == jobs:
+ if not _type_convert_stored(self._stored.jobs) == jobs: # pyright: ignore
self._stored.jobs = jobs
def _job_name(self, appname) -> str:
@@ -2126,7 +2132,7 @@ def set_alert_rule_data(self, name: str, unit_rules: dict, label_rules: bool = T
groups.append(updated_group)
relation.data[self._charm.app]["alert_rules"] = json.dumps({"groups": groups})
- if not _type_convert_stored(self._stored.alert_rules) == groups:
+ if not _type_convert_stored(self._stored.alert_rules) == groups: # pyright: ignore
self._stored.alert_rules = groups
def _on_alert_rules_departed(self, event):
@@ -2176,7 +2182,7 @@ def remove_alert_rules(self, group_name: str, unit_name: str) -> None:
json.dumps({"groups": groups}) if groups else "{}"
)
- if not _type_convert_stored(self._stored.alert_rules) == groups:
+ if not _type_convert_stored(self._stored.alert_rules) == groups: # pyright: ignore
self._stored.alert_rules = groups
def _get_alert_rules(self, relation) -> dict:
diff --git a/metadata.yaml b/metadata.yaml
index 1715f46..6b9e6cf 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
name: content-cache-k8s
diff --git a/pyproject.toml b/pyproject.toml
index 91ace93..65149b7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
[tool.bandit]
diff --git a/requirements.txt b/requirements.txt
index 408016c..9684eab 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
+cosl
ops>=1.2.0
tabulate
diff --git a/src-docs/charm.py.md b/src-docs/charm.py.md
new file mode 100644
index 0000000..e7cac91
--- /dev/null
+++ b/src-docs/charm.py.md
@@ -0,0 +1,98 @@
+
+
+
+
+# module `charm.py`
+Charm for Content-cache on Kubernetes.
+
+**Global Variables**
+---------------
+- **REQUIRED_INGRESS_RELATION_FIELDS**
+- **CACHE_PATH**
+- **CONTAINER_NAME**
+- **EXPORTER_CONTAINER_NAME**
+- **CONTAINER_PORT**
+- **REQUIRED_JUJU_CONFIGS**
+
+
+---
+
+## class `ContentCacheCharm`
+Charm the service.
+
+Attrs: on: Ingress Charm Events ERROR_LOG_PATH: NGINX error log ACCESS_LOG_PATH: NGINX access log _metrics_endpoint: Provider of metrics for Prometheus charm _logging: Requirer of logs for Loki charm _grafana_dashboards: Dashboard Provider for Grafana charm ingress_proxy_provides: Ingress proxy provider ingress: Ingress requirer unit: Charm's designated juju unit model: Charm's designated juju model
+
+
+
+### function `__init__`
+
+```python
+__init__(*args)
+```
+
+Init function for the charm.
+
+
+
+**Args:**
+
+ - `args`: Variable list of positional arguments passed to the parent constructor.
+
+
+---
+
+#### property app
+
+Application that this unit is part of.
+
+---
+
+#### property charm_dir
+
+Root directory of the charm as it is running.
+
+---
+
+#### property config
+
+A mapping containing the charm's config and current values.
+
+---
+
+#### property meta
+
+Metadata of this charm.
+
+---
+
+#### property model
+
+Shortcut for more simple access the model.
+
+---
+
+#### property unit
+
+Unit that this execution is responsible for.
+
+
+
+---
+
+
+
+### function `configure_workload_container`
+
+```python
+configure_workload_container(event: ConfigChangedEvent) → None
+```
+
+Configure/set up workload container inside pod.
+
+
+
+**Args:**
+
+ - `event`: config-changed event.
+
+
diff --git a/src-docs/file_reader.py.md b/src-docs/file_reader.py.md
new file mode 100644
index 0000000..87566b9
--- /dev/null
+++ b/src-docs/file_reader.py.md
@@ -0,0 +1,32 @@
+
+
+
+
+# module `file_reader.py`
+Short module for file reverse reading.
+
+
+---
+
+
+
+## function `readlines_reverse`
+
+```python
+readlines_reverse(qfile) → Generator[int, NoneType, NoneType]
+```
+
+Read the lines of a file in reverse order in a lazy way.
+
+
+
+**Args:**
+
+ - `qfile`: File in StringIO format.
+
+
+
+**Yields:**
+ A row from the read file.
+
+
diff --git a/src/charm.py b/src/charm.py
index ab7fef8..7ed86bf 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""Charm for Content-cache on Kubernetes."""
diff --git a/src/file_reader.py b/src/file_reader.py
index dc6aa2f..efef6fc 100644
--- a/src/file_reader.py
+++ b/src/file_reader.py
@@ -1,6 +1,6 @@
"""Short module for file reverse reading."""
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
import os
diff --git a/tests/conftest.py b/tests/conftest.py
index 5ef2d83..dd75ad3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""General configuration module for tests."""
diff --git a/tests/integration/any_charm.py b/tests/integration/any_charm.py
index 441d219..eae765a 100644
--- a/tests/integration/any_charm.py
+++ b/tests/integration/any_charm.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""This code snippet is used to be loaded into any-charm which is used for integration tests."""
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index 5e35bb5..5108e22 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""General configuration module for integration tests."""
diff --git a/tests/integration/test_core.py b/tests/integration/test_core.py
index 07ffeab..cdb1f46 100644
--- a/tests/integration/test_core.py
+++ b/tests/integration/test_core.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""Integration test module."""
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
index 42d8828..ea6e138 100644
--- a/tests/unit/test_charm.py
+++ b/tests/unit/test_charm.py
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
import copy
import io
diff --git a/tox.ini b/tox.ini
index bb5d90c..a379122 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
[tox]
@@ -72,6 +72,19 @@ commands =
-m pytest --ignore={[vars]tst_path}integration -v --tb native -s {posargs}
coverage report
+[testenv:src-docs]
+allowlist_externals=sh
+setenv =
+ PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path}
+description = Generate documentation for src
+deps =
+ cosl
+ lazydocs
+ -r{toxinidir}/requirements.txt
+commands =
+ ; can't run lazydocs directly due to needing to run it on src/* which produces an invocation error in tox
+ sh generate-src-docs.sh
+
[testenv:coverage-report]
description = Create test coverage report
deps =
diff --git a/trivy.yaml b/trivy.yaml
index 11603b1..2062a5c 100644
--- a/trivy.yaml
+++ b/trivy.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Canonical Ltd.
+# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
exit-code: 0
timeout: 20m