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

swapped ingress v1 -> v2 #173

Merged
merged 42 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
12e52bc
Minor tweaks
sed-i Jul 6, 2023
dd9d043
Add scheme
sed-i Jul 7, 2023
2e0052f
Lint
sed-i Jul 7, 2023
629e5df
Lint
sed-i Jul 7, 2023
97a34e2
fetch-lib
sed-i Jul 7, 2023
594b0d6
swapped ingress v1 -> v2
PietroPasotti Jul 7, 2023
67b94d8
Scenario session with Pietro
sed-i Jul 10, 2023
90f5c6b
Refactor fixture into helper method
sed-i Jul 10, 2023
c98ee80
Remove FakeProcessVersionCheck
sed-i Jul 10, 2023
645dd2e
Update scenario tests
sed-i Jul 11, 2023
05b2ba0
Cleanup
sed-i Jul 11, 2023
7656868
mild refactoring
PietroPasotti Jul 12, 2023
65d2134
pytested test_transition_to_https
PietroPasotti Jul 12, 2023
ada0d13
added transition tests
PietroPasotti Jul 12, 2023
1276a67
Cleanup
sed-i Jul 13, 2023
8f10066
fetch-lib
sed-i Jul 13, 2023
30eeae0
Fix TLS stuff
sed-i Jul 14, 2023
10a1538
Fix CI
sed-i Jul 14, 2023
ea6399b
Apply suggestions from code review
sed-i Jul 14, 2023
5405bef
Remove class-less scenario test (duplicate)
sed-i Jul 14, 2023
790cb6a
Lint
sed-i Jul 14, 2023
d13979e
swapped ingress v1 -> v2
PietroPasotti Jul 7, 2023
fe83525
"fetch-lib" from traefik/196
sed-i Jul 14, 2023
a4dfdcd
Pass scheme to IngressPerAppRequirer
sed-i Jul 14, 2023
299c65c
Merge remote-tracking branch 'origin/main' into feature/ingress-v2-su…
sed-i Jul 14, 2023
e9078ee
fetch-lib
sed-i Jul 14, 2023
d5f503b
Merge branch 'feature/ingress-v2-support' of github.com:canonical/ale…
PietroPasotti Jul 17, 2023
4b32ddc
use_https_scheme
PietroPasotti Jul 17, 2023
b5084f5
some fixes to ingress lib
PietroPasotti Jul 20, 2023
4d7a4a6
fixed leader guard
PietroPasotti Jul 20, 2023
9e66856
new ingress
PietroPasotti Jul 20, 2023
2eed35d
fetch-lib
sed-i Jul 26, 2023
8a4ad54
Merge remote-tracking branch 'origin/main' into feature/ingress-v2-su…
sed-i Jul 26, 2023
4c2a4c0
Lint
sed-i Jul 26, 2023
e2c8171
fetch-lib
sed-i Aug 3, 2023
ea02062
Fix scenario tests
sed-i Aug 3, 2023
3fc4488
Bump to 22.04 to avoid pip dep resolution issue
sed-i Aug 3, 2023
e9f0347
Itest fixes
sed-i Aug 3, 2023
cec1b1f
Revert back to 20.04
sed-i Aug 4, 2023
4b5b935
fetch-lib
sed-i Aug 4, 2023
fd036c0
Fetch lib
sed-i Aug 4, 2023
f66dca9
Back to focal in itest as well
sed-i Aug 5, 2023
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
2 changes: 0 additions & 2 deletions charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ bases:
parts:
charm:
charm-binary-python-packages: [ops, PyYAML, cryptography, jsonschema]
build-packages:
- git
139 changes: 50 additions & 89 deletions lib/charms/karma_k8s/v0/karma_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ def __init__(self, *args):
"""

import logging
from typing import Dict, List, Optional
from typing import Any, Dict, List, Optional

import ops.charm
from ops.charm import CharmBase, RelationJoinedEvent, RelationRole
from ops.framework import EventBase, EventSource, Object, ObjectEvents, StoredState
from pydantic import BaseModel, ValidationError

# The unique Charmhub library identifier, never change it
LIBID = "98f9dc00f7ff4b1197895886bdd92037"
Expand All @@ -37,76 +38,30 @@ def __init__(self, *args):

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

PYDEPS = ["pydantic < 2"]

# Set to match metadata.yaml
INTERFACE_NAME = "karma_dashboard"

logger = logging.getLogger(__name__)


class KarmaAlertmanagerConfig:
"""A helper class for alertmanager server configuration for Karma.

Refer to the Karma documentation for full details:
https://github.com/prymitive/karma/blob/main/docs/CONFIGURATION.md#alertmanagers
"""

required_fields = {"name", "uri"}
optional_fields = {"cluster"}
_supported_fields = required_fields | optional_fields

@staticmethod
def is_valid(config: Dict[str, str]) -> bool:
"""Validate alertmanager server configuration for Karma.
class _KarmaDashboardProviderUnitDataV0(BaseModel):
name: str
uri: str
cluster: str = ""
proxy: bool = True

Args:
config: target configuration to be validated.

