Skip to content

Commit

Permalink
Merge pull request #29 from canonical/update-charm-libs
Browse files Browse the repository at this point in the history
Update charm libs
  • Loading branch information
nsklikas authored Apr 22, 2024
2 parents 8b356a9 + 3b3e8b2 commit cf74a0d
Show file tree
Hide file tree
Showing 15 changed files with 2,740 additions and 214 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/on_schedule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
on:
schedule:
- cron: '0 8 * * MON'

jobs:
update-charm-libs:
name: Update Charm Libraries
uses: ./.github/workflows/update_libs.yaml
secrets:
CHARMCRAFT_CREDENTIALS: ${{ secrets.CHARMCRAFT_CREDENTIALS }}
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}

tests:
name: Run Tests
uses: ./.github/workflows/tests.yaml
51 changes: 51 additions & 0 deletions .github/workflows/update_libs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# reusable workflow for checking library versions and opening PRs to bump
name: Update Charm Libraries

on:
workflow_call:
inputs:
charmcraft_channel:
description: Channel from which to install charmcraft
default: 'latest/candidate'
required: false
type: string
secrets:
CHARMCRAFT_CREDENTIALS:
required: true
PAT_TOKEN:
required: true
workflow_dispatch:
inputs:
charmcraft_channel:
description: Channel from which to install charmcraft
default: 'latest/candidate'
required: false
type: string

jobs:
update-libs:
name: Update charm libraries
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0

- run: sudo snap install charmcraft --channel ${{ inputs.charmcraft_channel }} --classic

- name: update charm libs
run: charmcraft fetch-lib
env:
CHARMCRAFT_AUTH: ${{ secrets.CHARMCRAFT_CREDENTIALS }}

- name: Create Pull Request
id: create-pull-request
uses: canonical/create-pull-request@main
with:
github-token: ${{ secrets.PAT_TOKEN }}
commit-message: Update charm libs
branch-name: 'automated-update-charm-libs'
title: (Automated) Update Charm Libs
body: Update charm libs
upsert: true
ignore-no-changes: true
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: requirements-txt-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.7
rev: v0.4.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
from ops.charm import CharmBase, RelationJoinedEvent
from ops.main import main
from lib.charms.certificate_transfer_interface.v0.certificate_transfer import CertificateTransferProvides # noqa: E501 W505
from lib.charms.certificate_transfer_interface.v0.certificate_transfer import(
CertificateTransferProvides,
)
class DummyCertificateTransferProviderCharm(CharmBase):
Expand All @@ -36,7 +38,9 @@ def _on_certificates_relation_joined(self, event: RelationJoinedEvent):
certificate = "my certificate"
ca = "my CA certificate"
chain = ["certificate 1", "certificate 2"]
self.certificate_transfer.set_certificate(certificate=certificate, ca=ca, chain=chain, relation_id=event.relation.id)
self.certificate_transfer.set_certificate(
certificate=certificate, ca=ca, chain=chain, relation_id=event.relation.id
)
if __name__ == "__main__":
Expand Down Expand Up @@ -95,7 +99,7 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):

import json
import logging
from typing import List
from typing import List, Mapping

from jsonschema import exceptions, validate # type: ignore[import-untyped]
from ops.charm import CharmBase, CharmEvents, RelationBrokenEvent, RelationChangedEvent
Expand All @@ -109,7 +113,7 @@ def _on_certificate_removed(self, event: CertificateRemovedEvent):

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

PYDEPS = ["jsonschema"]

Expand Down Expand Up @@ -210,7 +214,7 @@ def restore(self, snapshot: dict):
self.relation_id = snapshot["relation_id"]


