Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IGNORE] Data interfaces v41 #47

Draft
wants to merge 3 commits into
base: 6/edge
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions lib/charms/data_platform_libs/v0/data_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,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 = 39
LIBPATCH = 41

PYDEPS = ["ops>=2.0.0"]

Expand Down Expand Up @@ -391,6 +391,10 @@ class IllegalOperationError(DataInterfacesError):
"""To be used when an operation is not allowed to be performed."""


class PrematureDataAccessError(DataInterfacesError):
"""To be raised when the Relation Data may be accessed (written) before protocol init complete."""


##############################################################################
# Global helpers / utilities
##############################################################################
Expand Down Expand Up @@ -1277,7 +1281,6 @@ def _delete_relation_data_without_secrets(
str(field),
str(relation.id),
)
pass

# Public interface methods
# Handling Relation Fields seamlessly, regardless if in databag or a Juju Secret
Expand Down Expand Up @@ -1453,6 +1456,8 @@ def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
class ProviderData(Data):
"""Base provides-side of the data products relation."""

RESOURCE_FIELD = "database"

def __init__(
self,
model: Model,
Expand Down Expand Up @@ -1536,21 +1541,26 @@ def _delete_relation_secret(
secret = self._get_relation_secret(relation.id, group)

if not secret:
logging.error("Can't delete secret for relation %s", str(relation.id))
logging.debug(
"Can't delete secrets from group '%s' (relation ID: %s)",
str(group),
str(relation.id),
)
return False

old_content = secret.get_content()
new_content = copy.deepcopy(old_content)
for field in fields:
for field in set(fields) & set(secret_fields):
try:
new_content.pop(field)
except KeyError:
logging.debug(
"Non-existing secret was attempted to be removed %s, %s",
str(relation.id),
"Non-existing secret '%s' was attempted to be removed (relation ID: %s)",
str(field),
str(relation.id),
)
return False
if old_content == new_content:
return False

# Remove secret from the relation if it's fully gone
if not new_content:
Expand Down Expand Up @@ -1618,6 +1628,15 @@ def _fetch_my_specific_relation_data(
def _update_relation_data(self, relation: Relation, data: Dict[str, str]) -> None:
"""Set values for fields not caring whether it's a secret or not."""
req_secret_fields = []

keys = set(data.keys())
if self.fetch_relation_field(relation.id, self.RESOURCE_FIELD) is None and (
keys - {"endpoints", "read-only-endpoints", "replset"}
):
raise PrematureDataAccessError(
"Premature access to relation data, update is forbidden before the connection is initialized."
)

if relation.app:
req_secret_fields = get_encoded_list(relation, relation.app, REQ_SECRET_FIELDS)

Expand Down Expand Up @@ -3290,6 +3309,8 @@ class KafkaRequiresEvents(CharmEvents):
class KafkaProviderData(ProviderData):
"""Provider-side of the Kafka relation."""

RESOURCE_FIELD = "topic"

def __init__(self, model: Model, relation_name: str) -> None:
super().__init__(model, relation_name)

Expand Down Expand Up @@ -3539,6 +3560,8 @@ class OpenSearchRequiresEvents(CharmEvents):
class OpenSearchProvidesData(ProviderData):
"""Provider-side of the OpenSearch relation."""

RESOURCE_FIELD = "index"

def __init__(self, model: Model, relation_name: str) -> None:
super().__init__(model, relation_name)

Expand Down
10 changes: 4 additions & 6 deletions lib/charms/mongodb/v0/config_server_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
DatabaseProvides,
DatabaseRequestedEvent,
DatabaseRequires,
PrematureDataAccessError,
)
from charms.mongodb.v0.mongo import MongoConnection
from charms.mongodb.v1.mongos import MongosConnection
Expand Down Expand Up @@ -158,15 +159,12 @@ def _on_database_requested(self, event: DatabaseRequestedEvent | RelationChanged

def _on_relation_changed(self, event: RelationChangedEvent) -> None:
"""Handles providing mongos with KeyFile and hosts."""
# First we need to ensure that the database requested event has run
# otherwise we risk the chance of writing secrets in plain sight.
if not self.database_provides.fetch_relation_field(event.relation.id, "database"):
try:
self._on_database_requested(event)
except PrematureDataAccessError:
logger.info("Database Requested has not run yet, skipping.")
event.defer()
return

# TODO : This workflow is a fix until we have time for a better and complete fix (DPE-5513)
self._on_database_requested(event)

def _on_relation_broken(self, event) -> None:
if self.charm.upgrade_in_progress:
Expand Down
12 changes: 0 additions & 12 deletions lib/charms/mongodb/v1/mongodb_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,18 +539,6 @@ def remove_all_relational_users(self):
fields = self.database_provides.fetch_my_relation_data([relation.id])[relation.id]
self.database_provides.delete_relation_data(relation.id, fields=list(fields))

# unforatunately the above doesn't work to remove secrets, so we forcibly remove the
# rest manually remove the secret before clearing the databag
for unit in relation.units:
secret_id = json.loads(relation.data[unit]["data"])["secret-user"]
# secret id is the same on all units for `secret-user`
break

user_secrets = self.charm.model.get_secret(id=secret_id)
user_secrets.remove_all_revisions()
user_secrets.get_content(refresh=True)
relation.data[self.charm.app].clear()

@staticmethod
def _get_database_from_relation(relation: Relation) -> Optional[str]:
"""Return database name from relation."""
Expand Down
Loading