Skip to content
Open
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: 1 addition & 1 deletion .github/.copilot-collections.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ copilot:
version: v0.12.0
collections:
- starcraft-core
- starcraft-docs
- starcraft-docs
2 changes: 1 addition & 1 deletion .github/workflows/copilot-collections-update.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ jobs:
check-update:
# Pinned to @main to get the latest logic, but the content version is controlled by .github/.copilot-collections.yaml
uses: canonical/copilot-collections/.github/workflows/auto_update_collections.yaml@main
secrets: inherit
secrets: inherit
31 changes: 31 additions & 0 deletions charmcraft/extensions/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pathlib import Path
from typing import Any

import yaml
from overrides import override

from ..errors import ExtensionError
Expand Down Expand Up @@ -71,6 +72,8 @@
}

COS_SUBDIRS = {"grafana_dashboards", "loki_alert_rules", "prometheus_alert_rules"}
PAAS_CONFIG_FILE = "paas-config.yaml"
JSON_LOGGING_SUPPORTED_FRAMEWORKS = {"fastapi", "flask", "django"}


class _AppBase(Extension):
Expand Down Expand Up @@ -194,6 +197,34 @@ def _check_input(self) -> None:
"Non-optional configuration options can not have default values.\n"
f"Please either remove the default value or set optional field to true or remove it for the {', '.join(invalid_non_optionals)} configuration option(s)."
)
self._check_paas_config()

def _check_paas_config(self) -> None:
"""Validate ``paas-config.yaml`` syntax and framework logging compatibility."""
config_path = self.project_root / PAAS_CONFIG_FILE
if not config_path.exists():
return

try:
parsed = yaml.safe_load(config_path.read_text(encoding="utf-8"))
except yaml.YAMLError as exc:
raise ExtensionError(f"invalid YAML in {PAAS_CONFIG_FILE}: {exc}") from exc

if parsed is None:
return
if not isinstance(parsed, dict):
raise ExtensionError(f"{PAAS_CONFIG_FILE} must contain a top-level mapping")

framework_logging_format = parsed.get("framework_logging_format")
if (
isinstance(framework_logging_format, str)
and framework_logging_format.lower() == "json"
and self.framework not in JSON_LOGGING_SUPPORTED_FRAMEWORKS
):
raise ExtensionError(
f"framework_logging_format: json in {PAAS_CONFIG_FILE} is not supported "
f"for '{self.framework}-framework'"
)

def _validate_cos_custom_dir(self) -> None:
"""Validate the custom COS directory if present."""
Expand Down
40 changes: 40 additions & 0 deletions tests/extensions/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,46 @@ def test_handle_charm_part_adds_part(flask_input_yaml, tmp_path):
}


def test_invalid_paas_config_yaml_fails_pack(flask_input_yaml, tmp_path):
(tmp_path / "paas-config.yaml").write_text(
"framework_logging_format: [json", encoding="utf-8"
)

with pytest.raises(ExtensionError, match=r"invalid YAML in paas-config.yaml"):
extensions.apply_extensions(tmp_path, flask_input_yaml)


def test_json_framework_logging_disallowed_framework_fails_pack(tmp_path):
go_input_yaml = {
"type": "charm",
"name": "test-go",
"summary": "test summary",
"description": "test description",
"base": "ubuntu@24.04",
"platforms": {
"amd64": None,
},
"extensions": ["go-framework"],
"config": NON_OPTIONAL_OPTIONS,
}
(tmp_path / "paas-config.yaml").write_text(
"framework_logging_format: json\n", encoding="utf-8"
)

with pytest.raises(
ExtensionError,
match=r"framework_logging_format: json in paas-config.yaml is not supported for 'go-framework'",
):
extensions.apply_extensions(tmp_path, go_input_yaml)


def test_json_framework_logging_supported_framework_passes(flask_input_yaml, tmp_path):
(tmp_path / "paas-config.yaml").write_text(
"framework_logging_format: json\n", encoding="utf-8"
)
extensions.apply_extensions(tmp_path, flask_input_yaml)


@pytest.mark.parametrize(
("input_yaml", "requires", "expected_options"),
[
Expand Down
Loading