def _load_relation_data(raw_relation_data: dict) -> dict:
def _load_relation_data(raw_relation_data: Mapping[str, str]) -> dict:
"""Load relation data from the relation data bag.
Args:
Expand Down Expand Up @@ -313,7 +317,7 @@ def remove_certificate(self, relation_id: int) -> None:
class CertificateTransferRequires(Object):
"""TLS certificates requirer class to be instantiated by TLS certificates requirers."""

on = CertificateTransferRequirerCharmEvents()
on = CertificateTransferRequirerCharmEvents() # type: ignore

def __init__(
self,
Expand Down Expand Up @@ -379,7 +383,7 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None:
)

def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
"""Handler triggered on relation broken event.
"""Handle relation broken event.
Args:
event: Juju event
Expand Down
79 changes: 59 additions & 20 deletions lib/charms/loki_k8s/v0/loki_push_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
implement the provider side of the `loki_push_api` relation interface. For instance, a Loki charm.
The provider side of the relation represents the server side, to which logs are being pushed.
- `LokiPushApiConsumer`: This object is meant to be used by any Charmed Operator that needs to
send log to Loki by implementing the consumer side of the `loki_push_api` relation interface.
For instance, a Promtail or Grafana agent charm which needs to send logs to Loki.
- `LokiPushApiConsumer`: Used to obtain the loki api endpoint. This is useful for configuring
applications such as pebble, or charmed operators of workloads such as grafana-agent or promtail,
that can communicate with loki directly.
- `LogProxyConsumer`: This object can be used by any Charmed Operator which needs to
send telemetry, such as logs, to Loki through a Log Proxy by implementing the consumer side of the
Expand Down Expand Up @@ -456,7 +456,7 @@ def _alert_rules_error(self, event):
from urllib.error import HTTPError

import yaml
from charms.observability_libs.v0.juju_topology import JujuTopology
from cosl import JujuTopology
from ops.charm import (
CharmBase,
HookEvent,
Expand All @@ -480,7 +480,9 @@ 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 = 21
LIBPATCH = 29

PYDEPS = ["cosl"]

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -604,7 +606,9 @@ def _validate_relation_by_interface_and_direction(
actual_relation_interface = relation.interface_name
if actual_relation_interface != expected_relation_interface:
raise RelationInterfaceMismatchError(
relation_name, expected_relation_interface, actual_relation_interface
relation_name,
expected_relation_interface,
actual_relation_interface, # pyright: ignore
)

if expected_relation_role == RelationRole.provides:
Expand Down Expand Up @@ -866,20 +870,20 @@ def _from_dir(self, dir_path: Path, recursive: bool) -> List[dict]:

return alert_groups

def add_path(self, path: str, *, recursive: bool = False):
def add_path(self, path_str: str, *, recursive: bool = False):
"""Add rules from a dir path.
All rules from files are aggregated into a data structure representing a single rule file.
All group names are augmented with juju topology.
Args:
path: either a rules file or a dir of rules files.
path_str: either a rules file or a dir of rules files.
recursive: whether to read files recursively or not (no impact if `path` is a file).
Raises:
InvalidAlertRulePathError: if the provided path is invalid.
"""
path = Path(path) # type: Path
path = Path(path_str) # type: Path
if path.is_dir():
self.alert_groups.extend(self._from_dir(path, recursive))
elif path.is_file():
Expand Down Expand Up @@ -992,6 +996,8 @@ def __init__(self, handle, relation, relation_id, app=None, unit=None):

def snapshot(self) -> Dict:
"""Save event information."""
if not self.relation:
return {}
snapshot = {"relation_name": self.relation.name, "relation_id": self.relation.id}
if self.app:
snapshot["app_name"] = self.app.name
Expand Down Expand Up @@ -1052,7 +1058,7 @@ class LokiPushApiEvents(ObjectEvents):
class LokiPushApiProvider(Object):
"""A LokiPushApiProvider class."""

on = LokiPushApiEvents()
on = LokiPushApiEvents() # pyright: ignore

def __init__(
self,
Expand Down Expand Up @@ -1146,11 +1152,11 @@ def _on_logging_relation_changed(self, event: HookEvent):
event: a `CharmEvent` in response to which the consumer
charm must update its relation data.
"""
should_update = self._process_logging_relation_changed(event.relation)
should_update = self._process_logging_relation_changed(event.relation) # pyright: ignore
if should_update:
self.on.loki_push_api_alert_rules_changed.emit(
relation=event.relation,
relation_id=event.relation.id,
relation=event.relation, # pyright: ignore
relation_id=event.relation.id, # pyright: ignore
app=self._charm.app,
unit=self._charm.unit,
)
Expand Down Expand Up @@ -1517,7 +1523,7 @@ def loki_endpoints(self) -> List[dict]:
class LokiPushApiConsumer(ConsumerBase):
"""Loki Consumer class."""

