redeploy 0.2.67__tar.gz → 0.2.69__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 (252) hide show
  1. {redeploy-0.2.67 → redeploy-0.2.69}/PKG-INFO +6 -6
  2. {redeploy-0.2.67 → redeploy-0.2.69}/README.md +5 -5
  3. {redeploy-0.2.67 → redeploy-0.2.69}/pyproject.toml +1 -1
  4. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/blueprint.py +6 -0
  6. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/prompt_cmd.py +81 -5
  7. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/steps/__init__.py +23 -2
  8. redeploy-0.2.69/redeploy/steps/docker.py +40 -0
  9. redeploy-0.2.69/redeploy/steps/generic.py +87 -0
  10. redeploy-0.2.69/redeploy/steps/hardware.py +22 -0
  11. redeploy-0.2.69/redeploy/steps/k3s.py +58 -0
  12. redeploy-0.2.69/redeploy/steps/podman.py +41 -0
  13. redeploy-0.2.69/redeploy/steps/process.py +32 -0
  14. redeploy-0.2.69/redeploy/steps/scm.py +22 -0
  15. redeploy-0.2.69/redeploy/steps/transfer.py +22 -0
  16. redeploy-0.2.69/redeploy/tests/test_steps_registry.py +109 -0
  17. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/PKG-INFO +6 -6
  18. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/SOURCES.txt +9 -1
  19. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_prompt_llm.py +50 -0
  20. redeploy-0.2.67/redeploy/steps/builtins.py +0 -275
  21. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/__init__.py +0 -0
  22. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/checkers/__init__.py +0 -0
  23. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/preflight_schema.py +0 -0
  24. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/spec_analyzer.py +0 -0
  25. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/__init__.py +0 -0
  26. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/exceptions.py +0 -0
  27. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/executor.py +0 -0
  28. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/handlers.py +0 -0
  29. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/progress.py +0 -0
  30. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/rollback.py +0 -0
  31. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/state.py +0 -0
  32. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/state_apply.py +0 -0
  33. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/utils/__init__.py +0 -0
  34. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/utils/run_container_build.py +0 -0
  35. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/audit.py +0 -0
  36. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/__init__.py +0 -0
  37. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/extractor.py +0 -0
  38. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/generators/__init__.py +0 -0
  39. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/generators/docker_compose.py +0 -0
  40. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/generators/migration.py +0 -0
  41. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/__init__.py +0 -0
  42. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/compose.py +0 -0
  43. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/hardware.py +0 -0
  44. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/infra.py +0 -0
  45. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/migration.py +0 -0
  46. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/__init__.py +0 -0
  47. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/__init__.py +0 -0
  48. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/audit.py +0 -0
  49. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/bump_fix.py +0 -0
  50. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/detect.py +0 -0
  51. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/device_map.py +0 -0
  52. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/device_map_renderers.py +0 -0
  53. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/devices.py +0 -0
  54. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/diagnose.py +0 -0
  55. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/diff.py +0 -0
  56. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/exec_.py +0 -0
  57. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/export.py +0 -0
  58. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/hardware.py +0 -0
  59. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/import_.py +0 -0
  60. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/init.py +0 -0
  61. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/inspect.py +0 -0
  62. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/lint.py +0 -0
  63. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/mcp_cmd.py +0 -0
  64. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/patterns.py +0 -0
  65. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/plan_apply.py +0 -0
  66. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/plugin.py +0 -0
  67. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/probe.py +0 -0
  68. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/push.py +0 -0
  69. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/state.py +0 -0
  70. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/status.py +0 -0
  71. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/target.py +0 -0
  72. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/__init__.py +0 -0
  73. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/commands.py +0 -0
  74. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/helpers.py +0 -0
  75. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/monorepo.py +0 -0
  76. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/release.py +0 -0
  77. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/scanner.py +0 -0
  78. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/utils/__init__.py +0 -0
  79. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/utils/changelog_config.py +0 -0
  80. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/utils/git_config.py +0 -0
  81. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/workflow.py +0 -0
  82. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/core.py +0 -0
  83. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/display.py +0 -0
  84. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/query.py +0 -0
  85. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli.py +0 -0
  86. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/config_apply/__init__.py +0 -0
  87. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/config_apply/applier.py +0 -0
  88. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/config_apply/loader.py +0 -0
  89. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/data_sync.py +0 -0
  90. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/__init__.py +0 -0
  91. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/builtin/__init__.py +0 -0
  92. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/detector.py +0 -0
  93. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/hardware.py +0 -0
  94. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/hardware_rules.py +0 -0
  95. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/probes.py +0 -0
  96. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/remote.py +0 -0
  97. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/templates.py +0 -0
  98. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/workflow.py +0 -0
  99. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/discovery.py +0 -0
  100. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl/__init__.py +0 -0
  101. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl/loader.py +0 -0
  102. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl/parser.py +0 -0
  103. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/__init__.py +0 -0
  104. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/context.py +0 -0
  105. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/decorators.py +0 -0
  106. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/docker_steps.py +0 -0
  107. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/exceptions.py +0 -0
  108. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/runner.py +0 -0
  109. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/steps.py +0 -0
  110. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/fleet.py +0 -0
  111. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/__init__.py +0 -0
  112. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/config_txt.py +0 -0
  113. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/__init__.py +0 -0
  114. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/hyperpixel.py +0 -0
  115. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/official.py +0 -0
  116. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/waveshare.py +0 -0
  117. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/fixes.py +0 -0
  118. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/__init__.py +0 -0
  119. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/autostart.py +0 -0
  120. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/browsers.py +0 -0
  121. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/compositors.py +0 -0
  122. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/output_profiles.py +0 -0
  123. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/panels.py +0 -0
  124. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/raspi_config.py +0 -0
  125. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/__init__.py +0 -0
  126. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/decider.py +0 -0
  127. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/hint_provider.py +0 -0
  128. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/log_writer.py +0 -0
  129. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/loop_detector.py +0 -0
  130. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/runner.py +0 -0
  131. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal.py +0 -0
  132. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/__init__.py +0 -0
  133. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/base.py +0 -0
  134. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/config_hints.py +0 -0
  135. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/docker_compose.py +0 -0
  136. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/parsers/__init__.py +0 -0
  137. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/parsers/compose.py +0 -0
  138. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/registry.py +0 -0
  139. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/integrations/__init__.py +0 -0
  140. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/integrations/op3_bridge.py +0 -0
  141. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/__init__.py +0 -0
  142. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/compiler.py +0 -0
  143. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/models.py +0 -0
  144. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/parser.py +0 -0
  145. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/mcp_server.py +0 -0
  146. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/__init__.py +0 -0
  147. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/blueprint.py +0 -0
  148. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/devices.py +0 -0
  149. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/enums.py +0 -0
  150. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/hardware.py +0 -0
  151. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/infra.py +0 -0
  152. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/manifest.py +0 -0
  153. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/persisted.py +0 -0
  154. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/pipeline.py +0 -0
  155. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/plan.py +0 -0
  156. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/spec.py +0 -0
  157. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/observe.py +0 -0
  158. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/parse.py +0 -0
  159. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/patterns.py +0 -0
  160. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plan/__init__.py +0 -0
  161. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plan/planner.py +0 -0
  162. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/__init__.py +0 -0
  163. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/__init__.py +0 -0
  164. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/browser_reload.py +0 -0
  165. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
  166. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/notify.py +0 -0
  167. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/process_control.py +0 -0
  168. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  169. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/schema.py +0 -0
  170. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/spec_loader.py +0 -0
  171. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/ssh.py +0 -0
  172. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/steps/kiosk.py +0 -0
  173. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/__init__.py +0 -0
  174. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_analyze.py +0 -0
  175. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_audit.py +0 -0
  176. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_cli.py +0 -0
  177. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_data_sync.py +0 -0
  178. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_discovery.py +0 -0
  179. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_dsl.py +0 -0
  180. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_examples.py +0 -0
  181. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_executor.py +0 -0
  182. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_fleet.py +0 -0
  183. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_git_integration.py +0 -0
  184. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_heal.py +0 -0
  185. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_heal_decider.py +0 -0
  186. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_hooks.py +0 -0
  187. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_iac.py +0 -0
  188. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_iac_compose_contract.py +0 -0
  189. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
  190. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_manifest.py +0 -0
  191. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_markpact_compiler.py +0 -0
  192. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_markpact_parser.py +0 -0
  193. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_models.py +0 -0
  194. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_observe.py +0 -0
  195. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_parse.py +0 -0
  196. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_patterns.py +0 -0
  197. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_planner_edge.py +0 -0
  198. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_plugins.py +0 -0
  199. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_probes.py +0 -0
  200. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_public_api.py +0 -0
  201. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_resume.py +0 -0
  202. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_spec_loader.py +0 -0
  203. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_ssh.py +0 -0
  204. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_templates.py +0 -0
  205. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_verify.py +0 -0
  206. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version.py +0 -0
  207. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_changelog.py +0 -0
  208. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_cli.py +0 -0
  209. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_manifest.py +0 -0
  210. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_sources.py +0 -0
  211. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_workflow.py +0 -0
  212. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/verify.py +0 -0
  213. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/__init__.py +0 -0
  214. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/bump.py +0 -0
  215. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/changelog.py +0 -0
  216. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/commits.py +0 -0
  217. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/diff.py +0 -0
  218. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/git_integration.py +0 -0
  219. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/git_transaction.py +0 -0
  220. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/manifest.py +0 -0
  221. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/__init__.py +0 -0
  222. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/base.py +0 -0
  223. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/json_.py +0 -0
  224. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/plain.py +0 -0
  225. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/regex.py +0 -0
  226. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/toml_.py +0 -0
  227. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/yaml_.py +0 -0
  228. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/transaction.py +0 -0
  229. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version.py +0 -0
  230. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/dependency_links.txt +0 -0
  231. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/entry_points.txt +0 -0
  232. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/requires.txt +0 -0
  233. {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/top_level.txt +0 -0
  234. {redeploy-0.2.67 → redeploy-0.2.69}/setup.cfg +0 -0
  235. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_blueprint_sources.py +0 -0
  236. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_blueprint_sources_compose.py +0 -0
  237. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_cli_query.py +0 -0
  238. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_example_argocd_flux_plugin.py +0 -0
  239. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_example_gitops_ci_plugin.py +0 -0
  240. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_example_helm_kustomize_plugin.py +0 -0
  241. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_hardware_config.py +0 -0
  242. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_hardware_kiosk.py +0 -0
  243. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_hardware_rules.py +0 -0
  244. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_heal_decider.py +0 -0
  245. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_heal_loop_detector.py +0 -0
  246. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_iac.py +0 -0
  247. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_iac_config_hints.py +0 -0
  248. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_iac_plugin_loading.py +0 -0
  249. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_import_plugin_template.py +0 -0
  250. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_parse.py +0 -0
  251. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_redeploy.py +0 -0
  252. {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_steps_kiosk.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.67
3
+ Version: 0.2.69
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -32,17 +32,17 @@ Requires-Dist: mcp>=1.0; extra == "mcp"
32
32
 
33
33
  ## AI Cost Tracking
34
34
 
35
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.67-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
36
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-34.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
35
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.69-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
36
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-36.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
37
 
38
- - 🤖 **LLM usage:** $7.5000 (88 commits)
39
- - 👤 **Human dev:** ~$3416 (34.2h @ $100/h, 30min dedup)
38
+ - 🤖 **LLM usage:** $7.5000 (90 commits)
39
+ - 👤 **Human dev:** ~$3606 (36.1h @ $100/h, 30min dedup)
40
40
 
41
41
  Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
42
42
 
43
43
  ---
44
44
 
45
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.67-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
45
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.69-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
46
46
 
47
47
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
48
48
 
@@ -3,17 +3,17 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.67-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-34.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.69-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-36.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (88 commits)
10
- - 👤 **Human dev:** ~$3416 (34.2h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (90 commits)
10
+ - 👤 **Human dev:** ~$3606 (36.1h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.67-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.69-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
19
19
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redeploy"
7
- version = "0.2.67"
7
+ version = "0.2.69"
8
8
  description = "Infrastructure migration toolkit: detect → plan → apply"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,7 +7,7 @@ Public API (stable from 0.2.0, semver guaranteed)::
7
7
 
8
8
  Everything not listed in ``__all__`` is internal and may change without notice.
9
9
  """
10
- __version__ = "0.2.67"
10
+ __version__ = "0.2.69"
11
11
 
12
12
  # ── Core models ───────────────────────────────────────────────────────────────
13
13
  from .models import ( # noqa: F401
@@ -212,6 +212,12 @@ def show(blueprint_file, fmt, apply_config, query_expr):
212
212
  from ...models import DeviceBlueprint
213
213
 
214
214
  bp = DeviceBlueprint.load(Path(blueprint_file))
215
+
216
+ # --query: extract specific values with JMESPath
217
+ if query_expr is not None:
218
+ _execute_query_blueprint(bp, query_expr, fmt)
219
+ return
220
+
215
221
  if fmt == "json":
216
222
  import json as _json
217
223
  click.echo(_json.dumps(bp.model_dump(mode="json"), indent=2))
@@ -121,6 +121,8 @@ def _call_llm(schema: dict, user_prompt: str) -> str:
121
121
 
122
122
  def _parse_llm_response(raw: str) -> dict:
123
123
  """Parse JSON from LLM response, strip accidental fences and escape control characters."""
124
+ import re
125
+
124
126
  # Strip ```json ... ``` if present
125
127
  clean = raw.strip()
126
128
  if clean.startswith("```"):
@@ -129,18 +131,92 @@ def _parse_llm_response(raw: str) -> dict:
129
131
  line for line in lines
130
132
  if not line.strip().startswith("```")
131
133
  )
134
+
135
+ # Extract innermost balanced {...} if extra text surrounds it
136
+ def _extract_json(text: str) -> str:
137
+ start = text.find("{")
138
+ if start == -1:
139
+ return text
140
+ depth = 0
141
+ for i, ch in enumerate(text[start:], start):
142
+ if ch == "{":
143
+ depth += 1
144
+ elif ch == "}":
145
+ depth -= 1
146
+ if depth == 0:
147
+ return text[start : i + 1]
148
+ return text
149
+
150
+ clean = _extract_json(clean)
151
+
132
152
  # Escape control characters (except \n, \r, \t) for JSON safety
133
153
  def escape_control_char(match):
134
154
  char = match.group(0)
135
- # Keep allowed whitespace, escape others as \uXXXX
136
155
  if char in "\n\r\t":
137
156
  return char
138
157
  return f"\\u{ord(char):04x}"
139
-
140
- # Match any control character (ord < 32) except \n, \r, \t
141
- import re
158
+
142
159
  clean = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", escape_control_char, clean)
143
- return json.loads(clean)
160
+
161
+ # Try strict parse first
162
+ try:
163
+ return json.loads(clean)
164
+ except json.JSONDecodeError:
165
+ pass
166
+
167
+ # Fallback: try ujson (more lenient with some LLM quirks) if available
168
+ try:
169
+ import ujson
170
+ return ujson.loads(clean)
171
+ except Exception:
172
+ pass
173
+
174
+ # Repair trailing commas before ] or }
175
+ repaired = re.sub(r",(\s*[}\]])", r"\1", clean)
176
+ try:
177
+ return json.loads(repaired)
178
+ except json.JSONDecodeError:
179
+ pass
180
+
181
+ # Repair raw newlines inside string values by escaping them
182
+ def _escape_newlines_in_strings(text: str) -> str:
183
+ result = []
184
+ in_string = False
185
+ escape = False
186
+ for ch in text:
187
+ if escape:
188
+ result.append(ch)
189
+ escape = False
190
+ continue
191
+ if ch == "\\":
192
+ result.append(ch)
193
+ escape = True
194
+ continue
195
+ if ch == '"' and not in_string:
196
+ in_string = True
197
+ result.append(ch)
198
+ continue
199
+ if ch == '"' and in_string:
200
+ in_string = False
201
+ result.append(ch)
202
+ continue
203
+ if ch == "\n" and in_string:
204
+ result.append("\\n")
205
+ continue
206
+ if ch == "\r" and in_string:
207
+ result.append("\\r")
208
+ continue
209
+ result.append(ch)
210
+ return "".join(result)
211
+
212
+ repaired = _escape_newlines_in_strings(clean)
213
+ try:
214
+ return json.loads(repaired)
215
+ except json.JSONDecodeError:
216
+ pass
217
+
218
+ # Last resort: raise original error with cleaned text for debugging
219
+ raise json.JSONDecodeError("Failed to parse LLM JSON after all repairs", clean, 0)
144
220
 
145
221
 
146
222
  # ---------------------------------------------------------------------------
@@ -19,11 +19,32 @@ from typing import Any, Optional
19
19
 
20
20
  from ..models import MigrationStep
21
21
 
22
- from . import builtins, kiosk
22
+ from . import (
23
+ docker,
24
+ generic,
25
+ hardware,
26
+ k3s,
27
+ kiosk,
28
+ podman,
29
+ process,
30
+ scm,
31
+ transfer,
32
+ )
23
33
 
24
34
  # Merge domain modules into a single flat registry.
35
+ _ALL_MODULES = (
36
+ docker.ALL
37
+ + generic.ALL
38
+ + hardware.ALL
39
+ + k3s.ALL
40
+ + kiosk.ALL
41
+ + podman.ALL
42
+ + process.ALL
43
+ + scm.ALL
44
+ + transfer.ALL
45
+ )
25
46
  _LIBRARY: dict[str, MigrationStep] = {
26
- s.id: s for s in (builtins.ALL + kiosk.ALL)
47
+ s.id: s for s in _ALL_MODULES
27
48
  }
28
49
 
29
50
 
@@ -0,0 +1,40 @@
1
+ """Docker container runtime domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ DOCKER_PRUNE = _step(
12
+ id="docker_prune",
13
+ action=StepAction.SSH_CMD,
14
+ description="Prune unused Docker images and build cache",
15
+ command="docker image prune -f && docker builder prune -f",
16
+ risk=ConflictSeverity.LOW,
17
+ )
18
+
19
+ DOCKER_COMPOSE_DOWN = _step(
20
+ id="docker_compose_down",
21
+ action=StepAction.DOCKER_COMPOSE_DOWN,
22
+ description="Stop Docker Compose stack",
23
+ command="docker compose down",
24
+ risk=ConflictSeverity.MEDIUM,
25
+ rollback_command="docker compose up -d",
26
+ )
27
+
28
+ RESTART_TRAEFIK = _step(
29
+ id="restart_traefik",
30
+ action=StepAction.SSH_CMD,
31
+ description="Restart Traefik container",
32
+ command="docker restart $(docker ps -q --filter name=traefik) 2>/dev/null || true",
33
+ risk=ConflictSeverity.LOW,
34
+ )
35
+
36
+ ALL: list[MigrationStep] = [
37
+ DOCKER_PRUNE,
38
+ DOCKER_COMPOSE_DOWN,
39
+ RESTART_TRAEFIK,
40
+ ]
@@ -0,0 +1,87 @@
1
+ """Generic / cross-domain utility steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ # ── waits ─────────────────────────────────────────────────────────────────────
12
+
13
+ WAIT_30 = _step(
14
+ id="wait_startup",
15
+ action=StepAction.WAIT,
16
+ description="Wait 30 s for services to start",
17
+ seconds=30,
18
+ risk=ConflictSeverity.LOW,
19
+ )
20
+
21
+ WAIT_60 = _step(
22
+ id="wait_startup_long",
23
+ action=StepAction.WAIT,
24
+ description="Wait 60 s for services to start (slow build)",
25
+ seconds=60,
26
+ risk=ConflictSeverity.LOW,
27
+ )
28
+
29
+ # ── verify ────────────────────────────────────────────────────────────────────
30
+
31
+ HTTP_HEALTH_CHECK = _step(
32
+ id="http_health_check",
33
+ action=StepAction.HTTP_CHECK,
34
+ description="Verify backend health endpoint",
35
+ url=None, # must be set by caller
36
+ expect="healthy",
37
+ risk=ConflictSeverity.LOW,
38
+ )
39
+
40
+ VERSION_CHECK = _step(
41
+ id="version_check",
42
+ action=StepAction.VERSION_CHECK,
43
+ description="Verify deployed version",
44
+ url=None, # must be set by caller
45
+ expect=None,
46
+ risk=ConflictSeverity.LOW,
47
+ )
48
+
49
+ # ── systemd generic ───────────────────────────────────────────────────────────
50
+
51
+ SYSTEMCTL_RESTART = _step(
52
+ id="systemctl_restart",
53
+ action=StepAction.SYSTEMCTL_START,
54
+ description="Restart a systemd service (set command= to override)",
55
+ command="systemctl restart {service}",
56
+ risk=ConflictSeverity.LOW,
57
+ )
58
+
59
+ SYSTEMCTL_DAEMON_RELOAD = _step(
60
+ id="systemctl_daemon_reload",
61
+ action=StepAction.SSH_CMD,
62
+ description="Reload systemd daemon",
63
+ command="systemctl daemon-reload",
64
+ risk=ConflictSeverity.LOW,
65
+ )
66
+
67
+ # ── nginx ─────────────────────────────────────────────────────────────────────
68
+
69
+ STOP_NGINX = _step(
70
+ id="stop_nginx",
71
+ action=StepAction.SYSTEMCTL_STOP,
72
+ service="nginx",
73
+ description="Stop host nginx (conflicts on port 80)",
74
+ command="systemctl stop nginx && systemctl disable nginx",
75
+ risk=ConflictSeverity.MEDIUM,
76
+ rollback_command="systemctl start nginx",
77
+ )
78
+
79
+ ALL: list[MigrationStep] = [
80
+ WAIT_30,
81
+ WAIT_60,
82
+ HTTP_HEALTH_CHECK,
83
+ VERSION_CHECK,
84
+ SYSTEMCTL_RESTART,
85
+ SYSTEMCTL_DAEMON_RELOAD,
86
+ STOP_NGINX,
87
+ ]
@@ -0,0 +1,22 @@
1
+ """Hardware diagnostic domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ HARDWARE_DIAGNOSTIC = _step(
12
+ id="hardware_diagnostic",
13
+ action=StepAction.PLUGIN,
14
+ plugin_type="hardware_diagnostic",
15
+ description="Analyze system hardware and provide configuration recommendations",
16
+ plugin_params={"checks": ["platform", "cpu", "memory", "storage"], "platform": "auto", "verbose": True},
17
+ risk=ConflictSeverity.LOW,
18
+ )
19
+
20
+ ALL: list[MigrationStep] = [
21
+ HARDWARE_DIAGNOSTIC,
22
+ ]
@@ -0,0 +1,58 @@
1
+ """k3s / Kubernetes domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ FLUSH_K3S_IPTABLES = _step(
12
+ id="flush_k3s_iptables",
13
+ action=StepAction.SSH_CMD,
14
+ description="Flush k3s CNI-HOSTPORT-DNAT + KUBE-* chains (stale rules block Docker-proxy on 80/443)",
15
+ command=(
16
+ "iptables -t nat -F CNI-HOSTPORT-DNAT 2>/dev/null || true && "
17
+ "iptables -t nat -F CNI-HOSTPORT-MASQ 2>/dev/null || true && "
18
+ "iptables -t nat -F KUBE-SERVICES 2>/dev/null || true && "
19
+ "iptables -t nat -F KUBE-NODEPORTS 2>/dev/null || true && "
20
+ "iptables -t filter -F KUBE-FORWARD 2>/dev/null || true && "
21
+ "echo iptables-flushed"
22
+ ),
23
+ risk=ConflictSeverity.LOW,
24
+ )
25
+
26
+ DELETE_K3S_INGRESSES = _step(
27
+ id="delete_k3s_ingresses",
28
+ action=StepAction.KUBECTL_DELETE,
29
+ description="Delete k3s ingresses to remove iptables DNAT rules",
30
+ command="k3s kubectl delete ingress --all-namespaces --all 2>/dev/null || true",
31
+ risk=ConflictSeverity.LOW,
32
+ )
33
+
34
+ STOP_K3S = _step(
35
+ id="stop_k3s",
36
+ action=StepAction.SYSTEMCTL_STOP,
37
+ service="k3s",
38
+ description="Stop k3s to free ports 80/443",
39
+ command="systemctl stop k3s",
40
+ risk=ConflictSeverity.MEDIUM,
41
+ rollback_command="systemctl start k3s",
42
+ )
43
+
44
+ DISABLE_K3S = _step(
45
+ id="disable_k3s",
46
+ action=StepAction.SYSTEMCTL_DISABLE,
47
+ service="k3s",
48
+ description="Disable k3s on boot",
49
+ command="systemctl disable k3s",
50
+ risk=ConflictSeverity.LOW,
51
+ )
52
+
53
+ ALL: list[MigrationStep] = [
54
+ FLUSH_K3S_IPTABLES,
55
+ DELETE_K3S_INGRESSES,
56
+ STOP_K3S,
57
+ DISABLE_K3S,
58
+ ]
@@ -0,0 +1,41 @@
1
+ """Podman / Quadlet domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ PODMAN_DAEMON_RELOAD = _step(
12
+ id="podman_daemon_reload",
13
+ action=StepAction.SYSTEMCTL_START,
14
+ description="Reload systemd to pick up Quadlet unit files",
15
+ command="systemctl --user daemon-reload",
16
+ risk=ConflictSeverity.LOW,
17
+ )
18
+
19
+ STOP_PODMAN = _step(
20
+ id="stop_podman",
21
+ action=StepAction.SYSTEMCTL_STOP,
22
+ service="podman",
23
+ description="Stop all Podman containers via systemd",
24
+ command="systemctl stop podman.service 2>/dev/null || podman stop --all 2>/dev/null || true",
25
+ risk=ConflictSeverity.MEDIUM,
26
+ rollback_command="systemctl start podman.service 2>/dev/null || true",
27
+ )
28
+
29
+ ENABLE_PODMAN_UNIT = _step(
30
+ id="enable_podman_unit",
31
+ action=StepAction.SYSTEMCTL_START,
32
+ description="Enable and start a Podman Quadlet unit (set service= to override)",
33
+ command="systemctl daemon-reload && systemctl enable --now {service}.service",
34
+ risk=ConflictSeverity.LOW,
35
+ )
36
+
37
+ ALL: list[MigrationStep] = [
38
+ PODMAN_DAEMON_RELOAD,
39
+ STOP_PODMAN,
40
+ ENABLE_PODMAN_UNIT,
41
+ ]
@@ -0,0 +1,32 @@
1
+ """Process-control domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ KILL_PROCESSES_ON_PORTS = _step(
12
+ id="kill_processes_on_ports",
13
+ action=StepAction.PLUGIN,
14
+ plugin_type="process_control",
15
+ description="Kill processes on specified ports (auto-detects PIDs)",
16
+ plugin_params={"ports": [], "strategy": "graceful", "timeout": 10, "notify": True},
17
+ risk=ConflictSeverity.LOW,
18
+ )
19
+
20
+ KILL_DEV_PROCESSES = _step(
21
+ id="kill_dev_processes",
22
+ action=StepAction.PLUGIN,
23
+ plugin_type="process_control",
24
+ description="Kill common dev server processes (vite, uvicorn, python)",
25
+ plugin_params={"ports": [8100, 8101, 8202, 3000, 5173], "strategy": "graceful", "timeout": 5, "notify": True},
26
+ risk=ConflictSeverity.LOW,
27
+ )
28
+
29
+ ALL: list[MigrationStep] = [
30
+ KILL_PROCESSES_ON_PORTS,
31
+ KILL_DEV_PROCESSES,
32
+ ]
@@ -0,0 +1,22 @@
1
+ """Source-control management domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ GIT_PULL = _step(
12
+ id="git_pull",
13
+ action=StepAction.SSH_CMD,
14
+ description="Pull latest code from git (cd to remote_dir first)",
15
+ command="git -C ~/app pull --ff-only",
16
+ risk=ConflictSeverity.LOW,
17
+ rollback_command="git -C ~/app reset --hard HEAD@{1}",
18
+ )
19
+
20
+ ALL: list[MigrationStep] = [
21
+ GIT_PULL,
22
+ ]
@@ -0,0 +1,22 @@
1
+ """File-transfer domain steps."""
2
+ from __future__ import annotations
3
+
4
+ from ..models import ConflictSeverity, MigrationStep, StepAction
5
+
6
+
7
+ def _step(id: str, action: StepAction, description: str, **kw) -> MigrationStep:
8
+ return MigrationStep(id=id, action=action, description=description, **kw)
9
+
10
+
11
+ SYNC_ENV = _step(
12
+ id="sync_env",
13
+ action=StepAction.SCP,
14
+ description="Copy .env file to remote",
15
+ src=None, # caller sets src/dst
16
+ dst=None,
17
+ risk=ConflictSeverity.LOW,
18
+ )
19
+
20
+ ALL: list[MigrationStep] = [
21
+ SYNC_ENV,
22
+ ]
@@ -0,0 +1,109 @@
1
+ """Tests that every domain module in redeploy.steps is correctly wired into StepLibrary."""
2
+ from __future__ import annotations
3
+
4
+ import pytest
5
+
6
+ from redeploy.models import MigrationStep
7
+ from redeploy.steps import StepLibrary
8
+ from redeploy.steps import (
9
+ docker,
10
+ generic,
11
+ hardware,
12
+ k3s,
13
+ kiosk,
14
+ podman,
15
+ process,
16
+ scm,
17
+ transfer,
18
+ )
19
+
20
+
21
+ ALL_MODULES = [
22
+ docker,
23
+ generic,
24
+ hardware,
25
+ k3s,
26
+ kiosk,
27
+ podman,
28
+ process,
29
+ scm,
30
+ transfer,
31
+ ]
32
+
33
+
34
+ class TestNoDuplicateIds:
35
+ def test_no_duplicate_ids_within_each_module(self):
36
+ for mod in ALL_MODULES:
37
+ ids = [s.id for s in mod.ALL]
38
+ assert len(ids) == len(set(ids)), f"duplicate ids in {mod.__name__}: {ids}"
39
+
40
+ def test_no_duplicate_ids_across_all_modules(self):
41
+ all_ids = []
42
+ for mod in ALL_MODULES:
43
+ all_ids.extend(s.id for s in mod.ALL)
44
+ assert len(all_ids) == len(set(all_ids)), f"duplicate ids across modules: {all_ids}"
45
+
46
+
47
+ class TestAllStepsReachableViaLibrary:
48
+ @pytest.mark.parametrize(
49
+ "module",
50
+ ALL_MODULES,
51
+ ids=lambda m: m.__name__.split(".")[-1],
52
+ )
53
+ def test_every_step_in_library(self, module):
54
+ for step in module.ALL:
55
+ found = StepLibrary.get(step.id)
56
+ assert found is not None, f"{step.id} missing from StepLibrary"
57
+ assert isinstance(found, MigrationStep)
58
+
59
+
60
+ class TestLibraryGetReturnsCopy:
61
+ def test_mutating_copy_does_not_affect_template(self):
62
+ s1 = StepLibrary.get("http_health_check")
63
+ s2 = StepLibrary.get("http_health_check")
64
+ assert s1 is not s2
65
+ s1.url = "https://modified.example.com"
66
+ assert s2.url != s1.url
67
+
68
+
69
+ class TestKnownStepsExist:
70
+ def test_k3s_steps(self):
71
+ for sid in ("flush_k3s_iptables", "delete_k3s_ingresses", "stop_k3s", "disable_k3s"):
72
+ assert StepLibrary.get(sid) is not None
73
+
74
+ def test_docker_steps(self):
75
+ for sid in ("docker_prune", "docker_compose_down", "restart_traefik"):
76
+ assert StepLibrary.get(sid) is not None
77
+
78
+ def test_podman_steps(self):
79
+ for sid in ("podman_daemon_reload", "stop_podman", "enable_podman_unit"):
80
+ assert StepLibrary.get(sid) is not None
81
+
82
+ def test_generic_steps(self):
83
+ for sid in (
84
+ "wait_startup",
85
+ "wait_startup_long",
86
+ "http_health_check",
87
+ "version_check",
88
+ "systemctl_restart",
89
+ "systemctl_daemon_reload",
90
+ "stop_nginx",
91
+ ):
92
+ assert StepLibrary.get(sid) is not None
93
+
94
+ def test_scm_steps(self):
95
+ assert StepLibrary.get("git_pull") is not None
96
+
97
+ def test_transfer_steps(self):
98
+ assert StepLibrary.get("sync_env") is not None
99
+
100
+ def test_process_steps(self):
101
+ for sid in ("kill_processes_on_ports", "kill_dev_processes"):
102
+ assert StepLibrary.get(sid) is not None
103
+
104
+ def test_hardware_steps(self):
105
+ assert StepLibrary.get("hardware_diagnostic") is not None
106
+
107
+ def test_kiosk_steps(self):
108
+ for sid in ("kanshi_dsi_only", "autostart_kiosk", "browser_kiosk_script"):
109
+ assert StepLibrary.get(sid) is not None