ワンクリックで
relation-data-design
Designing and implementing relation data bags for charm integrations
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Designing and implementing relation data bags for charm integrations
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Known-good Juju bundle shapes (COS Lite, 12-Factor + COS, Identity Platform, Charmed Kubeflow) — canonical app lists and relation edges
Implementing Juju actions for operational tasks in charms. WHEN: add Juju actions to a charm, implement backup / rotate-credentials / restore actions, declare actions in charmcraft.yaml, write action handlers, test actions with Scenario, run actions with juju run.
Adding and validating charm configuration options. WHEN: add charm configuration options, declare config in charmcraft.yaml, validate config values in config-changed, apply config to a Pebble layer, apply config to a machine charm, write Scenario tests for config.
End-to-end workflow for building ops-framework charms for custom applications (K8s and machine). WHEN: build a custom Juju charm, write src/charm.py with Pebble, write a machine charm with systemd, decide K8s vs machine substrate, scaffold a non-paas-charm with charmcraft init, customise the ops framework charm lifecycle.
Workflow for charming infrastructure software (databases, caches, message brokers, proxies, monitoring). WHEN: charm infrastructure software, charm a database / cache / message broker / proxy / monitoring system, implement peer relations, leader election, primary/replica failover, backup and restore actions, clustering.
Expert guidance for developing, building, testing, and publishing Juju charms using charmcraft. WHEN: edit charmcraft.yaml, run charmcraft pack, run charmcraft init, manage charm libraries, publish a charm to Charmhub, set up multi-base builds, configure relations / config options, set up storage or containers.
| name | relation-data-design |
| description | Designing and implementing relation data bags for charm integrations |
| globs | ["metadata.yaml","charmcraft.yaml","src/charm.py","src/**/charm.py","lib/**"] |
Relations are how Juju charms communicate. Each relation has data bags — key-value stores that charms read and write to exchange configuration, credentials, and status.
In charmcraft.yaml:
provides:
website:
interface: http
requires:
database:
interface: mysql
limit: 1
peers:
cluster:
interface: myapp-cluster
Prefer existing interface libraries from PyPI or Charmhub. Common interfaces:
| Interface | PyPI Package | Purpose |
|---|---|---|
mysql | ops-lib-mysql | MySQL database |
postgresql | ops-lib-pgsql | PostgreSQL database |
ingress | traefik-route-k8s | HTTP ingress |
tracing | ops-tracing | Distributed tracing |
grafana-dashboard | — | Grafana dashboards |
loki-push-api | — | Log forwarding |
prometheus-scrape | — | Metrics scraping |
Check PyPI first; fall back to charmcraft fetch-libs for Charmhub-only libraries.
import ops
class MyCharm(ops.CharmBase):
def __init__(self, framework: ops.Framework):
super().__init__(framework)
self.framework.observe(
self.on.database_relation_changed,
self._on_database_changed,
)
self.framework.observe(
self.on.database_relation_broken,
self._on_database_broken,
)
def _on_database_changed(self, event: ops.RelationChangedEvent):
if not event.relation.data.get(event.app):
return
data = event.relation.data[event.app]
host = data.get("host")
port = data.get("port")
if not host or not port:
self.unit.status = ops.WaitingStatus("waiting for database credentials")
return
# Configure workload with database connection.
self.unit.status = ops.ActiveStatus()
def _on_database_broken(self, event: ops.RelationBrokenEvent):
# Clean up database configuration.
self.unit.status = ops.BlockedStatus("database relation removed")
def _on_website_relation_joined(self, event: ops.RelationJoinedEvent):
if not self.unit.is_leader():
return
event.relation.data[self.app]["url"] = f"http://{self._get_hostname()}:8080"
event.relation.data[self.app]["port"] = "8080"
Peer relations are useful for sharing state between units:
def _on_cluster_relation_changed(self, event: ops.RelationChangedEvent):
# Read data from other units.
for unit in event.relation.units:
unit_data = event.relation.data[unit]
ip = unit_data.get("ip")
if ip:
# Add to cluster membership list.
...
def _set_unit_address(self):
# Write this unit's data to the peer relation.
rel = self.model.get_relation("cluster")
if rel:
rel.data[self.unit]["ip"] = str(self.model.get_binding("cluster").network.bind_address)
Application data for shared config, unit data for per-unit info. Database credentials go in app data (set by leader). Unit IP addresses go in unit data.
Only the leader writes app data. Always guard with self.unit.is_leader() before writing to event.relation.data[self.app].
Validate before using. Remote data may be incomplete. Always check for required keys before acting on relation data.
Use relation-changed for updates. This event fires whenever the remote side updates its data bags. Handle partial data gracefully — the remote application may set fields incrementally.
Use relation-broken for cleanup. Remove any configuration that depends on the relation.
All values must be strings. Relation data bags only store str → str mappings. Serialise complex data with JSON if needed, but prefer flat key-value pairs.
Keep data bags small. Do not store large blobs. Use relation data for connection parameters and metadata, not bulk data.
Use interface libraries where possible. They handle serialisation, validation, and versioning of the data format. Writing raw relation data is acceptable for simple custom interfaces.
When the charm shares a Juju secret over a relation, put only the secret identifier (an opaque string) in the relation databag — never the secret body. Two rules worth knowing:
secret://Xid form. Treat the value as a blob and hand it back to
the framework (self.model.get_secret(id=...)).secret.grant(relation) — consuming charms cannot
grant to themselves. When you're writing the charm that owns the
secret, do the grant in the relation-joined / relation-changed
handler. When you're writing the consumer, just get_secret(id=...);
do not attempt to manage the grant from your side.# Owner (offering) side
def _on_db_relation_changed(self, event):
if not self.unit.is_leader():
return
secret = self.model.get_secret(label="db-password")
secret.grant(event.relation)
event.relation.data[self.app]["password-id"] = secret.id
# Consumer side
def _on_db_relation_changed(self, event):
secret_id = event.relation.data[event.app].get("password-id")
if not secret_id:
return
secret = self.model.get_secret(id=secret_id)
password = secret.get_content()["password"]
event.relation.data[event.app] without guarding event.app. Both are checked deterministically by charmlint — REL001 flags subscript reads on event.app / event.unit without an is None / .get() guard; REL002 flags writes to event.relation.data[self.app] without an is_leader() guard. Run charmlint to surface these.relation-broken — the charm should gracefully degrade when a relation is removed.secret.grant(relation).Xid format or any specific length.