ignition-stack 0.4.0__tar.gz → 0.5.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.
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/.gitignore +3 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/PKG-INFO +1 -1
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/builtin_modules.yaml +38 -0
- ignition_stack-0.5.0/ignition_stack/__init__.py +1 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/catalog/builtins.py +46 -10
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/catalog/download.py +4 -14
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/catalog/schema.py +5 -18
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/cli.py +81 -37
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/commands/modules.py +1 -2
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/completion.py +16 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/engine.py +104 -45
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/templates/services/ignition.yaml.j2 +3 -3
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/writer.py +203 -48
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/config/__init__.py +4 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/config/io.py +2 -7
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/config/schema.py +242 -9
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/postsetup/generator.py +76 -5
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/__init__.py +2 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/base.py +116 -12
- ignition_stack-0.5.0/ignition_stack/profiles/carry.py +319 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/services/loader.py +1 -3
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/services/manifest.py +82 -2
- ignition_stack-0.5.0/ignition_stack/services/resolver.py +361 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/default-namespace/Sparkplug B/config.json +8 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/default-namespace/Sparkplug B/resource.json +17 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/general/config.json +21 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/general/resource.json +16 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/namespace-server-set/Sparkplug B-Default Set/config.json +4 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/namespace-server-set/Sparkplug B-Default Set/resource.json +17 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/server/Chariot SCADA/config.json.j2 +24 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/server/Chariot SCADA/resource.json +19 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/server-set/Default Set/config.json +4 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-engine/config/resources/core/com.cirruslink.mqtt.engine.gateway/server-set/Default Set/resource.json +17 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/general/config.json +4 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/general/resource.json +16 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/history-store/Default In-Memory Store/config.json +17 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/history-store/Default In-Memory Store/resource.json +18 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/server/Chariot SCADA/config.json.j2 +36 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/server/Chariot SCADA/resource.json +19 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/server-set/Default/config.json +5 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/server-set/Default/resource.json +17 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/transmitter/{{gateway}}/config.json.j2 +29 -0
- ignition_stack-0.5.0/ignition_stack/templates/iiot/gateway-resources-mqtt-transmission/config/resources/core/com.cirruslink.mqtt.transmission.gateway/transmitter/{{gateway}}/resource.json +14 -0
- ignition_stack-0.5.0/ignition_stack/templates/post-setup/identity-provider.md.j2 +25 -0
- ignition_stack-0.5.0/ignition_stack/templates/post-setup/mqtt-engine-connection.md.j2 +106 -0
- ignition_stack-0.5.0/ignition_stack/templates/services/chariot/compose.yaml.j2 +39 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/chariot/manifest.yaml +8 -0
- ignition_stack-0.5.0/ignition_stack/templates/services/chariot/seed/service/chariot-trial.sh +56 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/emqx/manifest.yaml +9 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/hivemq/manifest.yaml +8 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/keycloak/compose.yaml.j2 +2 -0
- ignition_stack-0.5.0/ignition_stack/templates/services/keycloak/manifest.yaml +32 -0
- ignition_stack-0.5.0/ignition_stack/templates/services/keycloak/seed/gateway-resources/config/resources/core/ignition/identity-provider/keycloak/config.json +57 -0
- ignition_stack-0.5.0/ignition_stack/templates/services/keycloak/seed/gateway-resources/config/resources/core/ignition/identity-provider/keycloak/resource.json +18 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/keycloak/seed/service/import/ignition-realm.json +23 -2
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mariadb/manifest.yaml +4 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mongo/manifest.yaml +4 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mysql/manifest.yaml +4 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/manifest.yaml +7 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/rabbitmq/manifest.yaml +8 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/wizard.py +220 -58
- ignition_stack-0.5.0/ignition_stack/wizard_composer.py +541 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/modules.yaml +7 -7
- ignition_stack-0.5.0/tests/golden/combos/hub-and-spoke-iiot-chariot/docker-compose.yaml +184 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/combos/network-split/docker-compose.yaml +2 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/profiles/hub-and-spoke/docker-compose.yaml +0 -6
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/scaleout-skeleton/docker-compose.yaml +0 -2
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/chariot/docker-compose.yaml +16 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/keycloak/docker-compose.yaml +2 -2
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_compose_engine.py +25 -23
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_declarative_io.py +147 -6
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_disable_builtins.py +65 -5
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_docs_cli_reference.py +1 -3
- ignition_stack-0.5.0/tests/test_iiot_overlay.py +384 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_init_standalone.py +3 -8
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_lifecycle.py +1 -3
- ignition_stack-0.5.0/tests/test_manifest_invariants.py +304 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_modules_catalog.py +1 -3
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_modules_cli.py +1 -3
- ignition_stack-0.5.0/tests/test_postsetup.py +156 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_profiles.py +250 -38
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_redundancy.py +5 -16
- ignition_stack-0.5.0/tests/test_registry.py +289 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_service_catalog.py +117 -35
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_service_catalog_smoke.py +1 -3
- ignition_stack-0.5.0/tests/test_switch_profile_registry.py +320 -0
- ignition_stack-0.5.0/tests/test_wizard_composer.py +448 -0
- ignition_stack-0.5.0/verification/iiot-spike/README.md +142 -0
- ignition_stack-0.4.0/ignition_stack/__init__.py +0 -1
- ignition_stack-0.4.0/ignition_stack/services/resolver.py +0 -186
- ignition_stack-0.4.0/ignition_stack/templates/post-setup/identity-provider.md.j2 +0 -13
- ignition_stack-0.4.0/ignition_stack/templates/post-setup/mqtt-engine-connection.md.j2 +0 -11
- ignition_stack-0.4.0/ignition_stack/templates/services/chariot/compose.yaml.j2 +0 -17
- ignition_stack-0.4.0/ignition_stack/templates/services/keycloak/manifest.yaml +0 -25
- ignition_stack-0.4.0/tests/test_postsetup.py +0 -84
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/LICENSE +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/README.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/catalog/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/catalog/loader.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/catalog/verify.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/commands/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/templates/footer.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/templates/header.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/compose/templates/services/bootstrap.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/lifecycle/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/lifecycle/cleanup.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/lifecycle/record.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/lifecycle/regenerate.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/postsetup/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/advisory.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/hub_and_spoke.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/mcp_n8n.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/scaleout.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/profiles/standalone.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/services/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/_default.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/device-connection.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/gateway-network-link.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/kafka-connector.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/mcp-module.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/opc-ua-connection.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/redundancy-pairing.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/post-setup/reverse-proxy.md.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/redundancy/redundancy.xml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/chariot/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/emqx/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/emqx/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/hivemq/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/hivemq/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/kafka/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/kafka/manifest.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/kafka/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mariadb/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mariadb/seed/service/initdb/00-create-extra-databases.sh +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/modbus-sim/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/modbus-sim/manifest.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/modbus-sim/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mongo/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mongo/seed/service/initdb/01-demo-collection.js +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mysql/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/mysql/seed/service/initdb/00-create-extra-databases.sh +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/n8n/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/n8n/manifest.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/n8n/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/opcua-sim/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/opcua-sim/manifest.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/opcua-sim/seed/service/USAGE.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/database-connection/db/config.json +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/database-connection/db/resource.json +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/secret-provider/internal-secret-provider/config.json +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/seed/gateway-resources/config/resources/core/ignition/secret-provider/internal-secret-provider/resource.json +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/postgres/seed/service/initdb/00-create-extra-databases.sh +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/rabbitmq/compose.yaml.j2 +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/services/rabbitmq/seed/service/enabled_plugins +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/standalone-postgres/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/standalone-postgres/scripts/docker-bootstrap.sh +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/standalone-postgres/services/ignition/config/resources/core/config-mode.json +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/standalone-postgres/services/ignition/config/resources/dev/config-mode.json +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/templates/standalone-postgres/services/ignition/projects/.gitkeep +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/ignition_stack/update_check.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/pyproject.toml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/__init__.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/conftest.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/combos/smoke-stack/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/profiles/mcp-n8n/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/profiles/scaleout/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/profiles/scaleout-redundant/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/profiles/standalone/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/db-mariadb/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/db-mongo/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/db-mysql/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/db-postgres/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/emqx/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/hivemq/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/kafka/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/modbus-sim/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/n8n/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/opcua-sim/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/services/rabbitmq/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/golden/standalone-postgres/docker-compose.yaml +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_builtin_catalog_smoke.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_completion.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/tests/test_update_check.py +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/verification/redundancy-spike/README.md +0 -0
- {ignition_stack-0.4.0 → ignition_stack-0.5.0}/verification/smoke/README.md +0 -0
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
# `identifier` is the fully-qualified module id used verbatim in
|
|
26
26
|
# GATEWAY_MODULES_ENABLED. `slug` is the friendly kebab name a user puts in
|
|
27
27
|
# `disable_builtins`. `name` is the gateway's display name (for wizard labels).
|
|
28
|
+
#
|
|
29
|
+
# `default_enabled` marks the curated "typical demo" set the interactive wizard
|
|
30
|
+
# pre-checks (opt-in selection). It does NOT change the engine math or the
|
|
31
|
+
# non-interactive profile path - profiles still enable every built-in. It only
|
|
32
|
+
# seeds the wizard's checkbox so the common path is a lean gateway. The JDBC
|
|
33
|
+
# drivers are all `default_enabled: false` on purpose: the wizard enables the
|
|
34
|
+
# one matching the chosen database (see `jdbc_driver_for`), not a static one.
|
|
35
|
+
# The smoke guard checks the module *set*, not this curation flag, so the
|
|
36
|
+
# default-set invariants live in tests/test_disable_builtins.py instead.
|
|
28
37
|
|
|
29
38
|
version: 1
|
|
30
39
|
|
|
@@ -36,87 +45,116 @@ modules:
|
|
|
36
45
|
- slug: alarm-notification
|
|
37
46
|
identifier: com.inductiveautomation.alarm-notification
|
|
38
47
|
name: Alarm Notification
|
|
48
|
+
default_enabled: true
|
|
39
49
|
- slug: allen-bradley-driver
|
|
40
50
|
identifier: com.inductiveautomation.opcua.drivers.ablegacy
|
|
41
51
|
name: Allen-Bradley Driver
|
|
52
|
+
default_enabled: false
|
|
42
53
|
- slug: bacnet-driver
|
|
43
54
|
identifier: com.inductiveautomation.opcua.drivers.bacnet
|
|
44
55
|
name: BACnet Driver
|
|
56
|
+
default_enabled: false
|
|
45
57
|
- slug: enterprise-administration
|
|
46
58
|
identifier: com.inductiveautomation.eam
|
|
47
59
|
name: Enterprise Administration
|
|
60
|
+
default_enabled: false
|
|
48
61
|
- slug: event-streams
|
|
49
62
|
identifier: com.inductiveautomation.eventstream
|
|
50
63
|
name: Event Streams
|
|
64
|
+
default_enabled: false
|
|
51
65
|
- slug: historian-core
|
|
52
66
|
identifier: com.inductiveautomation.historian
|
|
53
67
|
name: Historian Core
|
|
68
|
+
default_enabled: true
|
|
54
69
|
- slug: kafka-connector
|
|
55
70
|
identifier: com.inductiveautomation.connectors.kafka
|
|
56
71
|
name: Kafka Connector
|
|
72
|
+
default_enabled: false
|
|
57
73
|
- slug: legacy-dnp3-driver
|
|
58
74
|
identifier: com.inductiveautomation.opcua.drivers.dnp3
|
|
59
75
|
name: Legacy DNP3 Driver
|
|
76
|
+
default_enabled: false
|
|
60
77
|
- slug: logix-driver
|
|
61
78
|
identifier: com.inductiveautomation.opcua.drivers.logix
|
|
62
79
|
name: Logix Driver
|
|
80
|
+
default_enabled: false
|
|
63
81
|
- slug: mariadb-jdbc-driver
|
|
64
82
|
identifier: com.inductiveautomation.jdbc.mariadb
|
|
65
83
|
name: MariaDB JDBC Driver
|
|
84
|
+
default_enabled: false
|
|
66
85
|
- slug: micro800-driver
|
|
67
86
|
identifier: com.inductiveautomation.opcua.drivers.micro800
|
|
68
87
|
name: Micro800 Driver
|
|
88
|
+
default_enabled: false
|
|
69
89
|
- slug: mitsubishi-driver
|
|
70
90
|
identifier: com.inductiveautomation.opcua.drivers.mitsubishi
|
|
71
91
|
name: Mitsubishi Driver
|
|
92
|
+
default_enabled: false
|
|
72
93
|
- slug: modbus-driver
|
|
73
94
|
identifier: com.inductiveautomation.opcua.drivers.modbus
|
|
74
95
|
name: Modbus Driver
|
|
96
|
+
default_enabled: false
|
|
75
97
|
- slug: mssql-jdbc-driver
|
|
76
98
|
identifier: com.inductiveautomation.jdbc.mssql
|
|
77
99
|
name: MSSQL JDBC Driver
|
|
100
|
+
default_enabled: false
|
|
78
101
|
- slug: omron-driver
|
|
79
102
|
identifier: com.inductiveautomation.opcua.drivers.omron
|
|
80
103
|
name: Omron Driver
|
|
104
|
+
default_enabled: false
|
|
81
105
|
- slug: opc-ua
|
|
82
106
|
identifier: com.inductiveautomation.opcua
|
|
83
107
|
name: OPC-UA
|
|
108
|
+
default_enabled: true
|
|
84
109
|
- slug: perspective
|
|
85
110
|
identifier: com.inductiveautomation.perspective
|
|
86
111
|
name: Perspective
|
|
112
|
+
default_enabled: true
|
|
87
113
|
- slug: postgresql-jdbc-driver
|
|
88
114
|
identifier: com.inductiveautomation.jdbc.postgresql
|
|
89
115
|
name: PostgreSQL JDBC Driver
|
|
116
|
+
default_enabled: false
|
|
90
117
|
- slug: reporting
|
|
91
118
|
identifier: com.inductiveautomation.reporting
|
|
92
119
|
name: Reporting
|
|
120
|
+
default_enabled: true
|
|
93
121
|
- slug: sfc
|
|
94
122
|
identifier: com.inductiveautomation.sfc
|
|
95
123
|
name: SFC
|
|
124
|
+
default_enabled: false
|
|
96
125
|
- slug: siemens-drivers
|
|
97
126
|
identifier: com.inductiveautomation.opcua.drivers.siemens
|
|
98
127
|
name: Siemens Drivers
|
|
128
|
+
default_enabled: false
|
|
99
129
|
- slug: siemens-enhanced-driver
|
|
100
130
|
identifier: com.inductiveautomation.opcua.drivers.siemens-symbolic
|
|
101
131
|
name: Siemens Enhanced Driver
|
|
132
|
+
default_enabled: false
|
|
102
133
|
- slug: sms-notification
|
|
103
134
|
identifier: com.inductiveautomation.sms-notification
|
|
104
135
|
name: SMS Notification
|
|
136
|
+
default_enabled: false
|
|
105
137
|
- slug: sql-bridge
|
|
106
138
|
identifier: com.inductiveautomation.sqlbridge
|
|
107
139
|
name: SQL Bridge
|
|
140
|
+
default_enabled: true
|
|
108
141
|
- slug: sql-historian
|
|
109
142
|
identifier: com.inductiveautomation.historian.sql
|
|
110
143
|
name: SQL Historian
|
|
144
|
+
default_enabled: true
|
|
111
145
|
- slug: symbol-factory
|
|
112
146
|
identifier: com.inductiveautomation.symbol-factory
|
|
113
147
|
name: Symbol Factory
|
|
148
|
+
default_enabled: false
|
|
114
149
|
- slug: udp-and-tcp-drivers
|
|
115
150
|
identifier: com.inductiveautomation.opcua.drivers.tcpudp
|
|
116
151
|
name: UDP and TCP Drivers
|
|
152
|
+
default_enabled: false
|
|
117
153
|
- slug: vision
|
|
118
154
|
identifier: com.inductiveautomation.vision
|
|
119
155
|
name: Vision
|
|
156
|
+
default_enabled: false
|
|
120
157
|
- slug: webdev
|
|
121
158
|
identifier: com.inductiveautomation.webdev
|
|
122
159
|
name: WebDev
|
|
160
|
+
default_enabled: false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0"
|
|
@@ -52,6 +52,15 @@ class BuiltinModule(BaseModel):
|
|
|
52
52
|
),
|
|
53
53
|
]
|
|
54
54
|
name: Annotated[str, Field(min_length=1, description="Gateway display name (wizard label).")]
|
|
55
|
+
default_enabled: Annotated[
|
|
56
|
+
bool,
|
|
57
|
+
Field(
|
|
58
|
+
default=False,
|
|
59
|
+
description=(
|
|
60
|
+
"Whether the wizard pre-checks this module in its opt-in selection. Curation only - it does not change the engine math or the non-interactive profile path."
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
]
|
|
55
64
|
|
|
56
65
|
|
|
57
66
|
class BuiltinCatalog(BaseModel):
|
|
@@ -80,6 +89,17 @@ class BuiltinCatalog(BaseModel):
|
|
|
80
89
|
"""Every known built-in slug."""
|
|
81
90
|
return {m.slug for m in self.modules}
|
|
82
91
|
|
|
92
|
+
@property
|
|
93
|
+
def default_enabled_slugs(self) -> set[str]:
|
|
94
|
+
"""Slugs the wizard pre-checks in its opt-in module selection.
|
|
95
|
+
|
|
96
|
+
Curation only: this seeds the wizard checkbox and never feeds the
|
|
97
|
+
engine math or the non-interactive profile path. The matching JDBC
|
|
98
|
+
driver is added on top by :func:`jdbc_driver_for`, so JDBC drivers are
|
|
99
|
+
deliberately absent here (they are database-driven, not statically on).
|
|
100
|
+
"""
|
|
101
|
+
return {m.slug for m in self.modules if m.default_enabled}
|
|
102
|
+
|
|
83
103
|
def identifiers_excluding(self, disabled_slugs: list[str]) -> list[str]:
|
|
84
104
|
"""FQ identifiers of every built-in whose slug is not in ``disabled_slugs``.
|
|
85
105
|
|
|
@@ -109,9 +129,7 @@ def load_builtin_catalog(path: Path | None = None) -> BuiltinCatalog:
|
|
|
109
129
|
try:
|
|
110
130
|
return BuiltinCatalog.model_validate(raw)
|
|
111
131
|
except ValidationError as exc:
|
|
112
|
-
raise BuiltinCatalogLoadError(
|
|
113
|
-
f"{DEFAULT_BUILTIN_CATALOG_NAME} failed schema validation:\n{exc}"
|
|
114
|
-
) from exc
|
|
132
|
+
raise BuiltinCatalogLoadError(f"{DEFAULT_BUILTIN_CATALOG_NAME} failed schema validation:\n{exc}") from exc
|
|
115
133
|
|
|
116
134
|
|
|
117
135
|
@lru_cache(maxsize=1)
|
|
@@ -130,6 +148,29 @@ def builtin_slugs() -> frozenset[str]:
|
|
|
130
148
|
return frozenset(default_builtin_catalog().slugs)
|
|
131
149
|
|
|
132
150
|
|
|
151
|
+
# Which built-in JDBC driver the wizard enables for each database kind. The
|
|
152
|
+
# catalog ships no MySQL-specific driver - Ignition connects to MySQL with the
|
|
153
|
+
# wire-compatible MariaDB driver, so both map to it. Mongo (not a JDBC store)
|
|
154
|
+
# and an absent database have no entry and resolve to None.
|
|
155
|
+
_JDBC_DRIVER_FOR_DB: dict[str, str] = {
|
|
156
|
+
"postgres": "postgresql-jdbc-driver",
|
|
157
|
+
"mariadb": "mariadb-jdbc-driver",
|
|
158
|
+
"mysql": "mariadb-jdbc-driver",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def jdbc_driver_for(db_kind: str | None) -> str | None:
|
|
163
|
+
"""The built-in JDBC driver slug the wizard enables for ``db_kind``.
|
|
164
|
+
|
|
165
|
+
Data-driven so the wizard stays declarative: postgres/mariadb map to their
|
|
166
|
+
own driver, mysql reuses the MariaDB driver, and mongo / no-database get
|
|
167
|
+
none. Returns ``None`` when no driver applies.
|
|
168
|
+
"""
|
|
169
|
+
if db_kind is None:
|
|
170
|
+
return None
|
|
171
|
+
return _JDBC_DRIVER_FOR_DB.get(db_kind)
|
|
172
|
+
|
|
173
|
+
|
|
133
174
|
def validate_disable_slugs(slugs: list[str]) -> None:
|
|
134
175
|
"""Raise ``ValueError`` if any slug is not a known built-in.
|
|
135
176
|
|
|
@@ -141,10 +182,7 @@ def validate_disable_slugs(slugs: list[str]) -> None:
|
|
|
141
182
|
known = builtin_slugs()
|
|
142
183
|
unknown = [s for s in slugs if s not in known]
|
|
143
184
|
if unknown:
|
|
144
|
-
raise ValueError(
|
|
145
|
-
f"unknown built-in module slug(s): {', '.join(unknown)}. "
|
|
146
|
-
f"Valid slugs are: {', '.join(sorted(known))}"
|
|
147
|
-
)
|
|
185
|
+
raise ValueError(f"unknown built-in module slug(s): {', '.join(unknown)}. Valid slugs are: {', '.join(sorted(known))}")
|
|
148
186
|
|
|
149
187
|
|
|
150
188
|
def _read_yaml_text(path: Path | None) -> str:
|
|
@@ -165,7 +203,5 @@ def _read_yaml_text(path: Path | None) -> str:
|
|
|
165
203
|
repo_root = Path(__file__).resolve().parents[2]
|
|
166
204
|
dev_path = repo_root / DEFAULT_BUILTIN_CATALOG_NAME
|
|
167
205
|
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
|
-
)
|
|
206
|
+
raise BuiltinCatalogLoadError(f"{DEFAULT_BUILTIN_CATALOG_NAME} not found in package data or at {dev_path}.")
|
|
171
207
|
return dev_path.read_text(encoding="utf-8")
|
|
@@ -64,11 +64,7 @@ def download_entry(
|
|
|
64
64
|
if entry.requires_manual_download:
|
|
65
65
|
return _handle_manual(entry, target)
|
|
66
66
|
|
|
67
|
-
if (
|
|
68
|
-
target.exists()
|
|
69
|
-
and entry.sha256 != SHA256_UNPINNED
|
|
70
|
-
and sha256_of_file(target) == entry.sha256
|
|
71
|
-
):
|
|
67
|
+
if target.exists() and entry.sha256 != SHA256_UNPINNED and sha256_of_file(target) == entry.sha256:
|
|
72
68
|
return DownloadResult(
|
|
73
69
|
entry.name,
|
|
74
70
|
DownloadOutcome.SKIPPED_CACHED,
|
|
@@ -78,8 +74,7 @@ def download_entry(
|
|
|
78
74
|
|
|
79
75
|
if offline:
|
|
80
76
|
raise DownloadError(
|
|
81
|
-
f"{entry.name}: --offline set but artifact not in cache "
|
|
82
|
-
f"({target}). Pre-populate the cache or drop --offline.",
|
|
77
|
+
f"{entry.name}: --offline set but artifact not in cache " f"({target}). Pre-populate the cache or drop --offline.",
|
|
83
78
|
)
|
|
84
79
|
|
|
85
80
|
if entry.download_url is None:
|
|
@@ -94,8 +89,7 @@ def download_entry(
|
|
|
94
89
|
if actual != entry.sha256:
|
|
95
90
|
target.unlink(missing_ok=True)
|
|
96
91
|
raise DownloadError(
|
|
97
|
-
f"{entry.name}: sha256 mismatch after download "
|
|
98
|
-
f"(expected {entry.sha256}, got {actual}). Cached file removed.",
|
|
92
|
+
f"{entry.name}: sha256 mismatch after download " f"(expected {entry.sha256}, got {actual}). Cached file removed.",
|
|
99
93
|
)
|
|
100
94
|
|
|
101
95
|
return DownloadResult(
|
|
@@ -121,11 +115,7 @@ def _handle_manual(entry: CatalogEntry, target: Path) -> DownloadResult:
|
|
|
121
115
|
entry.name,
|
|
122
116
|
DownloadOutcome.SKIPPED_MANUAL,
|
|
123
117
|
None,
|
|
124
|
-
(
|
|
125
|
-
f"WARN: {entry.name} local_source_path missing ({source}). "
|
|
126
|
-
"Skipping: requires manual download. "
|
|
127
|
-
"See POST-SETUP.md for instructions."
|
|
128
|
-
),
|
|
118
|
+
(f"WARN: {entry.name} local_source_path missing ({source}). " "Skipping: requires manual download. " "See POST-SETUP.md for instructions."),
|
|
129
119
|
)
|
|
130
120
|
|
|
131
121
|
shutil.copy2(source, target)
|
|
@@ -65,35 +65,24 @@ class _EntryBase(BaseModel):
|
|
|
65
65
|
str,
|
|
66
66
|
Field(
|
|
67
67
|
pattern=rf"^([0-9a-f]{{64}}|{SHA256_UNPINNED})$",
|
|
68
|
-
description=(
|
|
69
|
-
f"Lowercase hex sha256 of the artifact, or '{SHA256_UNPINNED}' "
|
|
70
|
-
"while a maintainer is mid-bump (rejected by `modules validate`)."
|
|
71
|
-
),
|
|
68
|
+
description=(f"Lowercase hex sha256 of the artifact, or '{SHA256_UNPINNED}' " "while a maintainer is mid-bump (rejected by `modules validate`)."),
|
|
72
69
|
),
|
|
73
70
|
]
|
|
74
71
|
install_path: Annotated[
|
|
75
72
|
str,
|
|
76
73
|
Field(
|
|
77
74
|
min_length=1,
|
|
78
|
-
description=(
|
|
79
|
-
"Fully-qualified in-container destination path. The compose "
|
|
80
|
-
"layer mounts/copies the cached artifact here."
|
|
81
|
-
),
|
|
75
|
+
description=("Fully-qualified in-container destination path. The compose " "layer mounts/copies the cached artifact here."),
|
|
82
76
|
),
|
|
83
77
|
]
|
|
84
78
|
requires_license_env: str | None = Field(
|
|
85
79
|
default=None,
|
|
86
|
-
description=(
|
|
87
|
-
"Name of an env var the user must set with their license key. "
|
|
88
|
-
"None for community-usable modules and unlicensed drivers."
|
|
89
|
-
),
|
|
80
|
+
description=("Name of an env var the user must set with their license key. " "None for community-usable modules and unlicensed drivers."),
|
|
90
81
|
)
|
|
91
82
|
requires_manual_download: bool = Field(
|
|
92
83
|
default=False,
|
|
93
84
|
description=(
|
|
94
|
-
"True when the artifact has no public URL (e.g. EA-gated). "
|
|
95
|
-
"`modules download` skips these unless local_source_path is set "
|
|
96
|
-
"and points at an existing file."
|
|
85
|
+
"True when the artifact has no public URL (e.g. EA-gated). " "`modules download` skips these unless local_source_path is set " "and points at an existing file."
|
|
97
86
|
),
|
|
98
87
|
)
|
|
99
88
|
local_source_path: str | None = Field(
|
|
@@ -121,9 +110,7 @@ class ModuleEntry(_EntryBase):
|
|
|
121
110
|
min_length=1,
|
|
122
111
|
pattern=r"^[a-z0-9.]+$",
|
|
123
112
|
description=(
|
|
124
|
-
"Fully-qualified module identifier (e.g. "
|
|
125
|
-
"'com.cirruslink.mqtt.engine.gateway'). Used verbatim in "
|
|
126
|
-
"ACCEPT_MODULE_LICENSES and ACCEPT_MODULE_CERTS. NOT a path."
|
|
113
|
+
"Fully-qualified module identifier (e.g. " "'com.cirruslink.mqtt.engine.gateway'). Used verbatim in " "ACCEPT_MODULE_LICENSES and ACCEPT_MODULE_CERTS. NOT a path."
|
|
127
114
|
),
|
|
128
115
|
),
|
|
129
116
|
]
|
|
@@ -24,6 +24,7 @@ from ignition_stack.commands.modules import modules_app
|
|
|
24
24
|
from ignition_stack.completion import (
|
|
25
25
|
complete_disable_builtin,
|
|
26
26
|
complete_edge_role,
|
|
27
|
+
complete_iiot_broker,
|
|
27
28
|
complete_output_format,
|
|
28
29
|
complete_profile,
|
|
29
30
|
complete_redundant_role,
|
|
@@ -55,6 +56,13 @@ from ignition_stack.profiles import (
|
|
|
55
56
|
get_profile,
|
|
56
57
|
list_profiles,
|
|
57
58
|
)
|
|
59
|
+
from ignition_stack.profiles.carry import (
|
|
60
|
+
carry_registry,
|
|
61
|
+
database_carried_by_kind,
|
|
62
|
+
detect_iiot_broker,
|
|
63
|
+
is_default_representable,
|
|
64
|
+
)
|
|
65
|
+
from ignition_stack.services.loader import load_all_services
|
|
58
66
|
from ignition_stack.services.resolver import resolve
|
|
59
67
|
from ignition_stack.update_check import (
|
|
60
68
|
check_for_update,
|
|
@@ -111,8 +119,7 @@ def _notify_update_available() -> None:
|
|
|
111
119
|
return
|
|
112
120
|
current, latest = result
|
|
113
121
|
console.print(
|
|
114
|
-
f"[dim]update available[/dim] [yellow]{current}[/yellow] -> "
|
|
115
|
-
f"[green]{latest}[/green] · run: [cyan]{detect_upgrade_command()}[/cyan]",
|
|
122
|
+
f"[dim]update available[/dim] [yellow]{current}[/yellow] -> " f"[green]{latest}[/green] · run: [cyan]{detect_upgrade_command()}[/cyan]",
|
|
116
123
|
highlight=False,
|
|
117
124
|
)
|
|
118
125
|
|
|
@@ -153,18 +160,12 @@ def init(
|
|
|
153
160
|
network_split: bool | None = typer.Option(
|
|
154
161
|
None,
|
|
155
162
|
"--network-split/--no-network-split",
|
|
156
|
-
help=(
|
|
157
|
-
"Force the frontend/backend network split on or off. Default follows "
|
|
158
|
-
"the profile (scaleout splits, hub-and-spoke does not)."
|
|
159
|
-
),
|
|
163
|
+
help=("Force the frontend/backend network split on or off. Default follows " "the profile (scaleout splits, hub-and-spoke does not)."),
|
|
160
164
|
),
|
|
161
165
|
reverse_proxy: str | None = typer.Option(
|
|
162
166
|
None,
|
|
163
167
|
"--reverse-proxy",
|
|
164
|
-
help=(
|
|
165
|
-
"Scaffold a reverse proxy of the given kind ('traefik'). Lays down a "
|
|
166
|
-
"README + POST-SETUP entry at --proxy-path. Omit for plain host-port mapping."
|
|
167
|
-
),
|
|
168
|
+
help=("Scaffold a reverse proxy of the given kind ('traefik'). Lays down a " "README + POST-SETUP entry at --proxy-path. Omit for plain host-port mapping."),
|
|
168
169
|
autocompletion=complete_reverse_proxy,
|
|
169
170
|
),
|
|
170
171
|
proxy_path: str = typer.Option(
|
|
@@ -210,6 +211,26 @@ def init(
|
|
|
210
211
|
),
|
|
211
212
|
autocompletion=complete_disable_builtin,
|
|
212
213
|
),
|
|
214
|
+
iiot: bool = typer.Option(
|
|
215
|
+
False,
|
|
216
|
+
"--iiot/--no-iiot",
|
|
217
|
+
help=(
|
|
218
|
+
"Overlay an MQTT/Sparkplug IIoT pipeline: add a broker and wire the "
|
|
219
|
+
"Cirrus Link Transmission/Engine modules across the gateways by role "
|
|
220
|
+
"(spokes/frontends transmit, hub/backend run Engine; a single gateway "
|
|
221
|
+
"runs both). Defaults the broker to 'chariot'."
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
iiot_broker: str | None = typer.Option(
|
|
225
|
+
None,
|
|
226
|
+
"--iiot-broker",
|
|
227
|
+
help=(
|
|
228
|
+
"MQTT broker slug the IIoT overlay wires to (implies --iiot). Must be "
|
|
229
|
+
"a catalog 'mqtt-broker' kind (e.g. 'chariot', 'emqx', 'hivemq'). "
|
|
230
|
+
"Defaults to 'chariot' when --iiot is given without this flag."
|
|
231
|
+
),
|
|
232
|
+
autocompletion=complete_iiot_broker,
|
|
233
|
+
),
|
|
213
234
|
from_file: Path | None = typer.Option( # noqa: B008 - Typer pattern
|
|
214
235
|
None,
|
|
215
236
|
"--from-file",
|
|
@@ -279,6 +300,8 @@ def init(
|
|
|
279
300
|
proxy_path=proxy_path,
|
|
280
301
|
redundant=redundant,
|
|
281
302
|
disable_builtin=disable_builtin,
|
|
303
|
+
iiot=iiot,
|
|
304
|
+
iiot_broker=iiot_broker,
|
|
282
305
|
)
|
|
283
306
|
|
|
284
307
|
if dry_run:
|
|
@@ -300,19 +323,12 @@ def init(
|
|
|
300
323
|
console.print("Next steps:")
|
|
301
324
|
console.print(f" cd {target}")
|
|
302
325
|
console.print(" docker compose up -d")
|
|
303
|
-
console.print(
|
|
304
|
-
f" open http://localhost:{config.gateways[0].http_port} (admin / {config.admin_password})"
|
|
305
|
-
)
|
|
326
|
+
console.print(f" open http://localhost:{config.gateways[0].http_port} (admin / {config.admin_password})")
|
|
306
327
|
console.print()
|
|
307
|
-
console.print(
|
|
308
|
-
f" config recorded in {LIFECYCLE_DIR}/ - run `ignition-stack reset` to "
|
|
309
|
-
"regenerate or `switch-profile <name>` to reshape this stack."
|
|
310
|
-
)
|
|
328
|
+
console.print(f" config recorded in {LIFECYCLE_DIR}/ - run `ignition-stack reset` to " "regenerate or `switch-profile <name>` to reshape this stack.")
|
|
311
329
|
|
|
312
330
|
|
|
313
|
-
def _validate_init_flags(
|
|
314
|
-
*, profile: str | None, from_file: Path | None, dry_run: bool, fmt: str | None
|
|
315
|
-
) -> None:
|
|
331
|
+
def _validate_init_flags(*, profile: str | None, from_file: Path | None, dry_run: bool, fmt: str | None) -> None:
|
|
316
332
|
"""Enforce the mutual-exclusion + flag-applicability rules, or exit code 2.
|
|
317
333
|
|
|
318
334
|
``--from-file`` already fully specifies the topology, so combining it with
|
|
@@ -322,18 +338,13 @@ def _validate_init_flags(
|
|
|
322
338
|
supported formats here so a bad ``--output-format`` fails before any build.
|
|
323
339
|
"""
|
|
324
340
|
if from_file is not None and profile is not None:
|
|
325
|
-
console.print(
|
|
326
|
-
"[red]error[/red]: --from-file cannot be combined with --profile; a "
|
|
327
|
-
"config file already specifies the full topology."
|
|
328
|
-
)
|
|
341
|
+
console.print("[red]error[/red]: --from-file cannot be combined with --profile; a " "config file already specifies the full topology.")
|
|
329
342
|
raise typer.Exit(code=2)
|
|
330
343
|
if fmt is not None and not dry_run:
|
|
331
344
|
console.print("[red]error[/red]: --output-format only applies with --dry-run.")
|
|
332
345
|
raise typer.Exit(code=2)
|
|
333
346
|
if fmt is not None and fmt not in {"yaml", "json"}:
|
|
334
|
-
console.print(
|
|
335
|
-
f"[red]error[/red]: unsupported --output-format '{fmt}'; use 'yaml' or 'json'."
|
|
336
|
-
)
|
|
347
|
+
console.print(f"[red]error[/red]: unsupported --output-format '{fmt}'; use 'yaml' or 'json'.")
|
|
337
348
|
raise typer.Exit(code=2)
|
|
338
349
|
|
|
339
350
|
|
|
@@ -368,6 +379,8 @@ def _build_from_profile(
|
|
|
368
379
|
proxy_path: str,
|
|
369
380
|
redundant: str | None,
|
|
370
381
|
disable_builtin: list[str],
|
|
382
|
+
iiot: bool,
|
|
383
|
+
iiot_broker: str | None,
|
|
371
384
|
) -> ProjectConfig:
|
|
372
385
|
"""Materialize a config from the named profile + CLI flags, or exit cleanly."""
|
|
373
386
|
try:
|
|
@@ -377,6 +390,9 @@ def _build_from_profile(
|
|
|
377
390
|
raise typer.Exit(code=2) from exc
|
|
378
391
|
|
|
379
392
|
proxy = ReverseProxyConfig(kind=reverse_proxy, path=proxy_path) if reverse_proxy else None
|
|
393
|
+
# --iiot-broker implies --iiot, so naming a broker is enough to turn the
|
|
394
|
+
# overlay on; build_profile defaults the slug to 'chariot' when iiot is on
|
|
395
|
+
# without an explicit broker.
|
|
380
396
|
options = ProfileOptions(
|
|
381
397
|
spokes=spokes,
|
|
382
398
|
frontends=frontends,
|
|
@@ -386,6 +402,8 @@ def _build_from_profile(
|
|
|
386
402
|
reverse_proxy=proxy,
|
|
387
403
|
redundant_role=redundant,
|
|
388
404
|
disable_builtins=tuple(disable_builtin),
|
|
405
|
+
iiot=iiot or iiot_broker is not None,
|
|
406
|
+
iiot_broker=iiot_broker,
|
|
389
407
|
)
|
|
390
408
|
try:
|
|
391
409
|
config = build_profile(profile, name, options)
|
|
@@ -474,9 +492,7 @@ def switch_profile(
|
|
|
474
492
|
# 'gateway'), which the target profile may not have. Building its base
|
|
475
493
|
# topology lets us check before build_profile's mark_redundant would reject
|
|
476
494
|
# it - drop the intent with an advisory rather than failing the reshape.
|
|
477
|
-
if options.redundant_role is not None and not can_host_redundant_role(
|
|
478
|
-
get_profile(profile).build(current.name, options), options.redundant_role
|
|
479
|
-
):
|
|
495
|
+
if options.redundant_role is not None and not can_host_redundant_role(get_profile(profile).build(current.name, options), options.redundant_role):
|
|
480
496
|
console.print(
|
|
481
497
|
f"[yellow]note[/yellow]: redundancy on '{options.redundant_role}' was not "
|
|
482
498
|
f"carried to {profile} (no matching gateway); re-apply with --redundant "
|
|
@@ -492,6 +508,15 @@ def switch_profile(
|
|
|
492
508
|
console.print(f"[red]error[/red]: {exc}")
|
|
493
509
|
raise typer.Exit(code=2) from exc
|
|
494
510
|
|
|
511
|
+
# Re-graft the richer registry shapes ProfileOptions can't express (custom
|
|
512
|
+
# ids, per-instance overrides, partial attachment sets, a second database),
|
|
513
|
+
# re-mapping their attachments by role and dropping any that the new topology
|
|
514
|
+
# can't host - each with a printed advisory. Resolve first so the profile's
|
|
515
|
+
# legacy database is already lowered into per-gateway attachments; the carry's
|
|
516
|
+
# one-database-per-gateway guard then sees them and won't over-attach. The
|
|
517
|
+
# carry's output is resolved again by regenerate (resolve is idempotent).
|
|
518
|
+
new_config = carry_registry(resolve(new_config), current, console)
|
|
519
|
+
|
|
495
520
|
files = regenerate(project_dir, new_config)
|
|
496
521
|
console.print(f"[green]switched[/green] {current.profile or 'custom'} -> {profile}")
|
|
497
522
|
console.print(f" {len(files)} file(s) regenerated")
|
|
@@ -515,11 +540,7 @@ def _options_from_config(config: ProjectConfig) -> ProfileOptions:
|
|
|
515
540
|
# by the resolver), so recover the role/name of whichever gateway is the
|
|
516
541
|
# master and let the new profile re-expand the pair.
|
|
517
542
|
redundant_role = next(
|
|
518
|
-
(
|
|
519
|
-
gw.role or gw.name
|
|
520
|
-
for gw in config.gateways
|
|
521
|
-
if gw.redundancy is not None and gw.redundancy.mode == "master"
|
|
522
|
-
),
|
|
543
|
+
(gw.role or gw.name for gw in config.gateways if gw.redundancy is not None and gw.redundancy.mode == "master"),
|
|
523
544
|
None,
|
|
524
545
|
)
|
|
525
546
|
# Disabled built-ins are applied stack-wide, so carry over the slugs disabled
|
|
@@ -528,16 +549,39 @@ def _options_from_config(config: ProjectConfig) -> ProfileOptions:
|
|
|
528
549
|
# one node. The target profile re-applies it uniformly.
|
|
529
550
|
disabled_sets = [set(gw.disable_builtins) for gw in config.gateways]
|
|
530
551
|
disable_builtins = tuple(sorted(set.intersection(*disabled_sets))) if disabled_sets else ()
|
|
552
|
+
# A recorded config is resolved: its database + services live in the
|
|
553
|
+
# registry, not the legacy fields. Recover the profile inputs from the
|
|
554
|
+
# registry. The primary database rides database_kind (see below); the
|
|
555
|
+
# non-database instances that are default-representable ride `services`.
|
|
556
|
+
# IIoT intent is recovered from the attachment roles: a stack with any
|
|
557
|
+
# mqtt-transmission/mqtt-engine attachment was built with apply_iiot, so set
|
|
558
|
+
# iiot=True and the broker slug. build_profile re-runs the overlay in the new
|
|
559
|
+
# topology, re-mapping Transmission/Engine onto the new roles naturally; the
|
|
560
|
+
# broker instance is therefore excluded from `services` below to avoid a
|
|
561
|
+
# double-add. Anything richer than `services` can express (custom ids,
|
|
562
|
+
# per-instance overrides, partial/role-specific attachments, a second
|
|
563
|
+
# database) is carried after build_profile by carry_registry.
|
|
564
|
+
iiot_broker = detect_iiot_broker(config)
|
|
565
|
+
catalog = load_all_services()
|
|
566
|
+
representable = tuple(inst.service for inst in config.non_database_instances() if inst.service != iiot_broker and is_default_representable(inst, config, catalog))
|
|
567
|
+
# The primary database rides database_kind only when it has the clean
|
|
568
|
+
# canonical shape (id "db", default image/credentials, consumer on every
|
|
569
|
+
# non-Edge gateway). A custom primary database - or any second database - is
|
|
570
|
+
# left for carry_registry to re-graft, so database_kind stays None and the
|
|
571
|
+
# profile does not also lay down a colliding default DB.
|
|
572
|
+
carried_db = database_carried_by_kind(config, catalog)
|
|
531
573
|
return ProfileOptions(
|
|
532
574
|
spokes=spoke_count or 3,
|
|
533
575
|
frontends=frontend_count or 1,
|
|
534
576
|
edge_role=edge_roles[0] if edge_roles else "none",
|
|
535
577
|
network_split=config.network_split,
|
|
536
578
|
reverse_proxy=config.reverse_proxy,
|
|
537
|
-
database_kind=
|
|
538
|
-
services=
|
|
579
|
+
database_kind=carried_db.service if carried_db is not None else None,
|
|
580
|
+
services=representable,
|
|
539
581
|
redundant_role=redundant_role,
|
|
540
582
|
disable_builtins=disable_builtins,
|
|
583
|
+
iiot=iiot_broker is not None,
|
|
584
|
+
iiot_broker=iiot_broker,
|
|
541
585
|
)
|
|
542
586
|
|
|
543
587
|
|
|
@@ -100,8 +100,7 @@ def validate(
|
|
|
100
100
|
raise typer.Exit(code=1)
|
|
101
101
|
|
|
102
102
|
console.print(
|
|
103
|
-
f"[green]OK[/green]: {len(catalog.entries)} entries valid"
|
|
104
|
-
+ (" (schema only)" if skip_network else " (schema + reachability)"),
|
|
103
|
+
f"[green]OK[/green]: {len(catalog.entries)} entries valid" + (" (schema only)" if skip_network else " (schema + reachability)"),
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
|
|
@@ -44,6 +44,22 @@ def complete_reverse_proxy(incomplete: str) -> list[str]:
|
|
|
44
44
|
return [kind for kind in REVERSE_PROXY_VALUES if kind.startswith(incomplete)]
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def complete_iiot_broker(incomplete: str) -> list[tuple[str, str]]:
|
|
48
|
+
"""MQTT broker slugs (with summary) the IIoT overlay can wire to.
|
|
49
|
+
|
|
50
|
+
Reads the bundled service catalog and offers only ``mqtt-broker`` kinds, the
|
|
51
|
+
slugs ``--iiot-broker`` accepts. Degrades to no suggestions on any error so a
|
|
52
|
+
TAB never breaks the shell line.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
from ignition_stack.services.loader import load_all_services
|
|
56
|
+
|
|
57
|
+
catalog = load_all_services()
|
|
58
|
+
except Exception:
|
|
59
|
+
return []
|
|
60
|
+
return [(slug, m.summary) for slug, m in sorted(catalog.items()) if m.kind == "mqtt-broker" and slug.startswith(incomplete)]
|
|
61
|
+
|
|
62
|
+
|
|
47
63
|
# Roles `init --redundant` can pair. Only the singleton workhorse roles are
|
|
48
64
|
# eligible (a scaleout 'backend', a hub-and-spoke 'hub', a standalone
|
|
49
65
|
# 'gateway'); replicated 'frontend'/'spoke' tiers are rejected by the profile
|