redeploy 0.2.57__tar.gz → 0.2.59__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.57 → redeploy-0.2.59}/PKG-INFO +6 -6
- {redeploy-0.2.57 → redeploy-0.2.59}/README.md +5 -5
- {redeploy-0.2.57 → redeploy-0.2.59}/pyproject.toml +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/__init__.py +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/device_map_renderers.py +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/plan_apply.py +2 -2
- redeploy-0.2.59/redeploy/cli/commands/version/utils/__init__.py +8 -0
- redeploy-0.2.59/redeploy/cli/commands/version/utils/changelog_config.py +27 -0
- redeploy-0.2.59/redeploy/cli/commands/version/utils/git_config.py +27 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/core.py +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/display.py +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/workflow.py +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/docker_compose.py +37 -4
- redeploy-0.2.59/redeploy/models/__init__.py +121 -0
- redeploy-0.2.59/redeploy/models/blueprint.py +95 -0
- redeploy-0.2.59/redeploy/models/devices.py +170 -0
- redeploy-0.2.59/redeploy/models/hardware.py +117 -0
- redeploy-0.2.59/redeploy/models/infra.py +77 -0
- redeploy-0.2.59/redeploy/models/manifest.py +151 -0
- redeploy-0.2.59/redeploy/models/pipeline.py +35 -0
- redeploy-0.2.59/redeploy/models/plan.py +83 -0
- redeploy-0.2.59/redeploy/models/spec.py +182 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plan/planner.py +1 -1
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy.egg-info/SOURCES.txt +12 -1
- redeploy-0.2.57/redeploy/models.py +0 -1096
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/audit.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/bump_fix.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/device_map.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/hardware.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/mcp_cmd.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/prompt_cmd.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/cli.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/discovery.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/fleet.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal/decider.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal/hint_provider.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal/log_writer.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal/loop_detector.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal/runner.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/heal.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/config_hints.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/mcp_server.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/models/enums.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/models/persisted.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/observe.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/parse.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/patterns.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/schema.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/ssh.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/steps/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/steps/builtins.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/steps/kiosk.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_heal.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_hooks.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_iac_compose_contract.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/verify.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy/version.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/setup.cfg +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_blueprint_sources.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_blueprint_sources_compose.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_cli_query.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_example_argocd_flux_plugin.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_example_gitops_ci_plugin.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_example_helm_kustomize_plugin.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_heal_loop_detector.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_iac.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_iac_config_hints.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_iac_plugin_loading.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_import_plugin_template.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_parse.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_prompt_llm.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/tests/test_redeploy.py +0 -0
- {redeploy-0.2.57 → redeploy-0.2.59}/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.59
|
|
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 (79 commits)
|
|
39
|
+
- 👤 **Human dev:** ~$2897 (29.0h @ $100/h, 30min dedup)
|
|
40
40
|
|
|
41
41
|
Generated on 2026-04-21 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 (79 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2897 (29.0h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-21 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.59"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -147,7 +147,7 @@ def render_rich(console: Console, dm: "DeviceMap") -> None:
|
|
|
147
147
|
]
|
|
148
148
|
rt_str = " ".join(r for r in runtimes if r) or "—"
|
|
149
149
|
console.print(
|
|
150
|
-
f"\n[bold]Infra[/bold] strategy=[cyan]{infra.detected_strategy
|
|
150
|
+
f"\n[bold]Infra[/bold] strategy=[cyan]{infra.detected_strategy}[/cyan]"
|
|
151
151
|
f" version={infra.current_version or '?'} {rt_str}"
|
|
152
152
|
)
|
|
153
153
|
|
|
@@ -186,7 +186,7 @@ def migrate(ctx, host, app, domain, target, strategy, target_version,
|
|
|
186
186
|
state = d.run()
|
|
187
187
|
d.save(state, Path(infra_out))
|
|
188
188
|
console.print(
|
|
189
|
-
f" Strategy: {state.detected_strategy
|
|
189
|
+
f" Strategy: {state.detected_strategy} "
|
|
190
190
|
f" Version: {state.current_version or '?'} "
|
|
191
191
|
f" Conflicts: {len(state.conflicts)}"
|
|
192
192
|
)
|
|
@@ -412,7 +412,7 @@ def _perform_live_detect(console, spec):
|
|
|
412
412
|
)
|
|
413
413
|
state = d.run()
|
|
414
414
|
console.print(
|
|
415
|
-
f" detected: {state.detected_strategy
|
|
415
|
+
f" detected: {state.detected_strategy} "
|
|
416
416
|
f"version={state.current_version or '?'} "
|
|
417
417
|
f"conflicts={len(state.conflicts)}"
|
|
418
418
|
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Shared version-command utilities."""
|
|
2
|
+
from .git_config import resolve_package_release_git_config
|
|
3
|
+
from .changelog_config import resolve_package_release_changelog_config
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"resolve_package_release_git_config",
|
|
7
|
+
"resolve_package_release_changelog_config",
|
|
8
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Resolve changelog config for a package release."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def resolve_package_release_changelog_config(
|
|
8
|
+
manifest_model: Any,
|
|
9
|
+
package_name: str | None,
|
|
10
|
+
*,
|
|
11
|
+
allow_root_fallback: bool,
|
|
12
|
+
) -> Any:
|
|
13
|
+
"""Return the changelog config for *package_name* with optional root fallback."""
|
|
14
|
+
if not package_name:
|
|
15
|
+
return manifest_model.changelog
|
|
16
|
+
|
|
17
|
+
package_manifest = manifest_model.get_package(package_name)
|
|
18
|
+
if package_manifest is None:
|
|
19
|
+
raise ValueError(f"Package '{package_name}' not found in manifest")
|
|
20
|
+
|
|
21
|
+
if package_manifest.changelog is not None:
|
|
22
|
+
return package_manifest.changelog
|
|
23
|
+
|
|
24
|
+
if allow_root_fallback:
|
|
25
|
+
return manifest_model.changelog
|
|
26
|
+
|
|
27
|
+
return None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Resolve git-config for a package release."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def resolve_package_release_git_config(
|
|
8
|
+
manifest_model: Any,
|
|
9
|
+
package_name: str | None,
|
|
10
|
+
*,
|
|
11
|
+
allow_root_fallback: bool,
|
|
12
|
+
) -> Any:
|
|
13
|
+
"""Return the git config for *package_name* with optional root fallback."""
|
|
14
|
+
if not package_name:
|
|
15
|
+
return manifest_model.git
|
|
16
|
+
|
|
17
|
+
package_manifest = manifest_model.get_package(package_name)
|
|
18
|
+
if package_manifest is None:
|
|
19
|
+
raise ValueError(f"Package '{package_name}' not found in manifest")
|
|
20
|
+
|
|
21
|
+
if package_manifest.git is not None:
|
|
22
|
+
return package_manifest.git
|
|
23
|
+
|
|
24
|
+
if allow_root_fallback:
|
|
25
|
+
return manifest_model.git
|
|
26
|
+
|
|
27
|
+
return None
|
|
@@ -126,7 +126,7 @@ def run_detect_for_spec(
|
|
|
126
126
|
d = Detector(host=spec.source.host, app=spec.source.app, domain=spec.source.domain)
|
|
127
127
|
state = d.run()
|
|
128
128
|
console.print(
|
|
129
|
-
f" detected: {state.detected_strategy
|
|
129
|
+
f" detected: {state.detected_strategy} "
|
|
130
130
|
f"version={state.current_version or '?'} "
|
|
131
131
|
f"conflicts={len(state.conflicts)}"
|
|
132
132
|
)
|
|
@@ -36,7 +36,7 @@ def print_infrastructure_summary(console: "Console", state, host: str) -> None:
|
|
|
36
36
|
t.add_column("key", style="dim")
|
|
37
37
|
t.add_column("value")
|
|
38
38
|
t.add_row("App", state.app)
|
|
39
|
-
t.add_row("Strategy (detected)", state.detected_strategy
|
|
39
|
+
t.add_row("Strategy (detected)", state.detected_strategy)
|
|
40
40
|
t.add_row("Version", state.current_version or "unknown")
|
|
41
41
|
t.add_row("Docker", state.runtime.docker or "—")
|
|
42
42
|
t.add_row("k3s", state.runtime.k3s or "—")
|
|
@@ -398,7 +398,7 @@ class DetectionWorkflow:
|
|
|
398
398
|
host_res = self._deep_detect(host_res)
|
|
399
399
|
if host_res.state:
|
|
400
400
|
logger.info(
|
|
401
|
-
f"[workflow] strategy={host_res.state.detected_strategy
|
|
401
|
+
f"[workflow] strategy={host_res.state.detected_strategy} "
|
|
402
402
|
f"ports={sorted(host_res.state.ports.keys())} "
|
|
403
403
|
f"conflicts={len(host_res.state.conflicts)}"
|
|
404
404
|
)
|
|
@@ -399,6 +399,40 @@ class DockerComposeParser:
|
|
|
399
399
|
def _parse_service_command(self, cfg: dict) -> Optional[str]:
|
|
400
400
|
return str(cfg["command"]) if cfg.get("command") else None
|
|
401
401
|
|
|
402
|
+
def _build_service_info(
|
|
403
|
+
self,
|
|
404
|
+
*,
|
|
405
|
+
name: str,
|
|
406
|
+
cfg: dict,
|
|
407
|
+
image: Optional[str],
|
|
408
|
+
ports: list[PortInfo],
|
|
409
|
+
volumes: list[VolumeInfo],
|
|
410
|
+
env: dict[str, str],
|
|
411
|
+
env_files: list[str],
|
|
412
|
+
svc_networks: list[str],
|
|
413
|
+
depends_on: list[str],
|
|
414
|
+
healthcheck: Optional[str],
|
|
415
|
+
build_context: Optional[str],
|
|
416
|
+
replicas: int,
|
|
417
|
+
labels: dict[str, str],
|
|
418
|
+
) -> ServiceInfo:
|
|
419
|
+
return ServiceInfo(
|
|
420
|
+
name=name,
|
|
421
|
+
image=image,
|
|
422
|
+
ports=ports,
|
|
423
|
+
volumes=volumes,
|
|
424
|
+
env=env,
|
|
425
|
+
env_files=env_files,
|
|
426
|
+
networks=svc_networks,
|
|
427
|
+
depends_on=depends_on,
|
|
428
|
+
healthcheck=healthcheck,
|
|
429
|
+
restart=cfg.get("restart"),
|
|
430
|
+
command=self._parse_service_command(cfg),
|
|
431
|
+
build_context=build_context,
|
|
432
|
+
replicas=replicas,
|
|
433
|
+
labels=labels,
|
|
434
|
+
)
|
|
435
|
+
|
|
402
436
|
def _parse_service(self, name: str, cfg: dict,
|
|
403
437
|
env_context: dict[str, str],
|
|
404
438
|
spec: ParsedSpec) -> ServiceInfo:
|
|
@@ -413,18 +447,17 @@ class DockerComposeParser:
|
|
|
413
447
|
replicas = self._parse_service_replicas(cfg)
|
|
414
448
|
labels = self._parse_service_labels(cfg)
|
|
415
449
|
|
|
416
|
-
return
|
|
450
|
+
return self._build_service_info(
|
|
417
451
|
name=name,
|
|
452
|
+
cfg=cfg,
|
|
418
453
|
image=image,
|
|
419
454
|
ports=ports,
|
|
420
455
|
volumes=volumes,
|
|
421
456
|
env=env,
|
|
422
457
|
env_files=env_files,
|
|
423
|
-
|
|
458
|
+
svc_networks=svc_networks,
|
|
424
459
|
depends_on=depends_on,
|
|
425
460
|
healthcheck=healthcheck,
|
|
426
|
-
restart=cfg.get("restart"),
|
|
427
|
-
command=self._parse_service_command(cfg),
|
|
428
461
|
build_context=build_context,
|
|
429
462
|
replicas=replicas,
|
|
430
463
|
labels=labels,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""redeploy.models — shared data models (backward-compat re-export hub).
|
|
2
|
+
|
|
3
|
+
This package was refactored from the monolithic ``redeploy.models`` module
|
|
4
|
+
(R3, 0.2.53). All public names remain importable from the top-level
|
|
5
|
+
package for backward compatibility.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# ── core ─────────────────────────────────────────────────────────────────────
|
|
10
|
+
from .enums import (
|
|
11
|
+
ConflictSeverity,
|
|
12
|
+
DeployStrategy,
|
|
13
|
+
StepAction,
|
|
14
|
+
StepStatus,
|
|
15
|
+
_STRATEGY_ALIASES,
|
|
16
|
+
)
|
|
17
|
+
from .persisted import PersistedModel
|
|
18
|
+
|
|
19
|
+
# ── hardware ─────────────────────────────────────────────────────────────────
|
|
20
|
+
from .hardware import (
|
|
21
|
+
BacklightInfo,
|
|
22
|
+
DrmOutput,
|
|
23
|
+
HardwareDiagnostic,
|
|
24
|
+
HardwareInfo,
|
|
25
|
+
I2CBusInfo,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# ── infra state ─────────────────────────────────────────────────────────────
|
|
29
|
+
from .infra import (
|
|
30
|
+
AppHealthInfo,
|
|
31
|
+
ConflictInfo,
|
|
32
|
+
InfraState,
|
|
33
|
+
PortInfo,
|
|
34
|
+
RuntimeInfo,
|
|
35
|
+
ServiceInfo,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# ── pipeline ─────────────────────────────────────────────────────────────────
|
|
39
|
+
from .pipeline import Hook, PipelinePhase
|
|
40
|
+
|
|
41
|
+
# ── spec & target ───────────────────────────────────────────────────────────
|
|
42
|
+
from .spec import (
|
|
43
|
+
InfraSpec,
|
|
44
|
+
MigrationSpec,
|
|
45
|
+
TargetConfig,
|
|
46
|
+
_migrate_legacy_post_deploy,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# ── plan ─────────────────────────────────────────────────────────────────────
|
|
50
|
+
from .plan import MigrationPlan, MigrationStep
|
|
51
|
+
|
|
52
|
+
# ── manifest ─────────────────────────────────────────────────────────────────
|
|
53
|
+
from .manifest import EnvironmentConfig, ProjectManifest
|
|
54
|
+
|
|
55
|
+
# ── blueprint ────────────────────────────────────────────────────────────────
|
|
56
|
+
from .blueprint import (
|
|
57
|
+
BlueprintSource,
|
|
58
|
+
DeviceBlueprint,
|
|
59
|
+
HardwareRequirements,
|
|
60
|
+
ServicePort,
|
|
61
|
+
ServiceSpec,
|
|
62
|
+
VolumeMount,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# ── device registry / map ───────────────────────────────────────────────────
|
|
66
|
+
from .devices import (
|
|
67
|
+
DeployRecord,
|
|
68
|
+
DeviceMap,
|
|
69
|
+
DeviceRegistry,
|
|
70
|
+
KnownDevice,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
# enums
|
|
75
|
+
"ConflictSeverity",
|
|
76
|
+
"DeployStrategy",
|
|
77
|
+
"StepAction",
|
|
78
|
+
"StepStatus",
|
|
79
|
+
"_STRATEGY_ALIASES",
|
|
80
|
+
# persisted
|
|
81
|
+
"PersistedModel",
|
|
82
|
+
# hardware
|
|
83
|
+
"BacklightInfo",
|
|
84
|
+
"DrmOutput",
|
|
85
|
+
"HardwareDiagnostic",
|
|
86
|
+
"HardwareInfo",
|
|
87
|
+
"I2CBusInfo",
|
|
88
|
+
# infra
|
|
89
|
+
"AppHealthInfo",
|
|
90
|
+
"ConflictInfo",
|
|
91
|
+
"InfraState",
|
|
92
|
+
"PortInfo",
|
|
93
|
+
"RuntimeInfo",
|
|
94
|
+
"ServiceInfo",
|
|
95
|
+
# pipeline
|
|
96
|
+
"Hook",
|
|
97
|
+
"PipelinePhase",
|
|
98
|
+
# spec
|
|
99
|
+
"InfraSpec",
|
|
100
|
+
"MigrationSpec",
|
|
101
|
+
"TargetConfig",
|
|
102
|
+
"_migrate_legacy_post_deploy",
|
|
103
|
+
# plan
|
|
104
|
+
"MigrationPlan",
|
|
105
|
+
"MigrationStep",
|
|
106
|
+
# manifest
|
|
107
|
+
"EnvironmentConfig",
|
|
108
|
+
"ProjectManifest",
|
|
109
|
+
# blueprint
|
|
110
|
+
"BlueprintSource",
|
|
111
|
+
"DeviceBlueprint",
|
|
112
|
+
"HardwareRequirements",
|
|
113
|
+
"ServicePort",
|
|
114
|
+
"ServiceSpec",
|
|
115
|
+
"VolumeMount",
|
|
116
|
+
# devices
|
|
117
|
+
"DeployRecord",
|
|
118
|
+
"DeviceMap",
|
|
119
|
+
"DeviceRegistry",
|
|
120
|
+
"KnownDevice",
|
|
121
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Device blueprint models — portable deployment recipes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from .hardware import HardwareInfo
|
|
11
|
+
from .persisted import PersistedModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DEFAULT_BLUEPRINT_DIR = Path.home() / ".config" / "redeploy" / "blueprints"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ServicePort(BaseModel):
|
|
18
|
+
host: int
|
|
19
|
+
container: int
|
|
20
|
+
protocol: str = "tcp"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VolumeMount(BaseModel):
|
|
24
|
+
host: str
|
|
25
|
+
container: str
|
|
26
|
+
read_only: bool = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ServiceSpec(BaseModel):
|
|
30
|
+
name: str
|
|
31
|
+
image: str
|
|
32
|
+
platform: str = ""
|
|
33
|
+
ports: list[ServicePort] = Field(default_factory=list)
|
|
34
|
+
volumes: list[VolumeMount] = Field(default_factory=list)
|
|
35
|
+
env: dict[str, str] = Field(default_factory=dict)
|
|
36
|
+
command: Optional[str] = None
|
|
37
|
+
healthcheck: Optional[str] = None
|
|
38
|
+
depends_on: list[str] = Field(default_factory=list)
|
|
39
|
+
labels: dict[str, str] = Field(default_factory=dict)
|
|
40
|
+
restart: str = "unless-stopped"
|
|
41
|
+
network_mode: Optional[str] = None
|
|
42
|
+
privileged: bool = False
|
|
43
|
+
source_ref: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class HardwareRequirements(BaseModel):
|
|
47
|
+
arch: str = "linux/arm64"
|
|
48
|
+
min_ram_mb: int = 0
|
|
49
|
+
min_disk_gb: int = 0
|
|
50
|
+
display_type: Optional[str] = None
|
|
51
|
+
display_resolution: Optional[str] = None
|
|
52
|
+
i2c_required: bool = False
|
|
53
|
+
gpio_required: bool = False
|
|
54
|
+
features: list[str] = Field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class BlueprintSource(BaseModel):
|
|
58
|
+
device_id: Optional[str] = None
|
|
59
|
+
device_name: Optional[str] = None
|
|
60
|
+
migration_file: Optional[str] = None
|
|
61
|
+
markpact_files: list[str] = Field(default_factory=list)
|
|
62
|
+
compose_files: list[str] = Field(default_factory=list)
|
|
63
|
+
extracted_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DeviceBlueprint(PersistedModel):
|
|
67
|
+
"""Self-contained, portable deployment recipe."""
|
|
68
|
+
name: str
|
|
69
|
+
version: str = "0.0.0"
|
|
70
|
+
description: str = ""
|
|
71
|
+
tags: list[str] = Field(default_factory=list)
|
|
72
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
73
|
+
|
|
74
|
+
services: list[ServiceSpec] = Field(default_factory=list)
|
|
75
|
+
hardware: HardwareRequirements = Field(default_factory=HardwareRequirements)
|
|
76
|
+
app_url: Optional[str] = None
|
|
77
|
+
deploy_strategy: str = "podman_quadlet"
|
|
78
|
+
env_file: Optional[str] = None
|
|
79
|
+
notes: str = ""
|
|
80
|
+
source: BlueprintSource = Field(default_factory=BlueprintSource)
|
|
81
|
+
|
|
82
|
+
def service(self, name: str) -> Optional[ServiceSpec]:
|
|
83
|
+
return next((s for s in self.services if s.name == name), None)
|
|
84
|
+
|
|
85
|
+
def save(self, path: Optional[Path] = None) -> Path:
|
|
86
|
+
p = path or _DEFAULT_BLUEPRINT_DIR / f"{self.name}.blueprint.yaml"
|
|
87
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
p.write_text(self.to_yaml())
|
|
89
|
+
return p
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def list_saved(cls) -> list[Path]:
|
|
93
|
+
if not _DEFAULT_BLUEPRINT_DIR.exists():
|
|
94
|
+
return []
|
|
95
|
+
return sorted(_DEFAULT_BLUEPRINT_DIR.glob("*.blueprint.yaml"))
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Device registry and device-map models."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
10
|
+
|
|
11
|
+
from .hardware import HardwareInfo
|
|
12
|
+
from .infra import InfraState
|
|
13
|
+
from .persisted import PersistedModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_DEFAULT_DEVICE_MAP_DIR = Path.home() / ".config" / "redeploy" / "device-maps"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DeployRecord(BaseModel):
|
|
20
|
+
"""Single deployment event recorded for a device."""
|
|
21
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
22
|
+
spec_name: str = ""
|
|
23
|
+
from_strategy: str = ""
|
|
24
|
+
to_strategy: str = ""
|
|
25
|
+
version: str = ""
|
|
26
|
+
ok: bool = True
|
|
27
|
+
steps_total: int = 0
|
|
28
|
+
steps_ok: int = 0
|
|
29
|
+
note: str = ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class KnownDevice(BaseModel):
|
|
33
|
+
"""Device known to redeploy — persisted in ~/.config/redeploy/devices.yaml."""
|
|
34
|
+
id: str
|
|
35
|
+
host: str = ""
|
|
36
|
+
name: str = ""
|
|
37
|
+
tags: list[str] = Field(default_factory=list)
|
|
38
|
+
strategy: str = "docker_full"
|
|
39
|
+
app: str = ""
|
|
40
|
+
domain: str = ""
|
|
41
|
+
remote_dir: str = ""
|
|
42
|
+
ssh_port: int = 22
|
|
43
|
+
ssh_key: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
ip: str = ""
|
|
46
|
+
mac: str = ""
|
|
47
|
+
hostname: str = ""
|
|
48
|
+
ssh_user: str = ""
|
|
49
|
+
last_seen: Optional[datetime] = None
|
|
50
|
+
last_ssh_ok: Optional[datetime] = None
|
|
51
|
+
source: str = "manual"
|
|
52
|
+
|
|
53
|
+
hardware: Optional[HardwareInfo] = None
|
|
54
|
+
deploys: list[DeployRecord] = Field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def last_deploy(self) -> Optional[DeployRecord]:
|
|
58
|
+
return self.deploys[-1] if self.deploys else None
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def is_reachable(self) -> bool:
|
|
62
|
+
if self.last_seen is None:
|
|
63
|
+
return False
|
|
64
|
+
return (datetime.now(timezone.utc) - self.last_seen).total_seconds() < 300
|
|
65
|
+
|
|
66
|
+
def record_deploy(self, record: DeployRecord) -> None:
|
|
67
|
+
self.deploys.append(record)
|
|
68
|
+
self.deploys = self.deploys[-50:]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DeviceMap(PersistedModel):
|
|
72
|
+
"""Full, persisted snapshot of a device: identity + InfraState + HardwareInfo."""
|
|
73
|
+
id: str
|
|
74
|
+
host: str = ""
|
|
75
|
+
name: str = ""
|
|
76
|
+
tags: list[str] = Field(default_factory=list)
|
|
77
|
+
scanned_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
78
|
+
|
|
79
|
+
hardware: Optional[HardwareInfo] = None
|
|
80
|
+
infra: Optional[InfraState] = None
|
|
81
|
+
issues: list[dict] = Field(default_factory=list)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def has_errors(self) -> bool:
|
|
85
|
+
return any(i.get("severity") in ("error", "critical") for i in self.issues)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def display_summary(self) -> str:
|
|
89
|
+
if not self.hardware:
|
|
90
|
+
return "no hardware data"
|
|
91
|
+
hw = self.hardware
|
|
92
|
+
dsi = next((o for o in hw.drm_outputs if "DSI" in o.name), None)
|
|
93
|
+
if dsi:
|
|
94
|
+
mode = dsi.modes[0] if dsi.modes else "?"
|
|
95
|
+
bl = hw.backlights[0].brightness if hw.backlights else "?"
|
|
96
|
+
return f"DSI {dsi.connector} {dsi.status} {mode} backlight={bl}"
|
|
97
|
+
return "no DSI"
|
|
98
|
+
|
|
99
|
+
def save(self, path: Optional[Path] = None) -> Path:
|
|
100
|
+
p = path or _DEFAULT_DEVICE_MAP_DIR / f"{self.id.replace('/', '_').replace('@', '_at_')}.yaml"
|
|
101
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
p.write_text(self.to_yaml())
|
|
103
|
+
return p
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def load_for(cls, device_id: str) -> Optional[DeviceMap]:
|
|
107
|
+
p = _DEFAULT_DEVICE_MAP_DIR / f"{device_id.replace('/', '_').replace('@', '_at_')}.yaml"
|
|
108
|
+
if p.exists():
|
|
109
|
+
return cls.load(p)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def list_saved(cls) -> list[Path]:
|
|
114
|
+
if not _DEFAULT_DEVICE_MAP_DIR.exists():
|
|
115
|
+
return []
|
|
116
|
+
return sorted(_DEFAULT_DEVICE_MAP_DIR.glob("*.yaml"))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DeviceRegistry(BaseModel):
|
|
120
|
+
"""Persistent device registry — stored at ~/.config/redeploy/devices.yaml."""
|
|
121
|
+
devices: list[KnownDevice] = Field(default_factory=list)
|
|
122
|
+
_path: Optional[Path] = PrivateAttr(default=None)
|
|
123
|
+
|
|
124
|
+
def get(self, device_id: str) -> Optional[KnownDevice]:
|
|
125
|
+
return next((d for d in self.devices if d.id == device_id), None)
|
|
126
|
+
|
|
127
|
+
def upsert(self, device: KnownDevice) -> None:
|
|
128
|
+
for i, d in enumerate(self.devices):
|
|
129
|
+
if d.id == device.id:
|
|
130
|
+
self.devices[i] = device
|
|
131
|
+
return
|
|
132
|
+
self.devices.append(device)
|
|
133
|
+
|
|
134
|
+
def remove(self, device_id: str) -> bool:
|
|
135
|
+
before = len(self.devices)
|
|
136
|
+
self.devices = [d for d in self.devices if d.id != device_id]
|
|
137
|
+
return len(self.devices) < before
|
|
138
|
+
|
|
139
|
+
def by_tag(self, tag: str) -> list[KnownDevice]:
|
|
140
|
+
return [d for d in self.devices if tag in d.tags]
|
|
141
|
+
|
|
142
|
+
def by_strategy(self, strategy: str) -> list[KnownDevice]:
|
|
143
|
+
return [d for d in self.devices if d.strategy == strategy]
|
|
144
|
+
|
|
145
|
+
def reachable(self) -> list[KnownDevice]:
|
|
146
|
+
return [d for d in self.devices if d.is_reachable]
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def default_path(cls) -> Path:
|
|
150
|
+
return Path.home() / ".config" / "redeploy" / "devices.yaml"
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def load(cls, path: Optional[Path] = None) -> DeviceRegistry:
|
|
154
|
+
p = path or cls.default_path()
|
|
155
|
+
if not p.exists():
|
|
156
|
+
return cls()
|
|
157
|
+
raw = yaml.safe_load(p.read_text()) or {}
|
|
158
|
+
devices = [KnownDevice(**d) for d in raw.get("devices", [])]
|
|
159
|
+
reg = cls(devices=devices)
|
|
160
|
+
reg._path = p
|
|
161
|
+
return reg
|
|
162
|
+
|
|
163
|
+
def save(self, path: Optional[Path] = None) -> None:
|
|
164
|
+
p = path or self._path or self.default_path()
|
|
165
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
data = {"devices": [d.model_dump(mode="json") for d in self.devices]}
|
|
167
|
+
tmp = p.with_suffix(".tmp")
|
|
168
|
+
tmp.write_text(yaml.dump(data, allow_unicode=True, sort_keys=False))
|
|
169
|
+
tmp.chmod(0o600)
|
|
170
|
+
tmp.replace(p)
|