ignition-stack 0.1.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/.gitignore +0 -5
  2. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/PKG-INFO +1 -4
  3. ignition_stack-0.3.0/builtin_modules.yaml +122 -0
  4. ignition_stack-0.3.0/ignition_stack/__init__.py +1 -0
  5. ignition_stack-0.3.0/ignition_stack/catalog/builtins.py +171 -0
  6. ignition_stack-0.3.0/ignition_stack/cli.py +563 -0
  7. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/completion.py +48 -0
  8. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/engine.py +44 -8
  9. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/templates/services/ignition.yaml.j2 +17 -0
  10. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/writer.py +51 -8
  11. ignition_stack-0.3.0/ignition_stack/config/__init__.py +20 -0
  12. ignition_stack-0.3.0/ignition_stack/config/io.py +125 -0
  13. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/config/schema.py +113 -2
  14. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/lifecycle/__init__.py +1 -1
  15. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/lifecycle/cleanup.py +3 -2
  16. ignition_stack-0.3.0/ignition_stack/lifecycle/record.py +73 -0
  17. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/lifecycle/regenerate.py +2 -2
  18. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/postsetup/generator.py +38 -0
  19. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/profiles/__init__.py +4 -0
  20. ignition_stack-0.3.0/ignition_stack/profiles/base.py +236 -0
  21. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/profiles/hub_and_spoke.py +5 -0
  22. ignition_stack-0.3.0/ignition_stack/profiles/scaleout.py +80 -0
  23. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/services/resolver.py +54 -1
  24. ignition_stack-0.3.0/ignition_stack/templates/post-setup/redundancy-pairing.md.j2 +24 -0
  25. ignition_stack-0.3.0/ignition_stack/templates/redundancy/redundancy.xml.j2 +25 -0
  26. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/standalone-postgres/scripts/docker-bootstrap.sh +9 -1
  27. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/wizard.py +117 -7
  28. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/modules.yaml +1 -1
  29. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/pyproject.toml +2 -6
  30. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/profiles/scaleout/docker-compose.yaml +0 -1
  31. ignition_stack-0.3.0/tests/golden/profiles/scaleout-redundant/docker-compose.yaml +155 -0
  32. ignition_stack-0.3.0/tests/test_builtin_catalog_smoke.py +114 -0
  33. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_compose_engine.py +3 -4
  34. ignition_stack-0.3.0/tests/test_declarative_io.py +193 -0
  35. ignition_stack-0.3.0/tests/test_disable_builtins.py +249 -0
  36. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_docs_cli_reference.py +3 -1
  37. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_lifecycle.py +51 -43
  38. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_profiles.py +189 -3
  39. ignition_stack-0.3.0/tests/test_redundancy.py +267 -0
  40. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_service_catalog.py +16 -8
  41. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_service_catalog_smoke.py +3 -3
  42. ignition_stack-0.3.0/verification/redundancy-spike/README.md +239 -0
  43. ignition_stack-0.3.0/verification/smoke/README.md +58 -0
  44. ignition_stack-0.1.1/ignition_stack/__init__.py +0 -1
  45. ignition_stack-0.1.1/ignition_stack/cli.py +0 -354
  46. ignition_stack-0.1.1/ignition_stack/config/__init__.py +0 -8
  47. ignition_stack-0.1.1/ignition_stack/lifecycle/record.py +0 -67
  48. ignition_stack-0.1.1/ignition_stack/profiles/base.py +0 -108
  49. ignition_stack-0.1.1/ignition_stack/profiles/scaleout.py +0 -65
  50. ignition_stack-0.1.1/scripts/seeding-poc/README.md +0 -37
  51. ignition_stack-0.1.1/verification/phase-1-3/README.md +0 -58
  52. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/LICENSE +0 -0
  53. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/README.md +0 -0
  54. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/catalog/__init__.py +0 -0
  55. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/catalog/download.py +0 -0
  56. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/catalog/loader.py +0 -0
  57. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/catalog/schema.py +0 -0
  58. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/catalog/verify.py +0 -0
  59. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/commands/__init__.py +0 -0
  60. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/commands/modules.py +0 -0
  61. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/__init__.py +0 -0
  62. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/templates/footer.yaml.j2 +0 -0
  63. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/templates/header.yaml.j2 +0 -0
  64. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/compose/templates/services/bootstrap.yaml.j2 +0 -0
  65. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/postsetup/__init__.py +0 -0
  66. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/profiles/advisory.py +0 -0
  67. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/profiles/mcp_n8n.py +0 -0
  68. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/profiles/standalone.py +0 -0
  69. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/services/__init__.py +0 -0
  70. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/services/loader.py +0 -0
  71. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/services/manifest.py +0 -0
  72. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/__init__.py +0 -0
  73. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/_default.md.j2 +0 -0
  74. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/device-connection.md.j2 +0 -0
  75. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/gateway-network-link.md.j2 +0 -0
  76. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/identity-provider.md.j2 +0 -0
  77. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/kafka-connector.md.j2 +0 -0
  78. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/mcp-module.md.j2 +0 -0
  79. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/mqtt-engine-connection.md.j2 +0 -0
  80. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/opc-ua-connection.md.j2 +0 -0
  81. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/post-setup/reverse-proxy.md.j2 +0 -0
  82. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/chariot/compose.yaml.j2 +0 -0
  83. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/chariot/manifest.yaml +0 -0
  84. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/chariot/seed/service/USAGE.md +0 -0
  85. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/emqx/compose.yaml.j2 +0 -0
  86. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/emqx/manifest.yaml +0 -0
  87. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/emqx/seed/service/USAGE.md +0 -0
  88. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/hivemq/compose.yaml.j2 +0 -0
  89. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/hivemq/manifest.yaml +0 -0
  90. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/hivemq/seed/service/USAGE.md +0 -0
  91. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/kafka/compose.yaml.j2 +0 -0
  92. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/kafka/manifest.yaml +0 -0
  93. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/kafka/seed/service/USAGE.md +0 -0
  94. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/keycloak/compose.yaml.j2 +0 -0
  95. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/keycloak/manifest.yaml +0 -0
  96. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/keycloak/seed/service/import/ignition-realm.json +0 -0
  97. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mariadb/compose.yaml.j2 +0 -0
  98. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mariadb/manifest.yaml +0 -0
  99. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mariadb/seed/service/initdb/00-create-extra-databases.sh +0 -0
  100. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/modbus-sim/compose.yaml.j2 +0 -0
  101. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/modbus-sim/manifest.yaml +0 -0
  102. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/modbus-sim/seed/service/USAGE.md +0 -0
  103. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mongo/compose.yaml.j2 +0 -0
  104. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mongo/manifest.yaml +0 -0
  105. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mongo/seed/service/initdb/01-demo-collection.js +0 -0
  106. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mysql/compose.yaml.j2 +0 -0
  107. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mysql/manifest.yaml +0 -0
  108. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/mysql/seed/service/initdb/00-create-extra-databases.sh +0 -0
  109. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/n8n/compose.yaml.j2 +0 -0
  110. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/n8n/manifest.yaml +0 -0
  111. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/n8n/seed/service/USAGE.md +0 -0
  112. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/opcua-sim/compose.yaml.j2 +0 -0
  113. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/opcua-sim/manifest.yaml +0 -0
  114. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/opcua-sim/seed/service/USAGE.md +0 -0
  115. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/compose.yaml.j2 +0 -0
  116. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/manifest.yaml +0 -0
  117. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/database-connection/db/config.json +0 -0
  118. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/database-connection/db/resource.json +0 -0
  119. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/secret-provider/internal-secret-provider/config.json +0 -0
  120. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/secret-provider/internal-secret-provider/resource.json +0 -0
  121. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/postgres/seed/service/initdb/00-create-extra-databases.sh +0 -0
  122. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/rabbitmq/compose.yaml.j2 +0 -0
  123. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/rabbitmq/manifest.yaml +0 -0
  124. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/services/rabbitmq/seed/service/enabled_plugins +0 -0
  125. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/standalone-postgres/docker-compose.yaml +0 -0
  126. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/standalone-postgres/services/ignition/config/resources/core/config-mode.json +0 -0
  127. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/standalone-postgres/services/ignition/config/resources/dev/config-mode.json +0 -0
  128. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/ignition_stack/templates/standalone-postgres/services/ignition/projects/.gitkeep +0 -0
  129. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/__init__.py +0 -0
  130. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/conftest.py +0 -0
  131. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/combos/network-split/docker-compose.yaml +0 -0
  132. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/combos/smoke-stack/docker-compose.yaml +0 -0
  133. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/profiles/hub-and-spoke/docker-compose.yaml +0 -0
  134. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/profiles/mcp-n8n/docker-compose.yaml +0 -0
  135. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/profiles/standalone/docker-compose.yaml +0 -0
  136. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/scaleout-skeleton/docker-compose.yaml +0 -0
  137. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/chariot/docker-compose.yaml +0 -0
  138. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/db-mariadb/docker-compose.yaml +0 -0
  139. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/db-mongo/docker-compose.yaml +0 -0
  140. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/db-mysql/docker-compose.yaml +0 -0
  141. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/db-postgres/docker-compose.yaml +0 -0
  142. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/emqx/docker-compose.yaml +0 -0
  143. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/hivemq/docker-compose.yaml +0 -0
  144. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/kafka/docker-compose.yaml +0 -0
  145. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/keycloak/docker-compose.yaml +0 -0
  146. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/modbus-sim/docker-compose.yaml +0 -0
  147. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/n8n/docker-compose.yaml +0 -0
  148. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/opcua-sim/docker-compose.yaml +0 -0
  149. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/services/rabbitmq/docker-compose.yaml +0 -0
  150. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/golden/standalone-postgres/docker-compose.yaml +0 -0
  151. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_completion.py +0 -0
  152. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_init_standalone.py +0 -0
  153. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_modules_catalog.py +0 -0
  154. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_modules_cli.py +0 -0
  155. {ignition_stack-0.1.1 → ignition_stack-0.3.0}/tests/test_postsetup.py +0 -0
