Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config and actions processing to _extra_yaml_transform #1615

Merged
merged 4 commits into from
Mar 22, 2024
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
2 changes: 2 additions & 0 deletions charmcraft/application/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def _extra_yaml_transform(

preprocess.add_default_parts(yaml_data)
preprocess.add_bundle_snippet(self.project_dir, yaml_data)
preprocess.add_config(self.project_dir, yaml_data)
preprocess.add_actions(self.project_dir, yaml_data)

yaml_data = extensions.apply_extensions(self.project_dir, yaml_data)

Expand Down
24 changes: 2 additions & 22 deletions charmcraft/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,10 @@

from charmcraft import const, preprocess, utils
from charmcraft.const import (
JUJU_ACTIONS_FILENAME,
JUJU_CONFIG_FILENAME,
BaseStr,
BuildBaseStr,
CharmArch,
)
from charmcraft.metafiles.actions import parse_actions_yaml
from charmcraft.metafiles.config import parse_config_yaml
from charmcraft.models import charmcraft
from charmcraft.models.charmcraft import (
AnalysisConfig,
Expand Down Expand Up @@ -440,24 +436,8 @@ def from_yaml_file(cls, path: pathlib.Path) -> Self:
preprocess.add_default_parts(data)
preprocess.add_bundle_snippet(project_dir, data)
preprocess.add_metadata(project_dir, data)

config_file = project_dir / JUJU_CONFIG_FILENAME
if config_file.is_file():
if "config" in data:
raise errors.CraftValidationError(
f"Cannot specify 'config' section in 'charmcraft.yaml' when {JUJU_CONFIG_FILENAME!r} exists",
resolution=f"Move all data from {JUJU_CONFIG_FILENAME!r} to the 'config' section in 'charmcraft.yaml'",
)
data["config"] = parse_config_yaml(project_dir, allow_broken=True)

actions_file = project_dir / JUJU_ACTIONS_FILENAME
if actions_file.is_file():
if "actions" in data:
raise errors.CraftValidationError(
f"Cannot specify 'actions' section in 'charmcraft.yaml' when {JUJU_ACTIONS_FILENAME!r} exists",
resolution=f"Move all data from {JUJU_ACTIONS_FILENAME!r} to the 'actions' section in 'charmcraft.yaml'",
)
data["actions"] = parse_actions_yaml(project_dir).actions
preprocess.add_config(project_dir, data)
preprocess.add_actions(project_dir, data)

try:
project = cls.unmarshal(data)
Expand Down
61 changes: 54 additions & 7 deletions charmcraft/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import pathlib
from typing import Any

from craft_application import util
from craft_application import errors, util

from charmcraft import const, errors, utils
from charmcraft import const, utils


def add_default_parts(yaml_data: dict[str, Any]) -> None:
Expand Down Expand Up @@ -54,7 +54,7 @@ def add_metadata(project_dir: pathlib.Path, yaml_data: dict[str, Any]) -> None:
with metadata_path.open() as file:
metadata_yaml = util.safe_yaml_load(file)
if not isinstance(metadata_yaml, dict):
raise errors.CraftError(
raise errors.CraftValidationError(
"Invalid file: 'metadata.yaml'",
resolution="Ensure metadata.yaml meets the juju metadata.yaml specification.",
docs_url="https://juju.is/docs/sdk/metadata-yaml",
Expand All @@ -66,15 +66,15 @@ def add_metadata(project_dir: pathlib.Path, yaml_data: dict[str, Any]) -> None:
duplicate_fields.append(field)
yaml_data.setdefault(field, metadata_yaml.get(field))
if duplicate_fields:
raise errors.CraftError(
raise errors.CraftValidationError(
"Fields in charmcraft.yaml cannot be duplicated in metadata.yaml",
details=f"Duplicate fields: {utils.humanize_list(duplicate_fields, 'and')}",
resolution="Remove the duplicate fields from metadata.yaml.",
retcode=65, # Data error. per sysexits.h
)


def add_bundle_snippet(project_dir: pathlib.Path, yaml_data: dict[str, Any]):
def add_bundle_snippet(project_dir: pathlib.Path, yaml_data: dict[str, Any]) -> None:
"""Add metadata from bundle.yaml to a bundle.

:param yaml_data: The raw YAML dictionary of the project.
Expand All @@ -86,7 +86,7 @@ def add_bundle_snippet(project_dir: pathlib.Path, yaml_data: dict[str, Any]):

bundle_file = project_dir / const.BUNDLE_FILENAME
if not bundle_file.is_file():
raise errors.CraftError(
raise errors.CraftValidationError(
f"Missing 'bundle.yaml' file: {str(bundle_file)!r}",
resolution="Create a 'bundle.yaml' file in the same directory as 'charmcraft.yaml'.",
docs_url="https://juju.is/docs/sdk/create-a-charm-bundle",
Expand All @@ -97,11 +97,58 @@ def add_bundle_snippet(project_dir: pathlib.Path, yaml_data: dict[str, Any]):
with bundle_file.open() as bf:
bundle = util.safe_yaml_load(bf)
if not isinstance(bundle, dict):
raise errors.CraftError(
raise errors.CraftValidationError(
"Incorrectly formatted 'bundle.yaml' file",
resolution="Ensure 'bundle.yaml' matches the Juju 'bundle.yaml' format.",
docs_url="https://juju.is/docs/sdk/charm-bundles",
reportable=False,
retcode=65, # EX_DATAERR from sysexits.h
)
yaml_data["bundle"] = bundle


def add_config(project_dir: pathlib.Path, yaml_data: dict[str, Any]) -> None:
"""Add configuration options from config.yaml to existing YAML data.

:param project_dir: The Path to the directory containing charmcraft.yaml
:param yaml_data: The raw YAML dictionary of the project.
:returns: The same dictionary passed in, with necessary mutations.
"""
config_file = project_dir / const.JUJU_CONFIG_FILENAME
if not config_file.exists():
return

if "config" in yaml_data:
raise errors.CraftValidationError(
f"Cannot specify 'config' section in 'charmcraft.yaml' when {const.JUJU_CONFIG_FILENAME!r} exists",
resolution=f"Move all data from {const.JUJU_CONFIG_FILENAME!r} to the 'config' section in 'charmcraft.yaml'",
docs_url="https://juju.is/docs/sdk/charmcraft-yaml",
retcode=65, # Data error, per sysexits.h
)

with config_file.open() as f:
yaml_data["config"] = util.safe_yaml_load(f)


def add_actions(project_dir: pathlib.Path, yaml_data: dict[str, Any]) -> None:
"""Add actions from actions.yaml to existing YAML data.

:param project_dir: The Path to the directory containing charmcraft.yaml
:param yaml_data: The raw YAML dictionary of the project.
:returns: The same dictionary passed in, with necessary mutations.
"""
actions_file = project_dir / const.JUJU_ACTIONS_FILENAME
if not actions_file.exists():
return

if "actions" in yaml_data:
raise errors.CraftValidationError(
f"Cannot specify 'actions' section in 'charmcraft.yaml' when {const.JUJU_ACTIONS_FILENAME!r} exists",
resolution=f"Move all data from {const.JUJU_ACTIONS_FILENAME!r} to the 'actions' section in 'charmcraft.yaml'",
docs_url="https://juju.is/docs/sdk/charmcraft-yaml",
retcode=65, # Data error, per sysexits.h
)
with actions_file.open() as f:
actions = util.safe_yaml_load(f)
if actions and isinstance(actions, dict):
yaml_data["actions"] = actions
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: example-charm
summary: An example charm with platforms
description: |
A description for an example charm with platforms.
type: charm
base: ubuntu@22.04
platforms:
amd64:

parts:
charm:
plugin: charm

actions:
pause:
description: Pause the database.
resume:
description: Resume a paused database.
snapshot:
description: Take a snapshot of the database.
params:
filename:
type: string
description: The name of the snapshot file.
compression:
type: object
description: The type of compression to use.
properties:
kind:
type: string
enum: [gzip, bzip2, xz]
quality:
description: Compression quality
type: integer
minimum: 0
maximum: 9
required: [filename]
additionalProperties: false
46 changes: 46 additions & 0 deletions tests/integration/sample-charms/actions-included/expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: example-charm
summary: An example charm with platforms
description: |
A description for an example charm with platforms.
base: ubuntu@22.04
platforms:
amd64: null
parts:
charm:
source: .
charm-entrypoint: src/charm.py
charm-binary-python-packages: []
charm-python-packages: []
charm-requirements: []
charm-strict-dependencies: false
plugin: charm
type: charm
actions:
pause:
description: Pause the database.
resume:
description: Resume a paused database.
snapshot:
description: Take a snapshot of the database.
params:
filename:
type: string
description: The name of the snapshot file.
compression:
type: object
description: The type of compression to use.
properties:
kind:
type: string
enum:
- gzip
- bzip2
- xz
quality:
description: Compression quality
type: integer
minimum: 0
maximum: 9
required:
- filename
additionalProperties: false
24 changes: 24 additions & 0 deletions tests/integration/sample-charms/actions-separate/actions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
pause:
description: Pause the database.
resume:
description: Resume a paused database.
snapshot:
description: Take a snapshot of the database.
params:
filename:
type: string
description: The name of the snapshot file.
compression:
type: object
description: The type of compression to use.
properties:
kind:
type: string
enum: [gzip, bzip2, xz]
quality:
description: Compression quality
type: integer
minimum: 0
maximum: 9
required: [filename]
additionalProperties: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: example-charm
summary: An example charm with platforms
description: |
A description for an example charm with platforms.
type: charm
base: ubuntu@22.04
platforms:
amd64:

parts:
charm:
plugin: charm
46 changes: 46 additions & 0 deletions tests/integration/sample-charms/actions-separate/expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: example-charm
summary: An example charm with platforms
description: |
A description for an example charm with platforms.
base: ubuntu@22.04
platforms:
amd64: null
parts:
charm:
source: .
charm-entrypoint: src/charm.py
charm-binary-python-packages: []
charm-python-packages: []
charm-requirements: []
charm-strict-dependencies: false
plugin: charm
type: charm
actions:
pause:
description: Pause the database.
resume:
description: Resume a paused database.
snapshot:
description: Take a snapshot of the database.
params:
filename:
type: string
description: The name of the snapshot file.
compression:
type: object
description: The type of compression to use.
properties:
kind:
type: string
enum:
- gzip
- bzip2
- xz
quality:
description: Compression quality
type: integer
minimum: 0
maximum: 9
required:
- filename
additionalProperties: false
12 changes: 12 additions & 0 deletions tests/integration/sample-charms/basic-bases/charmcraft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: example-charm
summary: An example charm with bases
description: |
A description for an example charm with bases.
type: charm
bases:
- name: ubuntu
channel: "22.04"

parts:
charm:
plugin: charm
21 changes: 21 additions & 0 deletions tests/integration/sample-charms/basic-bases/expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: example-charm
summary: An example charm with bases
description: |
A description for an example charm with bases.
parts:
charm:
source: .
charm-entrypoint: src/charm.py
charm-binary-python-packages: []
charm-python-packages: []
charm-requirements: []
charm-strict-dependencies: false
plugin: charm
type: charm
bases:
- build-on:
- name: ubuntu
channel: '22.04'
run-on:
- name: ubuntu
channel: '22.04'
12 changes: 12 additions & 0 deletions tests/integration/sample-charms/basic-platforms/charmcraft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: example-charm
summary: An example charm with platforms
description: |
A description for an example charm with platforms.
type: charm
base: ubuntu@22.04
platforms:
amd64:

parts:
charm:
plugin: charm
17 changes: 17 additions & 0 deletions tests/integration/sample-charms/basic-platforms/expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: example-charm
summary: An example charm with platforms
description: |
A description for an example charm with platforms.
base: ubuntu@22.04
platforms:
amd64: null
parts:
charm:
source: .
charm-entrypoint: src/charm.py
charm-binary-python-packages: []
charm-python-packages: []
charm-requirements: []
charm-strict-dependencies: false
plugin: charm
type: charm
Loading
Loading