Skip to content

Commit

Permalink
chore: update charm libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
observability-noctua-bot committed Jun 7, 2024
1 parent c9eca75 commit e6d22ea
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 178 deletions.
75 changes: 57 additions & 18 deletions lib/charms/hydra/v0/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,14 @@ def _set_client_config(self):
```
"""

import inspect
import json
import logging
import re
from dataclasses import asdict, dataclass, field
from dataclasses import asdict, dataclass, field, fields
from typing import Dict, List, Mapping, Optional

import jsonschema
from ops.charm import (
CharmBase,
RelationBrokenEvent,
RelationChangedEvent,
RelationCreatedEvent,
RelationDepartedEvent,
)
from ops.charm import CharmBase, RelationBrokenEvent, RelationChangedEvent, RelationCreatedEvent
from ops.framework import EventBase, EventSource, Handle, Object, ObjectEvents
from ops.model import Relation, Secret, TooManyRelatedAppsError

Expand All @@ -74,12 +67,20 @@ def _set_client_config(self):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 6
LIBPATCH = 9

PYDEPS = ["jsonschema"]


logger = logging.getLogger(__name__)

DEFAULT_RELATION_NAME = "oauth"
ALLOWED_GRANT_TYPES = ["authorization_code", "refresh_token", "client_credentials"]
ALLOWED_GRANT_TYPES = [
"authorization_code",
"refresh_token",
"client_credentials",
"urn:ietf:params:oauth:grant-type:device_code",
]
ALLOWED_CLIENT_AUTHN_METHODS = ["client_secret_basic", "client_secret_post"]
CLIENT_SECRET_FIELD = "secret"

Expand Down Expand Up @@ -127,6 +128,7 @@ def _set_client_config(self):
},
"groups": {"type": "string", "default": None},
"ca_chain": {"type": "array", "items": {"type": "string"}, "default": []},
"jwt_access_token": {"type": "string", "default": "False"},
},
"required": [
"issuer_url",
Expand All @@ -153,13 +155,13 @@ def _set_client_config(self):
"type": "array",
"default": None,
"items": {
"enum": ["authorization_code", "client_credentials", "refresh_token"],
"enum": ALLOWED_GRANT_TYPES,
"type": "string",
},
},
"token_endpoint_auth_method": {
"type": "string",
"enum": ["client_secret_basic", "client_secret_post"],
"enum": ALLOWED_CLIENT_AUTHN_METHODS,
"default": "client_secret_basic",
},
},
Expand Down Expand Up @@ -200,11 +202,32 @@ def _dump_data(data: Dict, schema: Optional[Dict] = None) -> Dict:
ret[k] = json.dumps(v)
except json.JSONDecodeError as e:
raise DataValidationError(f"Failed to encode relation json: {e}")
elif isinstance(v, bool):
ret[k] = str(v)
else:
ret[k] = v
return ret


def strtobool(val: str) -> bool:
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
"""
if not isinstance(val, str):
raise ValueError(f"invalid value type {type(val)}")

val = val.lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return True
elif val in ("n", "no", "f", "false", "off", "0"):
return False
else:
raise ValueError(f"invalid truth value {val}")


class OAuthRelation(Object):
"""A class containing helper methods for oauth relation."""

Expand Down Expand Up @@ -291,11 +314,22 @@ class OauthProviderConfig:
client_secret: Optional[str] = None
groups: Optional[str] = None
ca_chain: Optional[str] = None
jwt_access_token: Optional[bool] = False

@classmethod
def from_dict(cls, dic: Dict) -> "OauthProviderConfig":
"""Generate OauthProviderConfig instance from dict."""
return cls(**{k: v for k, v in dic.items() if k in inspect.signature(cls).parameters})
jwt_access_token = False
if "jwt_access_token" in dic:
jwt_access_token = strtobool(dic["jwt_access_token"])
return cls(
jwt_access_token=jwt_access_token,
**{
k: v
for k, v in dic.items()
if k in [f.name for f in fields(cls)] and k != "jwt_access_token"
},
)


class OAuthInfoChangedEvent(EventBase):
Expand All @@ -315,6 +349,7 @@ def snapshot(self) -> Dict:

def restore(self, snapshot: Dict) -> None:
"""Restore event."""
super().restore(snapshot)
self.client_id = snapshot["client_id"]
self.client_secret_id = snapshot["client_secret_id"]

Expand Down Expand Up @@ -454,7 +489,9 @@ def is_client_created(self, relation_id: Optional[int] = None) -> bool:
and "client_secret_id" in relation.data[relation.app]
)

def get_provider_info(self, relation_id: Optional[int] = None) -> OauthProviderConfig:
def get_provider_info(
self, relation_id: Optional[int] = None
) -> Optional[OauthProviderConfig]:
"""Get the provider information from the databag."""
if len(self.model.relations) == 0:
return None
Expand Down Expand Up @@ -647,8 +684,8 @@ def __init__(self, charm: CharmBase, relation_name: str = DEFAULT_RELATION_NAME)
self._get_client_config_from_relation_data,
)
self.framework.observe(
events.relation_departed,
self._on_relation_departed,
events.relation_broken,
self._on_relation_broken,
)

def _get_client_config_from_relation_data(self, event: RelationChangedEvent) -> None:
Expand Down Expand Up @@ -696,7 +733,7 @@ def _get_client_config_from_relation_data(self, event: RelationChangedEvent) ->
def _get_secret_label(self, relation: Relation) -> str:
return f"client_secret_{relation.id}"

def _on_relation_departed(self, event: RelationDepartedEvent) -> None:
def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
# Workaround for https://github.com/canonical/operator/issues/888
self._pop_relation_data(event.relation.id)

Expand Down Expand Up @@ -725,6 +762,7 @@ def set_provider_info_in_relation_data(
scope: str,
groups: Optional[str] = None,
ca_chain: Optional[str] = None,
jwt_access_token: Optional[bool] = False,
) -> None:
"""Put the provider information in the databag."""
if not self.model.unit.is_leader():
Expand All @@ -738,6 +776,7 @@ def set_provider_info_in_relation_data(
"userinfo_endpoint": userinfo_endpoint,
"jwks_endpoint": jwks_endpoint,
"scope": scope,
"jwt_access_token": jwt_access_token,
}
if groups:
data["groups"] = groups
Expand Down
34 changes: 13 additions & 21 deletions lib/charms/observability_libs/v0/cert_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@
from typing import List, Optional, Union, cast

try:
from charms.tls_certificates_interface.v3.tls_certificates import ( # type: ignore
from charms.tls_certificates_interface.v2.tls_certificates import ( # type: ignore
AllCertificatesInvalidatedEvent,
CertificateAvailableEvent,
CertificateExpiringEvent,
CertificateInvalidatedEvent,
TLSCertificatesRequiresV3,
TLSCertificatesRequiresV2,
generate_csr,
generate_private_key,
)
except ImportError as e:
raise ImportError(
"failed to import charms.tls_certificates_interface.v3.tls_certificates; "
"failed to import charms.tls_certificates_interface.v2.tls_certificates; "
"Either the library itself is missing (please get it through charmcraft fetch-lib) "
"or one of its dependencies is unmet."
) from e
Expand All @@ -67,7 +67,7 @@

LIBID = "b5cd5cd580f3428fa5f59a8876dcbe6a"
LIBAPI = 0
LIBPATCH = 12
LIBPATCH = 13


def is_ip_address(value: str) -> bool:
Expand Down Expand Up @@ -132,7 +132,7 @@ def __init__(
self.peer_relation_name = peer_relation_name
self.certificates_relation_name = certificates_relation_name

self.certificates = TLSCertificatesRequiresV3(self.charm, self.certificates_relation_name)
self.certificates = TLSCertificatesRequiresV2(self.charm, self.certificates_relation_name)

self.framework.observe(
self.charm.on.config_changed,
Expand Down Expand Up @@ -289,7 +289,7 @@ def _generate_csr(
if clear_cert:
self._ca_cert = ""
self._server_cert = ""
self._chain = ""
self._chain = []

def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
"""Get the certificate from the event and store it in a peer relation.
Expand All @@ -311,7 +311,7 @@ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
if event_csr == self._csr:
self._ca_cert = event.ca
self._server_cert = event.certificate
self._chain = event.chain_as_pem()
self._chain = event.chain
self.on.cert_changed.emit() # pyright: ignore

@property
Expand Down Expand Up @@ -382,29 +382,21 @@ def _server_cert(self, value: str):
rel.data[self.charm.unit].update({"certificate": value})

@property
def _chain(self) -> str:
def _chain(self) -> List[str]:
if self._peer_relation:
if chain := self._peer_relation.data[self.charm.unit].get("chain", ""):
chain = json.loads(chain)

# In a previous version of this lib, chain used to be a list.
# Convert the List[str] to str, per
# https://github.com/canonical/tls-certificates-interface/pull/141
if isinstance(chain, list):
chain = "\n\n".join(reversed(chain))

return cast(str, chain)
return ""
if chain := self._peer_relation.data[self.charm.unit].get("chain", []):
return cast(list, json.loads(cast(str, chain)))
return []

@_chain.setter
def _chain(self, value: str):
def _chain(self, value: List[str]):
# Caller must guard. We want the setter to fail loudly. Failure must have a side effect.
rel = self._peer_relation
assert rel is not None # For type checker
rel.data[self.charm.unit].update({"chain": json.dumps(value)})

@property
def chain(self) -> str:
def chain(self) -> List[str]:
"""Return the ca chain."""
return self._chain

Expand Down
6 changes: 3 additions & 3 deletions lib/charms/prometheus_k8s/v0/prometheus_scrape.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def __init__(self, *args):
- `scrape_timeout`
- `proxy_url`
- `relabel_configs`
- `metrics_relabel_configs`
- `metric_relabel_configs`
- `sample_limit`
- `label_limit`
- `label_name_length_limit`
Expand Down Expand Up @@ -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 = 46
LIBPATCH = 47

PYDEPS = ["cosl"]

Expand All @@ -377,7 +377,7 @@ def _on_scrape_targets_changed(self, event):
"scrape_timeout",
"proxy_url",
"relabel_configs",
"metrics_relabel_configs",
"metric_relabel_configs",
"sample_limit",
"label_limit",
"label_name_length_limit",
Expand Down
22 changes: 9 additions & 13 deletions lib/charms/tempo_k8s/v1/charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def my_tracing_endpoint(self) -> Optional[str]:
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version

LIBPATCH = 6
LIBPATCH = 8

PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"]

Expand Down Expand Up @@ -509,18 +509,20 @@ def trace_type(cls: _T) -> _T:
logger.info(f"skipping {method} (dunder)")
continue

isstatic = isinstance(inspect.getattr_static(cls, method.__name__), staticmethod)
setattr(cls, name, trace_method(method, static=isstatic))
new_method = trace_method(method)
if isinstance(inspect.getattr_static(cls, method.__name__), staticmethod):
new_method = staticmethod(new_method)
setattr(cls, name, new_method)

return cls


def trace_method(method: _F, static: bool = False) -> _F:
def trace_method(method: _F) -> _F:
"""Trace this method.
A span will be opened when this method is called and closed when it returns.
"""
return _trace_callable(method, "method", static=static)
return _trace_callable(method, "method")


def trace_function(function: _F) -> _F:
Expand All @@ -531,20 +533,14 @@ def trace_function(function: _F) -> _F:
return _trace_callable(function, "function")


def _trace_callable(callable: _F, qualifier: str, static: bool = False) -> _F:
def _trace_callable(callable: _F, qualifier: str) -> _F:
logger.info(f"instrumenting {callable}")

# sig = inspect.signature(callable)
@functools.wraps(callable)
def wrapped_function(*args, **kwargs): # type: ignore
name = getattr(callable, "__qualname__", getattr(callable, "__name__", str(callable)))
with _span(f"{'(static) ' if static else ''}{qualifier} call: {name}"): # type: ignore
if static:
# fixme: do we or don't we need [1:]?
# The _trace_callable decorator doesn't always play nice with @staticmethods.
# Sometimes it will receive 'self', sometimes it won't.
# return callable(*args, **kwargs) # type: ignore
return callable(*args[1:], **kwargs) # type: ignore
with _span(f"{qualifier} call: {name}"): # type: ignore
return callable(*args, **kwargs) # type: ignore

# wrapped_function.__signature__ = sig
Expand Down
Loading

0 comments on commit e6d22ea

Please sign in to comment.