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.
- {redeploy-0.2.67 → redeploy-0.2.69}/PKG-INFO +6 -6
- {redeploy-0.2.67 → redeploy-0.2.69}/README.md +5 -5
- {redeploy-0.2.67 → redeploy-0.2.69}/pyproject.toml +1 -1
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/__init__.py +1 -1
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/blueprint.py +6 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/prompt_cmd.py +81 -5
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/steps/__init__.py +23 -2
- redeploy-0.2.69/redeploy/steps/docker.py +40 -0
- redeploy-0.2.69/redeploy/steps/generic.py +87 -0
- redeploy-0.2.69/redeploy/steps/hardware.py +22 -0
- redeploy-0.2.69/redeploy/steps/k3s.py +58 -0
- redeploy-0.2.69/redeploy/steps/podman.py +41 -0
- redeploy-0.2.69/redeploy/steps/process.py +32 -0
- redeploy-0.2.69/redeploy/steps/scm.py +22 -0
- redeploy-0.2.69/redeploy/steps/transfer.py +22 -0
- redeploy-0.2.69/redeploy/tests/test_steps_registry.py +109 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/SOURCES.txt +9 -1
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_prompt_llm.py +50 -0
- redeploy-0.2.67/redeploy/steps/builtins.py +0 -275
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/checkers/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/preflight_schema.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/analyze/spec_analyzer.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/audit.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/bump_fix.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/device_map.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/device_map_renderers.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/hardware.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/lint.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/mcp_cmd.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/utils/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/utils/changelog_config.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/version/utils/git_config.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/cli.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/discovery.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/fleet.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/decider.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/hint_provider.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/log_writer.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/loop_detector.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal/runner.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/heal.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/config_hints.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/mcp_server.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/blueprint.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/devices.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/enums.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/hardware.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/infra.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/manifest.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/persisted.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/pipeline.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/plan.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/models/spec.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/observe.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/parse.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/patterns.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/schema.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/ssh.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/steps/kiosk.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_analyze.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_heal.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_hooks.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_iac_compose_contract.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/verify.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy/version.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/setup.cfg +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_blueprint_sources.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_blueprint_sources_compose.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_cli_query.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_example_argocd_flux_plugin.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_example_gitops_ci_plugin.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_example_helm_kustomize_plugin.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_heal_loop_detector.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_iac.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_iac_config_hints.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_iac_plugin_loading.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_import_plugin_template.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_parse.py +0 -0
- {redeploy-0.2.67 → redeploy-0.2.69}/tests/test_redeploy.py +0 -0
- {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.
|
|
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
|
-
     
|
|
36
|
+
  
|
|
37
37
|
|
|
38
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
39
|
-
- 👤 **Human dev:** ~$
|
|
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
|
-
    
|
|
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
|
-
     
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
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
|
-
    
|
|
17
17
|
|
|
18
18
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
19
19
|
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|