Skip to content
This repository was archived by the owner on Jul 16, 2026. It is now read-only.
Merged
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
8 changes: 4 additions & 4 deletions lib/charms/grafana_agent/v0/cos_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class _MetricsEndpointDict(TypedDict):

LIBID = "dc15fa84cef84ce58155fb84f6c6213a"
LIBAPI = 0
LIBPATCH = 20
LIBPATCH = 21

PYDEPS = ["cosl >= 0.0.50", "pydantic"]

Expand Down Expand Up @@ -472,7 +472,7 @@ def dump(self, databag: Optional[MutableMapping] = None, clear: bool = True):
return databag


class CosAgentProviderUnitData(DatabagModel):
class CosAgentProviderUnitData(DatabagModel): # type: ignore
"""Unit databag model for `cos-agent` relation."""

# The following entries are the same for all units of the same principal.
Expand All @@ -499,7 +499,7 @@ class CosAgentProviderUnitData(DatabagModel):
KEY: ClassVar[str] = "config"


class CosAgentPeersUnitData(DatabagModel):
class CosAgentPeersUnitData(DatabagModel): # type: ignore
"""Unit databag model for `peers` cos-agent machine charm peer relation."""

# We need the principal unit name and relation metadata to be able to render identifiers
Expand Down Expand Up @@ -598,7 +598,7 @@ class Receiver(pydantic.BaseModel):
)


class CosAgentRequirerUnitData(DatabagModel): # noqa: D101
class CosAgentRequirerUnitData(DatabagModel): # type: ignore
"""Application databag model for the COS-agent requirer."""

