redeploy 0.2.76__tar.gz → 0.2.77__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.76 → redeploy-0.2.77}/PKG-INFO +5 -5
- {redeploy-0.2.76 → redeploy-0.2.77}/README.md +4 -4
- {redeploy-0.2.76 → redeploy-0.2.77}/pyproject.toml +1 -1
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/__init__.py +1 -1
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/plan_apply.py +19 -338
- redeploy-0.2.77/redeploy/cli/commands/plan_apply_report.py +296 -0
- redeploy-0.2.77/redeploy/cli/commands/plan_apply_run.py +104 -0
- redeploy-0.2.77/redeploy/discovery/helpers.py +32 -0
- redeploy-0.2.77/redeploy/discovery/probe_parse.py +45 -0
- redeploy-0.2.77/redeploy/discovery/registry.py +64 -0
- redeploy-0.2.77/redeploy/discovery/scanners.py +254 -0
- redeploy-0.2.77/redeploy/discovery/ssh_credentials.py +111 -0
- redeploy-0.2.77/redeploy/discovery/types.py +38 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/discovery.py +3 -99
- redeploy-0.2.77/redeploy/discovery_probe.py +45 -0
- redeploy-0.2.77/redeploy/discovery_registry.py +66 -0
- redeploy-0.2.77/redeploy/tests/test_discovery_probe.py +45 -0
- redeploy-0.2.77/redeploy/tests/test_plan_apply_report.py +91 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy.egg-info/PKG-INFO +5 -5
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy.egg-info/SOURCES.txt +12 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/base.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/binary.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/command_ref.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/docker_build.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/env.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/path.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/checkers/reference.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/ignore.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/preflight_schema.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/analyze/spec_analyzer.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/auditor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/extractor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/paths.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/audit/probe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/bump_fix.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/device_map.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/device_map_renderers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/gh_workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/lint.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/mcp_cmd.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/prompt_cmd.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/utils/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/utils/changelog_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/version/utils/git_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/cli.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/config_apply/handlers/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/config_apply/handlers/display.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/fleet.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal/decider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal/hint_provider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal/log_writer.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal/loop_detector.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal/runner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/heal.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/config_hints.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/mcp_server.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/blueprint.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/devices.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/enums.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/infra.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/persisted.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/pipeline.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/plan.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/models/spec.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/observe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/parse.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/schema.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/ssh.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/docker.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/generic.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/hardware.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/k3s.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/kiosk.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/podman.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/process.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/scm.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/steps/transfer.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_analyze.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_heal.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_hooks.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_iac_compose_contract.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_security_hardening.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_steps_registry.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/verify.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy/version.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/setup.cfg +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_blueprint_sources.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_blueprint_sources_compose.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_cli_gh_workflow.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_cli_query.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_example_argocd_flux_plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_example_gitops_ci_plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_example_helm_kustomize_plugin.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_heal_loop_detector.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_iac.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_iac_config_hints.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_iac_plugin_loading.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_import_plugin_template.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_parse.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_prompt_llm.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/tests/test_redeploy.py +0 -0
- {redeploy-0.2.76 → redeploy-0.2.77}/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.77
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -33,17 +33,17 @@ Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
|
33
33
|
|
|
34
34
|
## AI Cost Tracking
|
|
35
35
|
|
|
36
|
-
    
|
|
37
|
+
  
|
|
38
38
|
|
|
39
|
-
- 🤖 **LLM usage:** $5.
|
|
39
|
+
- 🤖 **LLM usage:** $5.9206 (99 commits)
|
|
40
40
|
- 👤 **Human dev:** ~$4198 (42.0h @ $100/h, 30min dedup)
|
|
41
41
|
|
|
42
42
|
Generated on 2026-05-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
    