@@ -30,8 +30,3 @@ Thumbs.db
30
30
  .env
31
31
  .env.local
32
32
  *.env
33
-
34
- # Phase-1 POC artifacts (transient container outputs)
35
- scripts/seeding-poc/runs/
36
- scripts/seeding-poc/.modl-cache/
37
- # Screenshots ARE committed - they're the matrix's evidence.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ignition-stack
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: CLI that generates ready-to-run Docker Compose stacks for Ignition 8.3 SCADA demos and SE engagements
5
5
  Author-email: Eric Knorr <etknorr@gmail.com>
6
6
  License: MIT
@@ -25,9 +25,6 @@ Requires-Dist: typer>=0.12
25
25
  Provides-Extra: dev
26
26
  Requires-Dist: pytest>=8.3; extra == 'dev'
27
27
  Requires-Dist: ruff>=0.7; extra == 'dev'
28
- Provides-Extra: poc
29
- Requires-Dist: httpx>=0.27; extra == 'poc'
30
- Requires-Dist: playwright>=1.49; extra == 'poc'
31
28
  Description-Content-Type: text/markdown
32
29
 
33
30
  # ignition-stack
@@ -0,0 +1,122 @@
1
+ # Catalog of the built-in IA modules that ship inside the Ignition gateway
2
+ # image. Used to translate a gateway's `disable_builtins` slugs into the
3
+ # GATEWAY_MODULES_ENABLED whitelist the engine emits (enabled = all built-ins
4
+ # minus the disabled ones, plus any third-party module identifiers).
5
+ #
6
+ # Why this file exists:
7
+ # GATEWAY_MODULES_ENABLED is a strict WHITELIST, not a blocklist - anything
8
+ # not listed is quarantined at boot (verified live on 8.3.6). So "disable
9
+ # Vision" can only be expressed as "enable every built-in except Vision".
10
+ # That inversion needs the COMPLETE built-in set; an incomplete list would
11
+ # silently quarantine the modules we forgot to enumerate. This file is that
12
+ # complete set, and tests/test_builtin_catalog.py (marked `smoke`) re-derives
13
+ # it from the live image so a stale list fails CI loudly instead of dropping
14
+ # modules silently.
15
+ #
16
+ # Provenance (how to regenerate when bumping the Ignition image):
17
+ # 1. Boot the pinned image with no whitelist so every built-in loads:
18
+ # docker run --rm -e ACCEPT_IGNITION_EULA=Y -e GATEWAY_ADMIN_PASSWORD=x \
19
+ # inductiveautomation/ignition:<tag>
20
+ # 2. Read the loaded set from the gateway log lines:
21
+ # Starting up module '<identifier>' ... module-name=<name>
22
+ # 3. Update `ignition_version` and the `modules` list below to match.
23
+ # The smoke guard test automates this comparison.
24
+ #
25
+ # `identifier` is the fully-qualified module id used verbatim in
26
+ # GATEWAY_MODULES_ENABLED. `slug` is the friendly kebab name a user puts in
27
+ # `disable_builtins`. `name` is the gateway's display name (for wizard labels).
28
+
29
+ version: 1
30
+
31
+ # The exact Ignition image tag this built-in set was captured from. The guard
32
+ # test only asserts a match when the running image reports this version.
33
+ ignition_version: "8.3.6"
34
+
35
+ modules:
36
+ - slug: alarm-notification
37
+ identifier: com.inductiveautomation.alarm-notification
38
+ name: Alarm Notification
39
+ - slug: allen-bradley-driver
40
+ identifier: com.inductiveautomation.opcua.drivers.ablegacy
41
+ name: Allen-Bradley Driver
42
+ - slug: bacnet-driver
43
+ identifier: com.inductiveautomation.opcua.drivers.bacnet
44
+ name: BACnet Driver
45
+ - slug: enterprise-administration
46
+ identifier: com.inductiveautomation.eam
47
+ name: Enterprise Administration
48
+ - slug: event-streams
49
+ identifier: com.inductiveautomation.eventstream
50
+ name: Event Streams
51
+ - slug: historian-core
52
+ identifier: com.inductiveautomation.historian
53
+ name: Historian Core
54
+ - slug: kafka-connector
55
+ identifier: com.inductiveautomation.connectors.kafka
56
+ name: Kafka Connector
57
+ - slug: legacy-dnp3-driver
58
+ identifier: com.inductiveautomation.opcua.drivers.dnp3
59
+ name: Legacy DNP3 Driver
60
+ - slug: logix-driver
61
+ identifier: com.inductiveautomation.opcua.drivers.logix
62
+ name: Logix Driver
63
+ - slug: mariadb-jdbc-driver
64
+ identifier: com.inductiveautomation.jdbc.mariadb
65
+ name: MariaDB JDBC Driver
66
+ - slug: micro800-driver
67
+ identifier: com.inductiveautomation.opcua.drivers.micro800
68
+ name: Micro800 Driver
69
+ - slug: mitsubishi-driver
70
+ identifier: com.inductiveautomation.opcua.drivers.mitsubishi
71
+ name: Mitsubishi Driver
72
+ - slug: modbus-driver
73
+ identifier: com.inductiveautomation.opcua.drivers.modbus
74
+ name: Modbus Driver
75
+ - slug: mssql-jdbc-driver
76
+ identifier: com.inductiveautomation.jdbc.mssql
77
+ name: MSSQL JDBC Driver
78
+ - slug: omron-driver
79
+ identifier: com.inductiveautomation.opcua.drivers.omron
80
+ name: Omron Driver
81
+ - slug: opc-ua
82
+ identifier: com.inductiveautomation.opcua
83
+ name: OPC-UA
84
+ - slug: perspective
85
+ identifier: com.inductiveautomation.perspective
86
+ name: Perspective
87
+ - slug: postgresql-jdbc-driver
88
+ identifier: com.inductiveautomation.jdbc.postgresql
89
+ name: PostgreSQL JDBC Driver
90
+ - slug: reporting
91
+ identifier: com.inductiveautomation.reporting
92
+ name: Reporting
93
+ - slug: sfc
94
+ identifier: com.inductiveautomation.sfc
95
+ name: SFC
96
+ - slug: siemens-drivers
97
+ identifier: com.inductiveautomation.opcua.drivers.siemens
98
+ name: Siemens Drivers
99
+ - slug: siemens-enhanced-driver
100
+ identifier: com.inductiveautomation.opcua.drivers.siemens-symbolic
101
+ name: Siemens Enhanced Driver
102
+ - slug: sms-notification
103
+ identifier: com.inductiveautomation.sms-notification
104
+ name: SMS Notification
105
+ - slug: sql-bridge
106
+ identifier: com.inductiveautomation.sqlbridge
107
+ name: SQL Bridge
108
+ - slug: sql-historian
109
+ identifier: com.inductiveautomation.historian.sql
110
+ name: SQL Historian
111
+ - slug: symbol-factory
112
+ identifier: com.inductiveautomation.symbol-factory
113
+ name: Symbol Factory
114
+ - slug: udp-and-tcp-drivers
115
+ identifier: com.inductiveautomation.opcua.drivers.tcpudp
116
+ name: UDP and TCP Drivers
117
+ - slug: vision
118
+ identifier: com.inductiveautomation.vision
119
+ name: Vision
120
+ - slug: webdev
121
+ identifier: com.inductiveautomation.webdev
122
+ name: WebDev
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -0,0 +1,171 @@
1
+ """Load and query the built-in IA module catalog (``builtin_modules.yaml``).
2
+
3
+ The third-party catalog (``modules.yaml`` + ``catalog/schema.py``) covers
4
+ modules the CLI *adds*. This module covers the modules that *already ship*
5
+ inside the gateway image, which the engine needs in order to translate a
6
+ gateway's ``disable_builtins`` slugs into a ``GATEWAY_MODULES_ENABLED``
7
+ whitelist.
8
+
9
+ The whitelist is strict: anything not listed is quarantined at boot. So
10
+ "disable Vision" is expressed as "enable every built-in except Vision",
11
+ which requires the *complete* built-in set - hence a pinned data file plus a
12
+ smoke guard test that re-derives it from the live image.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from functools import lru_cache
18
+ from importlib import resources
19
+ from pathlib import Path
20
+ from typing import Annotated
21
+
22
+ import yaml
23
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator
24
+
25
+
26
+ class BuiltinCatalogLoadError(Exception):
27
+ """Raised when builtin_modules.yaml cannot be read or fails validation."""
28
+
29
+
30
+ DEFAULT_BUILTIN_CATALOG_NAME = "builtin_modules.yaml"
31
+
32
+
33
+ class BuiltinModule(BaseModel):
34
+ """One built-in IA module that ships inside the gateway image."""
35
+
36
+ model_config = ConfigDict(extra="forbid", frozen=True)
37
+
38
+ slug: Annotated[
39
+ str,
40
+ Field(
41
+ min_length=1,
42
+ pattern=r"^[a-z0-9][a-z0-9-]*$",
43
+ description="Friendly kebab name a user puts in `disable_builtins`.",
44
+ ),
45
+ ]
46
+ identifier: Annotated[
47
+ str,
48
+ Field(
49
+ min_length=1,
50
+ pattern=r"^[a-z0-9.-]+$",
51
+ description="Fully-qualified module id, used verbatim in GATEWAY_MODULES_ENABLED.",
52
+ ),
53
+ ]
54
+ name: Annotated[str, Field(min_length=1, description="Gateway display name (wizard label).")]
55
+
56
+
57
+ class BuiltinCatalog(BaseModel):
58
+ """Top-level shape of builtin_modules.yaml."""
59
+
60
+ model_config = ConfigDict(extra="forbid", frozen=True)
61
+
62
+ version: Annotated[int, Field(ge=1)]
63
+ ignition_version: Annotated[
64
+ str,
65
+ Field(min_length=1, description="Image tag this built-in set was captured from."),
66
+ ]
67
+ modules: Annotated[list[BuiltinModule], Field(min_length=1)]
68
+
69
+ @field_validator("modules")
70
+ @classmethod
71
+ def _slugs_unique(cls, modules: list[BuiltinModule]) -> list[BuiltinModule]:
72
+ slugs = [m.slug for m in modules]
73
+ dupes = sorted({s for s in slugs if slugs.count(s) > 1})
74
+ if dupes:
75
+ raise ValueError(f"duplicate built-in slugs: {', '.join(dupes)}")
76
+ return modules
77
+
78
+ @property
79
+ def slugs(self) -> set[str]:
80
+ """Every known built-in slug."""
81
+ return {m.slug for m in self.modules}
82
+
83
+ def identifiers_excluding(self, disabled_slugs: list[str]) -> list[str]:
84
+ """FQ identifiers of every built-in whose slug is not in ``disabled_slugs``.
85
+
86
+ Order follows the catalog (already alphabetical by slug) so generated
87
+ whitelists are deterministic and golden-stable.
88
+ """
89
+ disabled = set(disabled_slugs)
90
+ return [m.identifier for m in self.modules if m.slug not in disabled]
91
+
92
+
93
+ def load_builtin_catalog(path: Path | None = None) -> BuiltinCatalog:
94
+ """Load and validate the built-in catalog.
95
+
96
+ With ``path=None`` the catalog shipped with the installed package is used;
97
+ a path overrides it (test fixtures). Mirrors ``catalog.loader.load_catalog``.
98
+ """
99
+ yaml_text = _read_yaml_text(path)
100
+ name = DEFAULT_BUILTIN_CATALOG_NAME
101
+ try:
102
+ raw = yaml.safe_load(yaml_text)
103
+ except yaml.YAMLError as exc:
104
+ raise BuiltinCatalogLoadError(f"{name} is not valid YAML: {exc}") from exc
105
+
106
+ if not isinstance(raw, dict):
107
+ raise BuiltinCatalogLoadError(f"{name} top-level must be a mapping.")
108
+
109
+ try:
110
+ return BuiltinCatalog.model_validate(raw)
111
+ except ValidationError as exc:
112
+ raise BuiltinCatalogLoadError(
113
+ f"{DEFAULT_BUILTIN_CATALOG_NAME} failed schema validation:\n{exc}"
114
+ ) from exc
115
+
116
+
117
+ @lru_cache(maxsize=1)
118
+ def default_builtin_catalog() -> BuiltinCatalog:
119
+ """The built-in catalog shipped with the package, loaded once and cached.
120
+
121
+ The data file is immutable package data, so both config validation and the
122
+ compose engine can share a single memoized read rather than re-parsing YAML
123
+ on every gateway.
124
+ """
125
+ return load_builtin_catalog()
126
+
127
+
128
+ def builtin_slugs() -> frozenset[str]:
129
+ """Slugs of the shipped built-in catalog, for cheap ``disable_builtins`` validation."""
130
+ return frozenset(default_builtin_catalog().slugs)
131
+
132
+
133
+ def validate_disable_slugs(slugs: list[str]) -> None:
134
+ """Raise ``ValueError`` if any slug is not a known built-in.
135
+
136
+ Shared by ``GatewayConfig`` field validation (construction-time) and
137
+ ``profiles.apply_disable_builtins`` (post-construction mutation, which
138
+ pydantic does not re-validate), so the wizard/CLI path is guarded too. A
139
+ typo would otherwise be a silent no-op - the slug just isn't disabled.
140
+ """
141
+ known = builtin_slugs()
142
+ unknown = [s for s in slugs if s not in known]
143
+ if unknown:
144
+ raise ValueError(
145
+ f"unknown built-in module slug(s): {', '.join(unknown)}. "
146
+ f"Valid slugs are: {', '.join(sorted(known))}"
147
+ )
148
+
149
+
150
+ def _read_yaml_text(path: Path | None) -> str:
151
+ if path is not None:
152
+ if not path.is_file():
153
+ raise BuiltinCatalogLoadError(f"Built-in catalog not found at {path}.")
154
+ return path.read_text(encoding="utf-8")
155
+
156
+ # Installed wheels: force-included as ignition_stack/builtin_modules.yaml.
157
+ # Editable dev installs: it lives at the repo root next to pyproject.toml.
158
+ try:
159
+ bundled = resources.files("ignition_stack").joinpath(DEFAULT_BUILTIN_CATALOG_NAME)
160
+ if bundled.is_file():
161
+ return bundled.read_text(encoding="utf-8")
162
+ except (FileNotFoundError, OSError, ModuleNotFoundError):
163
+ pass
164
+
165
+ repo_root = Path(__file__).resolve().parents[2]
166
+ dev_path = repo_root / DEFAULT_BUILTIN_CATALOG_NAME
167
+ if not dev_path.is_file():
168
+ raise BuiltinCatalogLoadError(
169
+ f"{DEFAULT_BUILTIN_CATALOG_NAME} not found in package data or at {dev_path}."
170
+ )
171
+ return dev_path.read_text(encoding="utf-8")