__init__.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. """Docker Compose module with multi-schema support."""
  2. import logging
  3. from collections import OrderedDict
  4. from typing import Annotated
  5. from typer import Argument, Option
  6. from ...core.module import Module
  7. from ...core.module.base_commands import validate_templates
  8. from ...core.registry import registry
  9. from ...core.schema import has_schema, list_versions, load_schema
  10. from .validate import run_docker_validation
  11. logger = logging.getLogger(__name__)
  12. def _load_json_spec_as_dict(version: str) -> OrderedDict:
  13. """Load JSON schema and convert to dict format for backward compatibility.
  14. Args:
  15. version: Schema version
  16. Returns:
  17. OrderedDict in the same format as Python specs
  18. """
  19. logger.debug(f"Loading compose schema {version} from JSON")
  20. json_spec = load_schema("compose", version)
  21. # Convert JSON array format to OrderedDict format
  22. spec_dict = OrderedDict()
  23. for section_data in json_spec:
  24. section_key = section_data["key"]
  25. # Build section dict
  26. section_dict = {}
  27. if "title" in section_data:
  28. section_dict["title"] = section_data["title"]
  29. if "description" in section_data:
  30. section_dict["description"] = section_data["description"]
  31. if "toggle" in section_data:
  32. section_dict["toggle"] = section_data["toggle"]
  33. if "required" in section_data:
  34. section_dict["required"] = section_data["required"]
  35. if "needs" in section_data:
  36. section_dict["needs"] = section_data["needs"]
  37. # Convert vars array to dict
  38. vars_dict = OrderedDict()
  39. for var_data in section_data["vars"]:
  40. var_name = var_data["name"]
  41. var_dict = {k: v for k, v in var_data.items() if k != "name"}
  42. vars_dict[var_name] = var_dict
  43. section_dict["vars"] = vars_dict
  44. spec_dict[section_key] = section_dict
  45. return spec_dict
  46. # Schema version mapping - loads JSON schemas on-demand
  47. class _SchemaDict(dict):
  48. """Dict subclass that loads JSON schemas on-demand."""
  49. def __getitem__(self, version):
  50. if not has_schema("compose", version):
  51. raise KeyError(
  52. f"Schema version {version} not found for compose module. "
  53. f"Available: {', '.join(list_versions('compose'))}"
  54. )
  55. return _load_json_spec_as_dict(version)
  56. def __contains__(self, version):
  57. return has_schema("compose", version)
  58. # Initialize schema dict
  59. SCHEMAS = _SchemaDict()
  60. # Default spec - load latest version
  61. spec = _load_json_spec_as_dict("1.2")
  62. class ComposeModule(Module):
  63. """Docker Compose module with extended validation."""
  64. name = "compose"
  65. description = "Manage Docker Compose configurations"
  66. schema_version = "1.2" # Current schema version supported by this module
  67. schemas = SCHEMAS # Available schema versions
  68. def validate( # noqa: PLR0913
  69. self,
  70. template_id: Annotated[
  71. str | None,
  72. Argument(help="Template ID to validate (omit to validate all templates)"),
  73. ] = None,
  74. *,
  75. path: Annotated[
  76. str | None,
  77. Option("--path", help="Path to template directory for validation"),
  78. ] = None,
  79. verbose: Annotated[bool, Option("--verbose", "-v", help="Show detailed validation information")] = False,
  80. semantic: Annotated[
  81. bool,
  82. Option(
  83. "--semantic/--no-semantic",
  84. help="Enable semantic validation (Docker Compose schema, etc.)",
  85. ),
  86. ] = True,
  87. docker: Annotated[
  88. bool,
  89. Option(
  90. "--docker/--no-docker",
  91. help="Enable Docker Compose validation using 'docker compose config'",
  92. ),
  93. ] = False,
  94. docker_test_all: Annotated[
  95. bool,
  96. Option(
  97. "--docker-test-all",
  98. help="Test all variable combinations (minimal, maximal, each toggle). Requires --docker",
  99. ),
  100. ] = False,
  101. ) -> None:
  102. """Validate templates for Jinja2 syntax, undefined variables, and semantic correctness.
  103. Extended for Docker Compose with optional docker compose config validation.
  104. Use --docker for single config test, --docker-test-all for comprehensive testing.
  105. Examples:
  106. # Validate specific template
  107. compose validate netbox
  108. # Validate all templates
  109. compose validate
  110. # Validate with Docker Compose config check
  111. compose validate netbox --docker
  112. """
  113. # Run standard validation first
  114. validate_templates(self, template_id, path, verbose, semantic)
  115. # If docker validation is enabled and we have a specific template
  116. if docker and (template_id or path):
  117. run_docker_validation(self, template_id, path, docker_test_all, verbose)
  118. registry.register(ComposeModule)