Returns:
True if all required keys are present and all remaining keys are supported optional
fields; False otherwise.
"""
all_required = all(key in config for key in KarmaAlertmanagerConfig.required_fields)
all_supported = all(key in KarmaAlertmanagerConfig._supported_fields for key in config)
return all_required and all_supported

@staticmethod
def from_dict(data: Dict[str, str]) -> Dict[str, str]:
"""Generate alertmanager server configuration from the given dict.

Configuration is constructed by creating a subset of the provided dictionary that contains
only the supported fields.

Args:
data: a dict that may contain alertmanager server configuration for Karma.

Returns:
A subset of `data` that contains all the supported fields found in `data`, if the
resulting subset makes a valid configuration; False otherwise.
"""
config = {k: data[k] for k in data if k in KarmaAlertmanagerConfig.required_fields}
optional_config = {
k: data[k] for k in data if data[k] and k in KarmaAlertmanagerConfig.optional_fields
class Config:
json_encoders = {
# We need this because relation data values must be strings, not <class 'bool'>
# Note: In pydantic>=2, can use `field_serializer`.
bool: lambda v: "true"
if v
else "false"
}
config.update(optional_config)
return config if KarmaAlertmanagerConfig.is_valid(config) else {}

@staticmethod
def build(name: str, url: str, *, cluster=None) -> Dict[str, str]:
"""Build alertmanager server configuration for Karma.

Args:
name: name for the alertmanager unit.
url: url of the alertmanager api server (including scheme and port)
cluster: name of a cluster to which the alertmanager unit belongs to (optional)

Returns:
Alertmanager server configuration for Karma.
"""
return KarmaAlertmanagerConfig.from_dict(
{"name": name, "uri": url, "cluster": cluster} # pyright: ignore
)


class KarmaAlertmanagerConfigChanged(EventBase):
Expand Down Expand Up @@ -227,7 +182,7 @@ def __init__(self, charm, relation_name: str = "karma-dashboard"):
self.framework.observe(events.relation_changed, self._on_relation_changed)
self.framework.observe(events.relation_departed, self._on_relation_departed)

def get_alertmanager_servers(self) -> List[Dict[str, str]]:
def get_alertmanager_servers(self) -> List[Dict[str, Any]]:
"""Return configuration data for all related alertmanager servers.

The exact spec is described in the Karma project documentation
Expand All @@ -243,15 +198,27 @@ def get_alertmanager_servers(self) -> List[Dict[str, str]]:
for relation in self.charm.model.relations[self.name]:
# get data from related application
for key in relation.data:
if key is not self.charm.unit and isinstance(
key, ops.charm.model.Unit # pyright: ignore
if (
key is not self.charm.unit
and isinstance(key, ops.charm.model.Unit) # pyright: ignore
and relation.data[key]
):
data = relation.data[key]
config = KarmaAlertmanagerConfig.from_dict(data)
if config and config not in servers:
servers.append(config)

return servers # TODO sorted
try:
data = _KarmaDashboardProviderUnitDataV0(**relation.data[key])
except ValidationError:
logger.warning(
"Relation data is invalid or not ready; "
"contents of relation.data[%s]: %s",
key,
relation.data[key],
)
else:
# Now convert relation data into config file format. Luckily it's trivial.
config = data.dict()
if config and config not in servers:
servers.append(config)

return sorted(servers, key=lambda itm: itm["name"])

def _on_relation_changed(self, _):
"""Event handler for RelationChangedEvent."""
Expand Down Expand Up @@ -340,16 +307,6 @@ def __init__(self, charm, relation_name: str = "dashboard"):
def _on_relation_joined(self, event: RelationJoinedEvent):
self._update_relation_data(event)

@property
def config_valid(self) -> bool:
"""Check if the current configuration is valid.

Returns:
True if the currently stored configuration for an alertmanager target is valid; False
otherwise.
"""
return KarmaAlertmanagerConfig.is_valid(self._stored.config) # type: ignore

@property
def target(self) -> Optional[str]:
"""str: Alertmanager URL to be used by Karma."""
Expand All @@ -367,14 +324,18 @@ def target(self, url: str) -> None:
Returns:
None.
"""
name = self.charm.unit.name
cluster = "{}_{}".format(self.charm.model.name, self.charm.app.name)
config = KarmaAlertmanagerConfig.build(name, url, cluster=cluster)
if not config:
logger.warning("Invalid config: {%s, %s}", name, url)
return

self._stored.config.update(config) # type: ignore
data = _KarmaDashboardProviderUnitDataV0(
name=self.charm.unit.name,
uri=url,
cluster=f"{self.charm.model.name}_{self.charm.app.name}",
proxy=True,
)
# TODO Use `data.model_dump()` when we switch to pydantic 2
as_dict = data.dict()
# Replace bool with str, otherwise:
# ops.model.RelationDataTypeError: relation data values must be strings, not <class 'bool'>
as_dict["proxy"] = "true" if as_dict["proxy"] else "false"
self._stored.config.update(as_dict) # type: ignore

# target changed - must update all relation data
self._update_relation_data()
Expand Down
Loading
Loading