|
|
47
47
|
|
|
48
48
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
49
49
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $5.
|
|
9
|
+
- 🤖 **LLM usage:** $5.9206 (99 commits)
|
|
10
10
|
- 👤 **Human dev:** ~$4198 (42.0h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-05-20 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.77"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""plan, apply, migrate, run commands — Migration planning and execution."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import subprocess
|
|
5
4
|
import sys
|
|
6
5
|
from datetime import datetime, timezone
|
|
7
6
|
from pathlib import Path
|
|
@@ -20,6 +19,8 @@ from ..core import (
|
|
|
20
19
|
overlay_device_onto_spec,
|
|
21
20
|
)
|
|
22
21
|
from ..display import print_plan_table
|
|
22
|
+
from .plan_apply_report import write_markdown_report
|
|
23
|
+
from .plan_apply_run import run_lint_phase, run_preflight_phase, setup_run_logging
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
@click.command()
|
|
@@ -325,17 +326,13 @@ def run(ctx, spec_file, dry_run, plan_only, do_detect, plan_out, output,
|
|
|
325
326
|
from ...models import ProjectManifest
|
|
326
327
|
from ...plan import Planner
|
|
327
328
|
from ...plugins import load_user_plugins
|
|
328
|
-
from ...apply import Executor
|
|
329
329
|
|
|
330
330
|
console = Console()
|
|
331
331
|
|
|
332
|
-
# Create deployment ignore policy on first run (project-aware template).
|
|
333
332
|
_ensure_redeployignore(Path.cwd(), console)
|
|
334
333
|
|
|
335
|
-
# Load project manifest (redeploy.yaml) if present
|
|
336
334
|
manifest = ProjectManifest.find_and_load(Path.cwd())
|
|
337
335
|
|
|
338
|
-
# Resolve spec file: arg > manifest.spec > "migration.yaml"
|
|
339
336
|
resolved_spec = spec_file or (manifest.spec if manifest else "migration.yaml")
|
|
340
337
|
if not Path(resolved_spec).exists():
|
|
341
338
|
console.print(f"[red]✗ spec file not found: {resolved_spec}[/red]")
|
|
@@ -343,47 +340,12 @@ def run(ctx, spec_file, dry_run, plan_only, do_detect, plan_out, output,
|
|
|
343
340
|
sys.exit(1)
|
|
344
341
|
|
|
345
342
|
spec = load_spec_or_exit(console, resolved_spec)
|
|
346
|
-
started_at =
|
|
347
|
-
|
|
348
|
-
# Redirect all logger output to a dated file under .redeploy/logs/
|
|
349
|
-
log_dir = Path.cwd() / ".redeploy" / "logs"
|
|
350
|
-
log_dir.mkdir(parents=True, exist_ok=True)
|
|
351
|
-
log_file = log_dir / f"redeploy-{started_at.strftime('%Y%m%d_%H%M%S')}.log"
|
|
352
|
-
file_handler_id = logger.add(
|
|
353
|
-
log_file,
|
|
354
|
-
level="DEBUG",
|
|
355
|
-
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level:<8} | {name}:{function}:{line} | {message}",
|
|
356
|
-
enqueue=True,
|
|
357
|
-
)
|
|
358
|
-
logger.info("redeploy run started — spec={}", resolved_spec)
|
|
343
|
+
file_handler_id, _log_file, started_at = setup_run_logging(resolved_spec)
|
|
359
344
|
|
|
360
|
-
# Overlay manifest values
|
|
361
345
|
_apply_manifest_to_spec(console, manifest, spec, env_name)
|
|
362
346
|
_print_spec_summary(console, spec)
|
|
363
347
|
|
|
364
|
-
|
|
365
|
-
lint_result = None
|
|
366
|
-
if lint:
|
|
367
|
-
from ...analyze import SpecAnalyzer, IssueSeverity
|
|
368
|
-
analyzer = SpecAnalyzer(base_dir=Path.cwd())
|
|
369
|
-
_, lint_result = analyzer.analyze_file(Path(resolved_spec))
|
|
370
|
-
if lint_result.issues:
|
|
371
|
-
console.print(f"\n[bold]lint[/bold]")
|
|
372
|
-
for i in lint_result.issues:
|
|
373
|
-
color = "red" if i.severity == IssueSeverity.ERROR else "yellow"
|
|
374
|
-
prefix = f"[{color}]{i.severity.value}[/{color}]"
|
|
375
|
-
step = f" ({i.step_id})" if i.step_id else ""
|
|
376
|
-
console.print(f" {prefix}{step} {i.category}: {i.message}")
|
|
377
|
-
if i.suggestion:
|
|
378
|
-
console.print(f" [dim]→ {i.suggestion}[/dim]")
|
|
379
|
-
if not lint_result.passed:
|
|
380
|
-
console.print("\n[red]✗ lint failed — fix errors above or use --no-lint to skip.[/red]")
|
|
381
|
-
logger.remove(file_handler_id)
|
|
382
|
-
sys.exit(1)
|
|
383
|
-
else:
|
|
384
|
-
console.print(f"[yellow]⚠ lint passed with {len(lint_result.warnings())} warning(s)[/yellow]")
|
|
385
|
-
else:
|
|
386
|
-
console.print("[green] ✓ lint passed[/green]")
|
|
348
|
+
lint_result = run_lint_phase(console, resolved_spec, lint, file_handler_id)
|
|
387
349
|
|
|
388
350
|
# Optional live detect
|
|
389
351
|
if do_detect:
|
|
@@ -405,39 +367,23 @@ def run(ctx, spec_file, dry_run, plan_only, do_detect, plan_out, output,
|
|
|
405
367
|
|
|
406
368
|
print_plan_table(console, migration)
|
|
407
369
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
blockers = len(preflight_result.blockers)
|
|
423
|
-
console.print(f"\n[bold]preflight[/bold] [dim]schema saved → {preflight_schema_out}[/dim]")
|
|
424
|
-
if blockers:
|
|
425
|
-
console.print(f"[yellow]⚠ preflight blockers: {blockers}[/yellow]")
|
|
426
|
-
for b in preflight_result.blockers[:10]:
|
|
427
|
-
sid = f" ({b.get('step_id')})" if b.get("step_id") else ""
|
|
428
|
-
console.print(f" [yellow]- {b.get('type')}{sid}: {b.get('message')}[/yellow]")
|
|
429
|
-
if blockers > 10:
|
|
430
|
-
console.print(f" [dim]... and {blockers - 10} more blockers in schema file[/dim]")
|
|
431
|
-
if strict_preflight:
|
|
432
|
-
console.print("\n[red]✗ strict preflight failed — aborting before apply[/red]")
|
|
433
|
-
logger.remove(file_handler_id)
|
|
434
|
-
sys.exit(1)
|
|
435
|
-
else:
|
|
436
|
-
console.print("[green] ✓ preflight passed (no blockers)[/green]")
|
|
370
|
+
run_preflight_phase(
|
|
371
|
+
console,
|
|
372
|
+
preflight=preflight,
|
|
373
|
+
resolved_spec=resolved_spec,
|
|
374
|
+
spec=spec,
|
|
375
|
+
migration=migration,
|
|
376
|
+
lint_result=lint_result,
|
|
377
|
+
preflight_schema_out=preflight_schema_out,
|
|
378
|
+
preflight_remote=preflight_remote,
|
|
379
|
+
dry_run=dry_run,
|
|
380
|
+
strict_preflight=strict_preflight,
|
|
381
|
+
file_handler_id=file_handler_id,
|
|
382
|
+
)
|
|
437
383
|
|
|
438
384
|
if plan_only:
|
|
439
385
|
if report:
|
|
440
|
-
|
|
386
|
+
write_markdown_report(
|
|
441
387
|
console=console,
|
|
442
388
|
migration=migration,
|
|
443
389
|
spec_path=Path(resolved_spec),
|
|
@@ -487,7 +433,7 @@ def run(ctx, spec_file, dry_run, plan_only, do_detect, plan_out, output,
|
|
|
487
433
|
)
|
|
488
434
|
|
|
489
435
|
if report:
|
|
490
|
-
|
|
436
|
+
write_markdown_report(
|
|
491
437
|
console=console,
|
|
492
438
|
migration=migration,
|
|
493
439
|
spec_path=Path(resolved_spec),
|
|
@@ -738,268 +684,3 @@ def _inject_project_sync_step(migration, spec, project_root: Path, console) -> N
|
|
|
738
684
|
console.print("[dim]plan: injected sync_project_tree (full rsync before apply)[/dim]")
|
|
739
685
|
|
|
740
686
|
|
|
741
|
-
def _default_report_path(spec_path: Path) -> Path:
|
|
742
|
-
return spec_path.parent / f".{spec_path.stem}.md"
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
def _resolve_audit_entry(migration, started_at: datetime, ok: bool):
|
|
746
|
-
from ...observe import AuditEntry, DeployAuditLog
|
|
747
|
-
|
|
748
|
-
log = DeployAuditLog()
|
|
749
|
-
entries = log.filter(host=migration.host, app=migration.app, since=started_at)
|
|
750
|
-
if entries:
|
|
751
|
-
return entries[-1]
|
|
752
|
-
|
|
753
|
-
# Fallback if audit record is unavailable for any reason.
|
|
754
|
-
done_count = sum(1 for s in migration.steps if str(s.status).endswith("DONE"))
|
|
755
|
-
failed_count = sum(1 for s in migration.steps if str(s.status).endswith("FAILED"))
|
|
756
|
-
if ok and done_count == 0 and failed_count == 0:
|
|
757
|
-
# plan-only fallback: treat all planned steps as successful planning output
|
|
758
|
-
done_count = len(migration.steps)
|
|
759
|
-
|
|
760
|
-
data = {
|
|
761
|
-
"ts": datetime.now(timezone.utc).isoformat(),
|
|
762
|
-
"host": migration.host,
|
|
763
|
-
"app": migration.app,
|
|
764
|
-
"from_strategy": migration.from_strategy.value,
|
|
765
|
-
"to_strategy": migration.to_strategy.value,
|
|
766
|
-
"ok": ok,
|
|
767
|
-
"dry_run": False,
|
|
768
|
-
"elapsed_s": 0.0,
|
|
769
|
-
"steps_total": len(migration.steps),
|
|
770
|
-
"steps_ok": done_count,
|
|
771
|
-
"steps_failed": failed_count,
|
|
772
|
-
"steps": [
|
|
773
|
-
{
|
|
774
|
-
"id": s.id,
|
|
775
|
-
"action": s.action.value,
|
|
776
|
-
"status": str(s.status).split(".")[-1].lower(),
|
|
777
|
-
"result": s.result,
|
|
778
|
-
"error": s.error,
|
|
779
|
-
}
|
|
780
|
-
for s in migration.steps
|
|
781
|
-
],
|
|
782
|
-
}
|
|
783
|
-
return AuditEntry(data)
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
def _step_command_block(step) -> str:
|
|
787
|
-
if step.command:
|
|
788
|
-
return step.command.strip()
|
|
789
|
-
if step.command_ref:
|
|
790
|
-
return f"command_ref: {step.command_ref}"
|
|
791
|
-
if step.action.value == "rsync":
|
|
792
|
-
parts = [f"rsync {step.src or ''} -> {step.dst or ''}"]
|
|
793
|
-
if step.excludes:
|
|
794
|
-
parts.append("excludes:")
|
|
795
|
-
parts.extend(f"- {x}" for x in step.excludes)
|
|
796
|
-
return "\n".join(parts)
|
|
797
|
-
if step.action.value == "scp":
|
|
798
|
-
return f"scp {step.src or ''} -> {step.dst or ''}"
|
|
799
|
-
if step.action.value in {"http_check", "version_check"}:
|
|
800
|
-
return f"url: {step.url or ''}\nexpect: {step.expect or ''}".strip()
|
|
801
|
-
return f"action: {step.action.value}"
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
def _build_checksum_verification(migration, executed: bool) -> dict | None:
|
|
805
|
-
"""Build post-deploy sync verification with checksum-aware rsync dry-run."""
|
|
806
|
-
sync_step = next((s for s in migration.steps if s.id == "sync_project_tree"), None)
|
|
807
|
-
if not sync_step:
|
|
808
|
-
return None
|
|
809
|
-
|
|
810
|
-
src = (sync_step.src or "").strip()
|
|
811
|
-
dst = (sync_step.dst or "").strip()
|
|
812
|
-
host = (migration.host or "").strip()
|
|
813
|
-
|
|
814
|
-
local_root = Path(src.rstrip("/")).expanduser() if src else Path.cwd()
|
|
815
|
-
remote_root = dst.rstrip("/") if dst else ""
|
|
816
|
-
|
|
817
|
-
if not executed:
|
|
818
|
-
return {
|
|
819
|
-
"status": "skipped",
|
|
820
|
-
"reason": "plan-only run (apply not executed)",
|
|
821
|
-
"host": host,
|
|
822
|
-
"local_root": str(local_root),
|
|
823
|
-
"remote_root": remote_root,
|
|
824
|
-
"command": "(not executed)",
|
|
825
|
-
"changed_paths": [],
|
|
826
|
-
"changed_count": 0,
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if not host or host.lower() in {"local", "localhost", "127.0.0.1"}:
|
|
830
|
-
return {
|
|
831
|
-
"status": "skipped",
|
|
832
|
-
"reason": "remote checksum verification requires SSH host",
|
|
833
|
-
"host": host,
|
|
834
|
-
"local_root": str(local_root),
|
|
835
|
-
"remote_root": remote_root,
|
|
836
|
-
"command": "(not executed)",
|
|
837
|
-
"changed_paths": [],
|
|
838
|
-
"changed_count": 0,
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if not local_root.exists() or not remote_root:
|
|
842
|
-
return {
|
|
843
|
-
"status": "error",
|
|
844
|
-
"reason": "sync scope is incomplete (missing local or remote root)",
|
|
845
|
-
"host": host,
|
|
846
|
-
"local_root": str(local_root),
|
|
847
|
-
"remote_root": remote_root,
|
|
848
|
-
"command": "(not executed)",
|
|
849
|
-
"changed_paths": [],
|
|
850
|
-
"changed_count": 0,
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
cmd = ["rsync", "-a", "-z", "--delete", "--checksum", "--dry-run", "--itemize-changes"]
|
|
854
|
-
if (local_root / ".gitignore").exists():
|
|
855
|
-
cmd += ["--filter=:- .gitignore"]
|
|
856
|
-
if (local_root / ".redeployignore").exists():
|
|
857
|
-
cmd += ["--filter=:- .redeployignore"]
|
|
858
|
-
for ex in sync_step.excludes or []:
|
|
859
|
-
cmd += ["--exclude", ex]
|
|
860
|
-
cmd += [f"{local_root.as_posix().rstrip('/')}/", f"{host}:{remote_root.rstrip('/')}/"]
|
|
861
|
-
|
|
862
|
-
command_preview = " ".join(cmd)
|
|
863
|
-
try:
|
|
864
|
-
result = subprocess.run(cmd, capture_output=True, text=True, timeout=1200)
|
|
865
|
-
except Exception as exc: # pragma: no cover - defensive, non-fatal for report
|
|
866
|
-
return {
|
|
867
|
-
"status": "error",
|
|
868
|
-
"reason": str(exc),
|
|
869
|
-
"host": host,
|
|
870
|
-
"local_root": str(local_root),
|
|
871
|
-
"remote_root": remote_root,
|
|
872
|
-
"command": command_preview,
|
|
873
|
-
"changed_paths": [],
|
|
874
|
-
"changed_count": 0,
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
raw_lines = [ln.rstrip() for ln in (result.stdout or "").splitlines() if ln.strip()]
|
|
878
|
-
changed_lines = [
|
|
879
|
-
ln
|
|
880
|
-
for ln in raw_lines
|
|
881
|
-
if ln not in {"sending incremental file list"}
|
|
882
|
-
and not ln.startswith("sent ")
|
|
883
|
-
and not ln.startswith("total size is ")
|
|
884
|
-
and not ln.startswith("created directory ")
|
|
885
|
-
]
|
|
886
|
-
|
|
887
|
-
status = "match" if result.returncode == 0 and not changed_lines else "mismatch"
|
|
888
|
-
reason = ""
|
|
889
|
-
if result.returncode != 0:
|
|
890
|
-
status = "error"
|
|
891
|
-
reason = (result.stderr or "").strip() or f"rsync exited with {result.returncode}"
|
|
892
|
-
|
|
893
|
-
return {
|
|
894
|
-
"status": status,
|
|
895
|
-
"reason": reason,
|
|
896
|
-
"host": host,
|
|
897
|
-
"local_root": str(local_root),
|
|
898
|
-
"remote_root": remote_root,
|
|
899
|
-
"command": command_preview,
|
|
900
|
-
"changed_paths": changed_lines[:200],
|
|
901
|
-
"changed_count": len(changed_lines),
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
def _render_markdown_report(entry, migration, spec_path: Path, checksum: dict | None = None) -> str:
|
|
906
|
-
by_id = {s.id: s for s in migration.steps}
|
|
907
|
-
lines: list[str] = [
|
|
908
|
-
f"# Redeploy Execution Report - {spec_path.name}",
|
|
909
|
-
"",
|
|
910
|
-
f"- Timestamp: {entry.ts}",
|
|
911
|
-
f"- Spec: {spec_path}",
|
|
912
|
-
f"- Host: {entry.host}",
|
|
913
|
-
f"- App: {entry.app}",
|
|
914
|
-
f"- Strategy: {entry.from_strategy} -> {entry.to_strategy}",
|
|
915
|
-
f"- Result: {'SUCCESS' if entry.ok else 'FAILED'}",
|
|
916
|
-
f"- Steps: {entry.steps_ok}/{entry.steps_total} ok",
|
|
917
|
-
f"- Elapsed: {entry.elapsed_s:.1f}s",
|
|
918
|
-
"",
|
|
919
|
-
]
|
|
920
|
-
|
|
921
|
-
if checksum:
|
|
922
|
-
status = (checksum.get("status") or "unknown").upper()
|
|
923
|
-
lines += [
|
|
924
|
-
"## Sync Checksum Verification",
|
|
925
|
-
"",
|
|
926
|
-
"- Method: rsync --checksum --dry-run --itemize-changes",
|
|
927
|
-
f"- Scope: {checksum.get('local_root', '')} -> {checksum.get('host', '')}:{checksum.get('remote_root', '')}",
|
|
928
|
-
f"- Status: {status}",
|
|
929
|
-
f"- Changed Paths: {checksum.get('changed_count', 0)}",
|
|
930
|
-
]
|
|
931
|
-
reason = (checksum.get("reason") or "").strip()
|
|
932
|
-
if reason:
|
|
933
|
-
lines.append(f"- Note: {reason}")
|
|
934
|
-
lines += [
|
|
935
|
-
"",
|
|
936
|
-
"#### Command",
|
|
937
|
-
"```bash",
|
|
938
|
-
checksum.get("command") or "(not executed)",
|
|
939
|
-
"```",
|
|
940
|
-
"",
|
|
941
|
-
]
|
|
942
|
-
if checksum.get("changed_paths"):
|
|
943
|
-
lines += [
|
|
944
|
-
"#### Differences",
|
|
945
|
-
"```text",
|
|
946
|
-
"\n".join(checksum.get("changed_paths") or []),
|
|
947
|
-
"```",
|
|
948
|
-
"",
|
|
949
|
-
]
|
|
950
|
-
|
|
951
|
-
lines += [
|
|
952
|
-
"## Step Logs",
|
|
953
|
-
"",
|
|
954
|
-
]
|
|
955
|
-
|
|
956
|
-
for i, step_row in enumerate(entry.steps, 1):
|
|
957
|
-
sid = step_row.get("id", "")
|
|
958
|
-
status = (step_row.get("status") or "").upper()
|
|
959
|
-
action = step_row.get("action", "")
|
|
960
|
-
model_step = by_id.get(sid)
|
|
961
|
-
desc = model_step.description if model_step else ""
|
|
962
|
-
cmd = _step_command_block(model_step) if model_step else f"action: {action}"
|
|
963
|
-
result = (step_row.get("result") or "").strip()
|
|
964
|
-
error = (step_row.get("error") or "").strip()
|
|
965
|
-
log_text = error or result or "(no output captured)"
|
|
966
|
-
|
|
967
|
-
lines += [
|
|
968
|
-
f"### {i}. {sid} [{status}]",
|
|
969
|
-
"",
|
|
970
|
-
f"- Action: {action}",
|
|
971
|
-
f"- Description: {desc}",
|
|
972
|
-
"",
|
|
973
|
-
"#### Executed",
|
|
974
|
-
"```bash",
|
|
975
|
-
cmd,
|
|
976
|
-
"```",
|
|
977
|
-
"",
|
|
978
|
-
"#### Output",
|
|
979
|
-
"```text",
|
|
980
|
-
log_text,
|
|
981
|
-
"```",
|
|
982
|
-
"",
|
|
983
|
-
]
|
|
984
|
-
|
|
985
|
-
return "\n".join(lines)
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
def _write_markdown_report(
|
|
989
|
-
console,
|
|
990
|
-
migration,
|
|
991
|
-
spec_path: Path,
|
|
992
|
-
started_at: datetime,
|
|
993
|
-
ok: bool,
|
|
994
|
-
executed: bool,
|
|
995
|
-
report_file: Path | None = None,
|
|
996
|
-
) -> None:
|
|
997
|
-
entry = _resolve_audit_entry(migration, started_at=started_at, ok=ok)
|
|
998
|
-
checksum = _build_checksum_verification(migration, executed=executed)
|
|
999
|
-
out_path = report_file or _default_report_path(spec_path)
|
|
1000
|
-
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1001
|
-
out_path.write_text(
|
|
1002
|
-
_render_markdown_report(entry, migration, spec_path, checksum=checksum),
|
|
1003
|
-
encoding="utf-8",
|
|
1004
|
-
)
|
|
1005
|
-
console.print(f"[bold]report[/bold] [dim]saved → {out_path}[/dim]")
|