on = LokiPushApiEvents()
on = LokiPushApiEvents() # pyright: ignore

def __init__(
self,
Expand Down Expand Up @@ -1760,7 +1766,7 @@ class LogProxyConsumer(ConsumerBase):
role.
"""

on = LogProxyEvents()
on = LogProxyEvents() # pyright: ignore

def __init__(
self,
Expand All @@ -1773,6 +1779,8 @@ def __init__(
recursive: bool = False,
container_name: str = "",
promtail_resource_name: Optional[str] = None,
*, # TODO: In v1, move the star up so everything after 'charm' is a kwarg
insecure_skip_verify: bool = False,
):
super().__init__(charm, relation_name, alert_rules_path, recursive)
self._charm = charm
Expand All @@ -1792,6 +1800,7 @@ def __init__(
self._is_syslog = enable_syslog
self.topology = JujuTopology.from_charm(charm)
self._promtail_resource_name = promtail_resource_name or "promtail-bin"
self.insecure_skip_verify = insecure_skip_verify

# architecture used for promtail binary
arch = platform.processor()
Expand Down Expand Up @@ -1882,7 +1891,7 @@ def _on_relation_departed(self, _: RelationEvent) -> None:
self._container.stop(WORKLOAD_SERVICE_NAME)
self.on.log_proxy_endpoint_departed.emit()

def _get_container(self, container_name: str = "") -> Container:
def _get_container(self, container_name: str = "") -> Container: # pyright: ignore
"""Gets a single container by name or using the only container running in the Pod.
If there is more than one container in the Pod a `PromtailDigestError` is emitted.
Expand Down Expand Up @@ -1956,7 +1965,9 @@ def _add_pebble_layer(self, workload_binary_path: str) -> None:
}
},
}
self._container.add_layer(self._container_name, pebble_layer, combine=True)
self._container.add_layer(
self._container_name, pebble_layer, combine=True # pyright: ignore
)

def _create_directories(self) -> None:
"""Creates the directories for Promtail binary and config file."""
Expand Down Expand Up @@ -1993,7 +2004,11 @@ def _push_binary_to_workload(self, binary_path: str, workload_binary_path: str)
"""
with open(binary_path, "rb") as f:
self._container.push(
workload_binary_path, f, permissions=0o755, encoding=None, make_dirs=True
workload_binary_path,
f,
permissions=0o755,
encoding=None, # pyright: ignore
make_dirs=True,
)
logger.debug("The promtail binary file has been pushed to the workload container.")

Expand Down Expand Up @@ -2102,7 +2117,24 @@ 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
# If no Juju proxy variable was set, we set proxies to None to let the ProxyHandler get
# the proxy env variables from the environment
proxies = {
# The ProxyHandler uses only the protocol names as keys
# https://docs.python.org/3/library/urllib.request.html#urllib.request.ProxyHandler
"https": os.environ.get("JUJU_CHARM_HTTPS_PROXY", ""),
"http": os.environ.get("JUJU_CHARM_HTTP_PROXY", ""),
# The ProxyHandler uses `no` for the no_proxy key
# https://github.com/python/cpython/blob/3.12/Lib/urllib/request.py#L2553
"no": os.environ.get("JUJU_CHARM_NO_PROXY", ""),
}
proxies = {k: v for k, v in proxies.items() if v != ""} or 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:
Expand Down Expand Up @@ -2153,8 +2185,15 @@ def _current_config(self) -> dict:

@property
def _promtail_config(self) -> dict:
"""Generates the config file for Promtail."""
"""Generates the config file for Promtail.
Reference: https://grafana.com/docs/loki/latest/send-data/promtail/configuration
"""
config = {"clients": self._clients_list()}
if self.insecure_skip_verify:
for client in config["clients"]:
client["tls_config"] = {"insecure_skip_verify": True}

config.update(self._server_config())
config.update(self._positions())
config.update(self._scrape_configs())
Expand Down
Loading

0 comments on commit cf74a0d

Please sign in to comment.