diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 64936605..7fa64e2c 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -8,11 +8,6 @@ jobs: uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main secrets: inherit with: - chaos-app-label: app.kubernetes.io/name=discourse-k8s - chaos-duration: 600 - chaos-enabled: false - chaos-experiments: pod-delete - chaos-status-duration: 300 extra-arguments: --localstack-address 172.17.0.1 -m "not (requires_secrets)" pre-run-script: localstack-installation.sh trivy-image-config: "trivy.yaml" diff --git a/.github/workflows/integration_test_with_secrets.yaml b/.github/workflows/integration_test_with_secrets.yaml index 6f47ac99..fa745931 100644 --- a/.github/workflows/integration_test_with_secrets.yaml +++ b/.github/workflows/integration_test_with_secrets.yaml @@ -8,11 +8,6 @@ jobs: uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main secrets: inherit with: - chaos-app-label: app.kubernetes.io/name=discourse-k8s - chaos-duration: 600 - chaos-enabled: false - chaos-experiments: pod-delete - chaos-status-duration: 300 extra-arguments: --localstack-address 172.17.0.1 -m "requires_secrets" pre-run-script: localstack-installation.sh trivy-image-config: "trivy.yaml" diff --git a/discourse_rock/rockcraft.yaml b/discourse_rock/rockcraft.yaml index 6d6a52e0..89b27f9d 100644 --- a/discourse_rock/rockcraft.yaml +++ b/discourse_rock/rockcraft.yaml @@ -5,7 +5,7 @@ name: discourse summary: Discourse rock description: Discourse OCI image for the Discourse charm base: ubuntu@22.04 -# renovate: base: ubuntu:22.04@sha256:e6173d4dc55e76b87c4af8db8821b1feae4146dd47341e4d431118c7dd060a74 +# renovate: base: ubuntu:22.04@sha256:e9569c25505f33ff72e88b2990887c9dcf230f23259da296eb814fc2b41af999 run-user: _daemon_ # UID/GID 584792 license: Apache-2.0 version: "1.0" diff --git a/lib/charms/data_platform_libs/v0/data_interfaces.py b/lib/charms/data_platform_libs/v0/data_interfaces.py index fbe98972..c940cc00 100644 --- a/lib/charms/data_platform_libs/v0/data_interfaces.py +++ b/lib/charms/data_platform_libs/v0/data_interfaces.py @@ -320,7 +320,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 26 +LIBPATCH = 27 PYDEPS = ["ops>=2.0.0"] @@ -422,15 +422,15 @@ def diff(event: RelationChangedEvent, bucket: Union[Unit, Application]) -> Diff: ) # These are the keys that were added to the databag and triggered this event. - added = new_data.keys() - old_data.keys() # pyright: ignore [reportGeneralTypeIssues] + added = new_data.keys() - old_data.keys() # pyright: ignore [reportAssignmentType] # These are the keys that were removed from the databag and triggered this event. - deleted = old_data.keys() - new_data.keys() # pyright: ignore [reportGeneralTypeIssues] + deleted = old_data.keys() - new_data.keys() # pyright: ignore [reportAssignmentType] # These are the keys that already existed in the databag, # but had their values changed. changed = { key - for key in old_data.keys() & new_data.keys() # pyright: ignore [reportGeneralTypeIssues] - if old_data[key] != new_data[key] # pyright: ignore [reportGeneralTypeIssues] + for key in old_data.keys() & new_data.keys() # pyright: ignore [reportAssignmentType] + if old_data[key] != new_data[key] # pyright: ignore [reportAssignmentType] } # Convert the new_data to a serializable format and save it for a next diff check. set_encoded_field(event.relation, bucket, "data", new_data) @@ -1619,7 +1619,8 @@ def _delete_relation_data(self, relation: Relation, fields: List[str]) -> None: current_data.get(relation.id, []) ): logger.error( - "Non-existing secret %s was attempted to be removed.", non_existent + "Non-existing secret %s was attempted to be removed.", + ", ".join(non_existent), ) _, normal_fields = self._process_secret_fields( @@ -1686,12 +1687,8 @@ def extra_user_roles(self) -> Optional[str]: return self.relation.data[self.relation.app].get("extra-user-roles") -class AuthenticationEvent(RelationEvent): - """Base class for authentication fields for events. - - The amount of logic added here is not ideal -- but this was the only way to preserve - the interface when moving to Juju Secrets - """ +class RelationEventWithSecret(RelationEvent): + """Base class for Relation Events that need to handle secrets.""" @property def _secrets(self) -> dict: @@ -1703,18 +1700,6 @@ def _secrets(self) -> dict: self._cached_secrets = {} return self._cached_secrets - @property - def _jujuversion(self) -> JujuVersion: - """Caching jujuversion to avoid a Juju call on each field evaluation. - - DON'T USE the encapsulated helper variable outside of this function - """ - if not hasattr(self, "_cached_jujuversion"): - self._cached_jujuversion = None - if not self._cached_jujuversion: - self._cached_jujuversion = JujuVersion.from_environ() - return self._cached_jujuversion - def _get_secret(self, group) -> Optional[Dict[str, str]]: """Retrieveing secrets.""" if not self.app: @@ -1730,7 +1715,15 @@ def _get_secret(self, group) -> Optional[Dict[str, str]]: @property def secrets_enabled(self): """Is this Juju version allowing for Secrets usage?""" - return self._jujuversion.has_secrets + return JujuVersion.from_environ().has_secrets + + +class AuthenticationEvent(RelationEventWithSecret): + """Base class for authentication fields for events. + + The amount of logic added here is not ideal -- but this was the only way to preserve + the interface when moving to Juju Secrets + """ @property def username(self) -> Optional[str]: @@ -1813,7 +1806,7 @@ class DatabaseProvidesEvents(CharmEvents): database_requested = EventSource(DatabaseRequestedEvent) -class DatabaseRequiresEvent(RelationEvent): +class DatabaseRequiresEvent(RelationEventWithSecret): """Base class for database events.""" @property @@ -1868,6 +1861,11 @@ def uris(self) -> Optional[str]: if not self.relation.app: return None + if self.secrets_enabled: + secret = self._get_secret("user") + if secret: + return secret.get("uris") + return self.relation.data[self.relation.app].get("uris") @property @@ -1911,7 +1909,7 @@ class DatabaseRequiresEvents(CharmEvents): class DatabaseProvides(DataProvides): """Provider-side of the database relations.""" - on = DatabaseProvidesEvents() # pyright: ignore [reportGeneralTypeIssues] + on = DatabaseProvidesEvents() # pyright: ignore [reportAssignmentType] def __init__(self, charm: CharmBase, relation_name: str) -> None: super().__init__(charm, relation_name) @@ -2006,7 +2004,7 @@ def set_version(self, relation_id: int, version: str) -> None: class DatabaseRequires(DataRequires): """Requires-side of the database relation.""" - on = DatabaseRequiresEvents() # pyright: ignore [reportGeneralTypeIssues] + on = DatabaseRequiresEvents() # pyright: ignore [reportAssignmentType] def __init__( self, @@ -2335,7 +2333,7 @@ class KafkaRequiresEvents(CharmEvents): class KafkaProvides(DataProvides): """Provider-side of the Kafka relation.""" - on = KafkaProvidesEvents() # pyright: ignore [reportGeneralTypeIssues] + on = KafkaProvidesEvents() # pyright: ignore [reportAssignmentType] def __init__(self, charm: CharmBase, relation_name: str) -> None: super().__init__(charm, relation_name) @@ -2396,7 +2394,7 @@ def set_zookeeper_uris(self, relation_id: int, zookeeper_uris: str) -> None: class KafkaRequires(DataRequires): """Requires-side of the Kafka relation.""" - on = KafkaRequiresEvents() # pyright: ignore [reportGeneralTypeIssues] + on = KafkaRequiresEvents() # pyright: ignore [reportAssignmentType] def __init__( self, @@ -2533,7 +2531,7 @@ class OpenSearchRequiresEvents(CharmEvents): class OpenSearchProvides(DataProvides): """Provider-side of the OpenSearch relation.""" - on = OpenSearchProvidesEvents() # pyright: ignore[reportGeneralTypeIssues] + on = OpenSearchProvidesEvents() # pyright: ignore[reportAssignmentType] def __init__(self, charm: CharmBase, relation_name: str) -> None: super().__init__(charm, relation_name) @@ -2586,7 +2584,7 @@ def set_version(self, relation_id: int, version: str) -> None: class OpenSearchRequires(DataRequires): """Requires-side of the OpenSearch relation.""" - on = OpenSearchRequiresEvents() # pyright: ignore[reportGeneralTypeIssues] + on = OpenSearchRequiresEvents() # pyright: ignore[reportAssignmentType] def __init__( self, diff --git a/lib/charms/loki_k8s/v0/loki_push_api.py b/lib/charms/loki_k8s/v0/loki_push_api.py index 01d7dc16..0e4a2667 100644 --- a/lib/charms/loki_k8s/v0/loki_push_api.py +++ b/lib/charms/loki_k8s/v0/loki_push_api.py @@ -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 = 25 +LIBPATCH = 26 logger = logging.getLogger(__name__) @@ -2115,7 +2115,21 @@ def _download_and_push_promtail_to_workload(self, promtail_info: dict) -> None: - "zipsha": sha256 sum of zip file of promtail binary - "binsha": sha256 sum of unpacked promtail binary """ - with request.urlopen(promtail_info["url"]) as r: + # Check for Juju proxy variables and fall back to standard ones if not set + proxies: Optional[Dict[str, str]] = {} + if proxies and os.environ.get("JUJU_CHARM_HTTP_PROXY"): + proxies.update({"http": os.environ["JUJU_CHARM_HTTP_PROXY"]}) + if proxies and os.environ.get("JUJU_CHARM_HTTPS_PROXY"): + proxies.update({"https": os.environ["JUJU_CHARM_HTTPS_PROXY"]}) + if proxies and os.environ.get("JUJU_CHARM_NO_PROXY"): + proxies.update({"no_proxy": os.environ["JUJU_CHARM_NO_PROXY"]}) + else: + proxies = None + + proxy_handler = request.ProxyHandler(proxies) + opener = request.build_opener(proxy_handler) + + with opener.open(promtail_info["url"]) as r: file_bytes = r.read() file_path = os.path.join(BINARY_DIR, promtail_info["filename"] + ".gz") with open(file_path, "wb") as f: diff --git a/lib/charms/rolling_ops/v0/rollingops.py b/lib/charms/rolling_ops/v0/rollingops.py index d08b16d4..5a7d4ce3 100644 --- a/lib/charms/rolling_ops/v0/rollingops.py +++ b/lib/charms/rolling_ops/v0/rollingops.py @@ -88,7 +88,7 @@ def _on_trigger_restart(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 4 +LIBPATCH = 5 class LockNoRelationError(Exception): @@ -386,7 +386,8 @@ def _on_acquire_lock(self: CharmBase, event: ActionEvent): # persist callback override for eventual run relation.data[self.charm.unit].update({"callback_override": event.callback_override}) - self.charm.on[self.name].relation_changed.emit(relation) + self.charm.on[self.name].relation_changed.emit(relation, app=self.charm.app) + except LockNoRelationError: logger.debug("No {} peer relation yet. Delaying rolling op.".format(self.name)) event.defer() diff --git a/src/charm.py b/src/charm.py index ccc908fc..11164fb6 100755 --- a/src/charm.py +++ b/src/charm.py @@ -663,9 +663,11 @@ def _configure_pod(self) -> None: current_env["DISCOURSE_USE_S3"] if "DISCOURSE_USE_S3" in current_env else "", current_env["DISCOURSE_S3_REGION"] if "DISCOURSE_S3_REGION" in current_env else "", current_env["DISCOURSE_S3_BUCKET"] if "DISCOURSE_S3_BUCKET" in current_env else "", - current_env["DISCOURSE_S3_ENDPOINT"] - if "DISCOURSE_S3_ENDPOINT" in current_env - else "", + ( + current_env["DISCOURSE_S3_ENDPOINT"] + if "DISCOURSE_S3_ENDPOINT" in current_env + else "" + ), ) if self.model.unit.is_leader() and self._should_run_s3_migration( current_plan, previous_s3_info diff --git a/tests/unit/helpers.py b/tests/unit/helpers.py index b377e1bf..3ac35c93 100644 --- a/tests/unit/helpers.py +++ b/tests/unit/helpers.py @@ -114,9 +114,9 @@ def add_redis_relation(harness, relation_data=None): # We need to bypass protected access to inject the relation data # pylint: disable=protected-access harness.charm._stored.redis_relation = { - redis_relation_id: {"hostname": "redis-host", "port": 1010} - if relation_data is None - else relation_data + redis_relation_id: ( + {"hostname": "redis-host", "port": 1010} if relation_data is None else relation_data + ) }