receivers: List[Receiver] = pydantic.Field(
Expand Down
85 changes: 56 additions & 29 deletions lib/charms/operator_libs_linux/v0/apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
and their properties (available groups, baseuri. gpg key). This class can add, disable, or
manipulate repositories. Items can be retrieved as `DebianRepository` objects.

In order add a new repository with explicit details for fields, a new `DebianRepository` can
be added to `RepositoryMapping`
In order to add a new repository with explicit details for fields, a new `DebianRepository`
can be added to `RepositoryMapping`

`RepositoryMapping` provides an abstraction around the existing repositories on the system,
and can be accessed and iterated over like any `Mapping` object, to retrieve values by key,
Expand Down Expand Up @@ -98,6 +98,10 @@
repo = DebianRepository.from_repo_line(line)
repositories.add(repo)
```

Dependencies:
Note that this module requires `opentelemetry-api`, which is already included into
your charm's virtual environment via `ops >= 2.21`.
"""

from __future__ import annotations
Expand All @@ -114,7 +118,10 @@
from typing import Any, Iterable, Iterator, Literal, Mapping
from urllib.parse import urlparse

import opentelemetry.trace

logger = logging.getLogger(__name__)
tracer = opentelemetry.trace.get_tracer(__name__)

# The unique Charmhub library identifier, never change it
LIBID = "7c3dbc9c2ad44a47bd6fcb25caa270e5"
Expand All @@ -124,7 +131,9 @@

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

PYDEPS = ["opentelemetry-api"]


VALID_SOURCE_TYPES = ("deb", "deb-src")
Expand Down Expand Up @@ -249,7 +258,9 @@ def _apt(
try:
env = os.environ.copy()
env["DEBIAN_FRONTEND"] = "noninteractive"
subprocess.run(_cmd, capture_output=True, check=True, text=True, env=env)
with tracer.start_as_current_span(_cmd[0]) as span:
span.set_attribute("argv", _cmd)
subprocess.run(_cmd, capture_output=True, check=True, text=True, env=env)
except CalledProcessError as e:
raise PackageError(
f"Could not {command} package(s) {package_names}: {e.stderr}"
Expand Down Expand Up @@ -464,18 +475,20 @@ def from_apt_cache(
arch: an optional architecture, defaulting to `dpkg --print-architecture`.
If an architecture is not specified, this will be used for selection.
"""
system_arch = check_output(
["dpkg", "--print-architecture"], universal_newlines=True
).strip()
cmd = ["dpkg", "--print-architecture"]
with tracer.start_as_current_span(cmd[0]) as span:
span.set_attribute("argv", cmd)
system_arch = check_output(cmd, universal_newlines=True).strip()
arch = arch if arch else system_arch

# Regexps are a really terrible way to do this. Thanks dpkg
keys = ("Package", "Architecture", "Version")

cmd = ["apt-cache", "show", package]
try:
output = check_output(
["apt-cache", "show", package], stderr=PIPE, universal_newlines=True
)
with tracer.start_as_current_span(cmd[0]) as span:
span.set_attribute("argv", cmd)
output = check_output(cmd, stderr=PIPE, universal_newlines=True)
except CalledProcessError as e:
raise PackageError(f"Could not list packages in apt-cache: {e.stderr}") from None

Expand Down Expand Up @@ -880,7 +893,9 @@ def update() -> None:
"""Update the apt cache via `apt-get update`."""
cmd = ["apt-get", "update", "--error-on=any"]
try:
subprocess.run(cmd, capture_output=True, check=True)
with tracer.start_as_current_span(cmd[0]) as span:
span.set_attribute("argv", cmd)
subprocess.run(cmd, capture_output=True, check=True)
except CalledProcessError as e:
logger.error(
"%s:\nstdout:\n%s\nstderr:\n%s",
Expand Down Expand Up @@ -1107,12 +1122,14 @@ def disable(self) -> None:
" Please raise an issue if you require this feature."
)
searcher = f"{self.repotype} {self.make_options_string()}{self.uri} {self.release}"
with fileinput.input(self._filename, inplace=True) as lines:
for line in lines:
if re.match(rf"^{re.escape(searcher)}\s", line):
print(f"# {line}", end="")
else:
print(line, end="")
with tracer.start_as_current_span("disable source") as span:
span.set_attribute("filename", self._filename)
with fileinput.input(self._filename, inplace=True) as lines:
for line in lines:
if re.match(rf"^{re.escape(searcher)}\s", line):
print(f"# {line}", end="")
else:
print(line, end="")

def import_key(self, key: str) -> None:
"""Import an ASCII Armor key.
Expand Down Expand Up @@ -1145,8 +1162,10 @@ def _get_keyid_by_gpg_key(key_material: bytes) -> str:
"""
# Use the same gpg command for both Xenial and Bionic
cmd = ["gpg", "--with-colons", "--with-fingerprint"]
ps = subprocess.run(cmd, capture_output=True, input=key_material)
out, err = ps.stdout.decode(), ps.stderr.decode()
with tracer.start_as_current_span(cmd[0]) as span:
span.set_attribute("argv", cmd)
ps = subprocess.run(cmd, capture_output=True, input=key_material)
out, err = ps.stdout.decode(), ps.stderr.decode()
if "gpg: no valid OpenPGP data found." in err:
raise GPGKeyError("Invalid GPG key material provided")
# from gnupg2 docs: fpr :: Fingerprint (fingerprint is in field 10)
Expand Down Expand Up @@ -1191,8 +1210,10 @@ def _get_key_by_keyid(keyid: str) -> str:
"https://keyserver.ubuntu.com" "/pks/lookup?op=get&options=mr&exact=on&search=0x{}"
)
curl_cmd = ["curl", keyserver_url.format(keyid)]
# use proxy server settings in order to retrieve the key
return check_output(curl_cmd).decode()
with tracer.start_as_current_span(curl_cmd[0]) as span:
span.set_attribute("argv", curl_cmd)
# use proxy server settings in order to retrieve the key
return check_output(curl_cmd).decode()

@staticmethod
def _dearmor_gpg_key(key_asc: bytes) -> bytes:
Expand All @@ -1207,8 +1228,11 @@ def _dearmor_gpg_key(key_asc: bytes) -> bytes:
Raises:
GPGKeyError
"""
ps = subprocess.run(["gpg", "--dearmor"], capture_output=True, input=key_asc)
out, err = ps.stdout, ps.stderr.decode()
cmd = ["gpg", "--dearmor"]
with tracer.start_as_current_span(cmd[0]) as span:
span.set_attribute("argv", cmd)
ps = subprocess.run(cmd, capture_output=True, input=key_asc)
out, err = ps.stdout, ps.stderr.decode()
if "gpg: no valid OpenPGP data found." in err:
raise GPGKeyError(
"Invalid GPG key material. Check your network setup"
Expand Down Expand Up @@ -1289,11 +1313,12 @@ def __init__(self):
if not os.path.isfile(default_sources):
raise

# read sources.list.d
for file in glob.iglob(os.path.join(sources_dir, "*.list")):
self.load(file)
for file in glob.iglob(os.path.join(sources_dir, "*.sources")):
self.load_deb822(file)
with tracer.start_as_current_span("load sources"):
# read sources.list.d
for file in glob.iglob(os.path.join(sources_dir, "*.list")):
self.load(file)
for file in glob.iglob(os.path.join(sources_dir, "*.sources")):
self.load_deb822(file)

def __contains__(self, key: Any) -> bool:
"""Magic method for checking presence of repo in mapping.
Expand Down Expand Up @@ -1533,7 +1558,9 @@ def _add_repository(
cmd.append("--no-update")
logger.info("%s", cmd)
try:
subprocess.run(cmd, check=True, capture_output=True)
with tracer.start_as_current_span(cmd[0]) as span:
span.set_attribute("argv", cmd)
subprocess.run(cmd, check=True, capture_output=True)
except CalledProcessError as e:
logger.error(
"subprocess.run(%s):\nstdout:\n%s\nstderr:\n%s",
Expand Down
Loading
Loading