Skip to content

Commit

Permalink
support unified charmcraft model
Browse files Browse the repository at this point in the history
  • Loading branch information
PietroPasotti committed Jan 4, 2024
1 parent 2789c0a commit bd0baf3
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 21 deletions.
43 changes: 34 additions & 9 deletions scenario/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,8 @@ class _CharmSpec(_DCBase):
"""Charm spec."""

charm_type: Type["CharmType"]

# TODO: consider unifying the data model since nowadays it's all in one file
meta: Optional[Dict[str, Any]]
actions: Optional[Dict[str, Any]] = None
config: Optional[Dict[str, Any]] = None
Expand All @@ -956,16 +958,10 @@ class _CharmSpec(_DCBase):
is_autoloaded: bool = False

@staticmethod
def autoload(charm_type: Type["CharmType"]):
charm_source_path = Path(inspect.getfile(charm_type))
charm_root = charm_source_path.parent.parent

def _load_metadata_legacy(charm_root: Path):
# back in the days, we used to have separate metadata.yaml, config.yaml and actions.yaml
# files for charm metadata.
metadata_path = charm_root / "metadata.yaml"
if not metadata_path.exists():
raise MetadataNotFoundError(
f"invalid charm root {charm_root!r}; "
f"expected to contain at least a `metadata.yaml` file.",
)
meta = yaml.safe_load(metadata_path.open())

actions = config = None
Expand All @@ -978,6 +974,35 @@ def autoload(charm_type: Type["CharmType"]):
if actions_path.exists():
actions = yaml.safe_load(actions_path.open())

return meta, config, actions

@staticmethod
def _load_metadata(charm_root: Path):
metadata_path = charm_root / "charmcraft.yaml"
meta = yaml.safe_load(metadata_path.open()) if metadata_path.exists() else {}
config = meta.get("config", None)
actions = meta.get("actions", None)
return meta, config, actions

@staticmethod
def autoload(charm_type: Type["CharmType"]):
charm_source_path = Path(inspect.getfile(charm_type))
charm_root = charm_source_path.parent.parent

metadata_path = charm_root / "metadata.yaml"

if metadata_path.exists():
meta, config, actions = _CharmSpec._load_metadata_legacy(charm_root)
else:
meta, config, actions = _CharmSpec._load_metadata(charm_root)

if not meta:
raise MetadataNotFoundError(
f"invalid charm root {charm_root!r}; "
f"expected to contain at least a `charmcraft.yaml` file "
f"(or a `metadata.yaml` file if it's an old charm).",
)

return _CharmSpec(
charm_type=charm_type,
meta=meta,
Expand Down
42 changes: 30 additions & 12 deletions tests/test_charm_spec_autoload.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,51 +38,69 @@ def create_tempcharm(
actions=None,
config=None,
name: str = "MyCharm",
legacy: bool = True,
):
src = root / "src"
src.mkdir(parents=True)
charmpy = src / "charm.py"
charmpy.write_text(charm)

if meta is not None:
(root / "metadata.yaml").write_text(yaml.safe_dump(meta))
if legacy:
if meta is not None:
(root / "metadata.yaml").write_text(yaml.safe_dump(meta))

if actions is not None:
(root / "actions.yaml").write_text(yaml.safe_dump(actions))
if actions is not None:
(root / "actions.yaml").write_text(yaml.safe_dump(actions))

if config is not None:
(root / "config.yaml").write_text(yaml.safe_dump(config))
if config is not None:
(root / "config.yaml").write_text(yaml.safe_dump(config))
else:
unified_meta = meta or {}
if actions:
unified_meta["actions"] = actions
if config:
unified_meta["config"] = config
if unified_meta:
(root / "charmcraft.yaml").write_text(yaml.safe_dump(unified_meta))

with import_name(name, charmpy) as charm:
yield charm


def test_meta_autoload(tmp_path):
with create_tempcharm(tmp_path, meta={"name": "foo"}) as charm:
@pytest.mark.parametrize("legacy", (True, False))
def test_meta_autoload(tmp_path, legacy):
with create_tempcharm(tmp_path, legacy=legacy, meta={"name": "foo"}) as charm:
ctx = Context(charm)
ctx.run("start", State())


def test_no_meta_raises(tmp_path):
@pytest.mark.parametrize("legacy", (True, False))
def test_no_meta_raises(tmp_path, legacy):
with create_tempcharm(
tmp_path,
legacy=legacy,
) as charm:
# metadata not found:
with pytest.raises(ContextSetupError):
Context(charm)


def test_relations_ok(tmp_path):
@pytest.mark.parametrize("legacy", (True, False))
def test_relations_ok(tmp_path, legacy):
with create_tempcharm(
tmp_path, meta={"name": "josh", "requires": {"cuddles": {"interface": "arms"}}}
tmp_path,
legacy=legacy,
meta={"name": "josh", "requires": {"cuddles": {"interface": "arms"}}},
) as charm:
# this would fail if there were no 'cuddles' relation defined in meta
Context(charm).run("start", State(relations=[Relation("cuddles")]))


def test_config_defaults(tmp_path):
@pytest.mark.parametrize("legacy", (True, False))
def test_config_defaults(tmp_path, legacy):
with create_tempcharm(
tmp_path,
legacy=legacy,
meta={"name": "josh"},
config={"options": {"foo": {"type": "bool", "default": True}}},
) as charm:
Expand Down

0 comments on commit bd0baf3

Please sign in to comment.