Skip to content
Closed
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
14 changes: 14 additions & 0 deletions kepler/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ containers:
demo-server:
resource: demo-server-image

actions:
update-backup:
description: |
Overwrite the workload's backup file at /etc/myapp/backup.yaml with the
contents provided in the "data" action parameter.

This is used to verify whether writes performed by the charm to a
host-mounted file propagate back to the host file.
params:
data:
type: string
description: The new contents to write to the backup file.
required: [data]

resources:
# An OCI image resource for the container listed above.
demo-server-image:
Expand Down
1 change: 1 addition & 0 deletions kepler/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ requires-python = ">=3.10"
# 'charm-libs' block in charmcraft.yaml, run `charmcraft fetch-libs` to download the libraries,
# then inspect the libraries for dependencies specified in PYDEPS. List those dependencies here.
dependencies = [
"charmlibs-pathops>=1,<2",
"ops~=3.7",
]

Expand Down
23 changes: 23 additions & 0 deletions kepler/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import logging

import ops
from charmlibs import pathops

# Log messages can be retrieved using juju debug-log
logger = logging.getLogger(__name__)
Expand All @@ -31,6 +32,28 @@ def __init__(self, framework: ops.Framework) -> None:
super().__init__(framework)
self.pebble_service_name = "fastapi-service"
framework.observe(self.on["demo-server"].pebble_ready, self._on_demo_server_pebble_ready)
framework.observe(self.on.update_backup_action, self._on_update_backup)

def _on_update_backup(self, event: ops.ActionEvent) -> None:
"""Overwrite the backup file in the workload container.

The backup file is expected to be host-mounted at
``/etc/myapp/backup.yaml`` (see the unit test). We remove the existing
file before writing the new contents, to emulate a charm that clears a
stale file before writing fresh data.
"""
container = self.unit.get_container("demo-server")
if not container.can_connect():
event.fail("workload container is not ready")
return
data = event.params["data"]
backup_root = pathops.ContainerPath("/etc/myapp", container=container)
backup_file = backup_root / "backup.yaml"
# Remove any existing file before writing, so that we write a fresh file
# rather than appending to / modifying stale contents.
backup_file.unlink(missing_ok=True)
backup_file.write_text(data)
event.set_results({"written": data})

def _on_demo_server_pebble_ready(self, event: ops.PebbleReadyEvent) -> None:
"""Define and start a workload using the Pebble API."""
Expand Down
33 changes: 33 additions & 0 deletions kepler/tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,36 @@ def test_pebble_layer():
state_out.get_container(container.name).service_statuses["fastapi-service"]
== ops.pebble.ServiceStatus.ACTIVE
)


def test_update_backup_writes_through_mount(tmp_path):
"""Refutation attempt for doc.md's claim about host-mounted files.

doc.md (line 210) claims: "If the charm writes to /etc/myapp/backup.yaml in
the container while handling the event, backup_file.read_text() will return
the data that the charm wrote."

This test mounts a host file at /etc/myapp/backup.yaml, runs the
update-backup action (which removes the file and writes new contents), and
checks whether the host file reflects the write.
"""
backup_file = tmp_path / "backup.yaml"
original_data = "original: data\n"
backup_file.write_text(original_data)

ctx = testing.Context(KosmosCharm)
container = testing.Container(
name="demo-server",
can_connect=True,
mounts={"backup": testing.Mount(location="/etc/myapp/backup.yaml", source=backup_file)},
)
state_in = testing.State(containers={container})

new_data = "updated: data\n"
ctx.run(
ctx.on.action("update-backup", params={"data": new_data}),
state_in,
)

# If the claim holds, the host file should now contain the new data.
assert backup_file.read_text() == new_data
84 changes: 50 additions & 34 deletions kepler/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading