diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8b3d5ba77..9073ce1c3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,8 +49,8 @@ jobs: terraform fmt terraform validate - name: lint test charm module - working-directory: ./terraform/tests - run: | + working-directory: ./terraform/tests + run: | terraform init terraform fmt terraform validate @@ -77,7 +77,7 @@ jobs: - name: Terraform deploy working-directory: ./terraform/tests/ run: | - terraform apply -var "model=test" -target null_resource.simple_deployment_juju_wait_deployment -auto-approve + terraform apply -var "model_name=test" -target null_resource.simple_deployment_juju_wait_deployment -auto-approve lib-check: name: Check libraries diff --git a/lib/charms/grafana_agent/v0/cos_agent.py b/lib/charms/grafana_agent/v0/cos_agent.py index c57e3f059..1ea79a625 100644 --- a/lib/charms/grafana_agent/v0/cos_agent.py +++ b/lib/charms/grafana_agent/v0/cos_agent.py @@ -22,7 +22,6 @@ Using the `COSAgentProvider` object only requires instantiating it, typically in the `__init__` method of your charm (the one which sends telemetry). -The constructor of `COSAgentProvider` has only one required and ten optional parameters: ```python def __init__( @@ -235,10 +234,10 @@ def __init__(self, *args): import pydantic from cosl import GrafanaDashboard, JujuTopology from cosl.rules import AlertRules -from ops import CharmBase from ops.charm import RelationChangedEvent from ops.framework import EventBase, EventSource, Object, ObjectEvents from ops.model import ModelError, Relation +from ops.testing import CharmType if TYPE_CHECKING: try: @@ -253,7 +252,7 @@ class _MetricsEndpointDict(TypedDict): LIBID = "dc15fa84cef84ce58155fb84f6c6213a" LIBAPI = 0 -LIBPATCH = 11 +LIBPATCH = 12 PYDEPS = ["cosl", "pydantic"] @@ -468,7 +467,7 @@ def dump(self, databag: Optional[MutableMapping] = None, clear: bool = True): return databag -class CosAgentProviderUnitData(DatabagModel): # pyright: ignore [reportGeneralTypeIssues] +class CosAgentProviderUnitData(DatabagModel): """Unit databag model for `cos-agent` relation.""" # The following entries are the same for all units of the same principal. @@ -495,7 +494,7 @@ class CosAgentProviderUnitData(DatabagModel): # pyright: ignore [reportGeneralT KEY: ClassVar[str] = "config" -class CosAgentPeersUnitData(DatabagModel): # pyright: ignore [reportGeneralTypeIssues] +class CosAgentPeersUnitData(DatabagModel): """Unit databag model for `peers` cos-agent machine charm peer relation.""" # We need the principal unit name and relation metadata to be able to render identifiers @@ -594,9 +593,7 @@ class Receiver(pydantic.BaseModel): ) -class CosAgentRequirerUnitData( - DatabagModel -): # pyright: ignore [reportGeneralTypeIssues] # noqa: D101 +class CosAgentRequirerUnitData(DatabagModel): # noqa: D101 """Application databag model for the COS-agent requirer.""" receivers: List[Receiver] = pydantic.Field( @@ -610,7 +607,7 @@ class COSAgentProvider(Object): def __init__( self, - charm: CharmBase, + charm: CharmType, relation_name: str = DEFAULT_RELATION_NAME, metrics_endpoints: Optional[List["_MetricsEndpointDict"]] = None, metrics_rules_dir: str = "./src/prometheus_alert_rules", @@ -879,7 +876,7 @@ class COSAgentRequirer(Object): def __init__( self, - charm: CharmBase, + charm: CharmType, *, relation_name: str = DEFAULT_RELATION_NAME, peer_relation_name: str = DEFAULT_PEER_RELATION_NAME, diff --git a/lib/charms/operator_libs_linux/v2/snap.py b/lib/charms/operator_libs_linux/v2/snap.py index 9d09a78d3..d14f864fd 100644 --- a/lib/charms/operator_libs_linux/v2/snap.py +++ b/lib/charms/operator_libs_linux/v2/snap.py @@ -64,6 +64,7 @@ import socket import subprocess import sys +import time import urllib.error import urllib.parse import urllib.request @@ -83,7 +84,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 7 +LIBPATCH = 9 # Regex to locate 7-bit C1 ANSI sequences @@ -332,7 +333,7 @@ def get(self, key: Optional[str], *, typed: bool = False) -> Any: return self._snap("get", [key]).strip() - def set(self, config: Dict[str, Any], *, typed: bool = False) -> str: + def set(self, config: Dict[str, Any], *, typed: bool = False) -> None: """Set a snap configuration value. Args: @@ -340,11 +341,9 @@ def set(self, config: Dict[str, Any], *, typed: bool = False) -> str: typed: set to True to convert all values in the config into typed values while configuring the snap (set with typed=True). Default is not to convert. """ - if typed: - kv = [f"{key}={json.dumps(val)}" for key, val in config.items()] - return self._snap("set", ["-t"] + kv) - - return self._snap("set", [f"{key}={val}" for key, val in config.items()]) + if not typed: + config = {k: str(v) for k, v in config.items()} + self._snap_client._put_snap_conf(self._name, config) def unset(self, key) -> str: """Unset a snap configuration value. @@ -770,7 +769,33 @@ def _request( headers["Content-Type"] = "application/json" response = self._request_raw(method, path, query, headers, data) - return json.loads(response.read().decode())["result"] + response = json.loads(response.read().decode()) + if response["type"] == "async": + return self._wait(response["change"]) + return response["result"] + + def _wait(self, change_id: str, timeout=300) -> JSONType: + """Wait for an async change to complete. + + The poll time is 100 milliseconds, the same as in snap clients. + """ + deadline = time.time() + timeout + while True: + if time.time() > deadline: + raise TimeoutError(f"timeout waiting for snap change {change_id}") + response = self._request("GET", f"changes/{change_id}") + status = response["status"] + if status == "Done": + return response.get("data") + if status == "Doing" or status == "Do": + time.sleep(0.1) + continue + if status == "Wait": + logger.warning("snap change %s succeeded with status 'Wait'", change_id) + return response.get("data") + raise SnapError( + f"snap change {response.get('kind')!r} id {change_id} failed with status {status}" + ) def _request_raw( self, @@ -818,6 +843,10 @@ def get_installed_snap_apps(self, name: str) -> List: """Query the snap server for apps belonging to a named, currently installed snap.""" return self._request("GET", "apps", {"names": name, "select": "service"}) + def _put_snap_conf(self, name: str, conf: Dict[str, Any]): + """Set the configuration details for an installed snap.""" + return self._request("PUT", f"snaps/{name}/conf", body=conf) + class SnapCache(Mapping): """An abstraction to represent installed/available packages. diff --git a/lib/charms/tls_certificates_interface/v3/tls_certificates.py b/lib/charms/tls_certificates_interface/v3/tls_certificates.py index 141412b00..8cab71336 100644 --- a/lib/charms/tls_certificates_interface/v3/tls_certificates.py +++ b/lib/charms/tls_certificates_interface/v3/tls_certificates.py @@ -318,7 +318,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 23 +LIBPATCH = 24 PYDEPS = ["cryptography", "jsonschema"] @@ -526,7 +526,7 @@ def chain_as_pem(self) -> str: class CertificateExpiringEvent(EventBase): """Charm Event triggered when a TLS certificate is almost expired.""" - def __init__(self, handle, certificate: str, expiry: str): + def __init__(self, handle: Handle, certificate: str, expiry: str): """CertificateExpiringEvent. Args: diff --git a/terraform/main.tf b/terraform/main.tf index bf330f69c..c8c6b5fdf 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -7,7 +7,7 @@ resource "juju_application" "mongodb" { name = "mongodb" channel = var.channel revision = var.revision - base = "ubuntu@22.04" + base = "ubuntu@22.04" } config = var.config model = var.model diff --git a/terraform/tests/simple_deployment.tf b/terraform/tests/simple_deployment.tf index 74e3ba041..a3e30d584 100644 --- a/terraform/tests/simple_deployment.tf +++ b/terraform/tests/simple_deployment.tf @@ -3,7 +3,7 @@ module "mongodb" { app_name = var.app_name model = var.model_name units = var.simple_mongodb_units - channel = "6/edge" + channel = "6/edge" } resource "juju_integration" "simple_deployment_tls-operator_mongodb-integration" {