remo-cli 2.0.0rc1__tar.gz → 2.0.0rc2__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.
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/PKG-INFO +1 -1
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/pyproject.toml +1 -1
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/shell.py +40 -13
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/ssh.py +33 -11
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/cli/test_shell.py +58 -5
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_ssh.py +44 -18
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/uv.lock +1 -1
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.analyze.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.checklist.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.clarify.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.constitution.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.implement.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.plan.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.specify.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.tasks.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.claude/commands/speckit.taskstoissues.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.devcontainer/devcontainer.json +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.dockerignore +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/dependabot.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/workflows/ci.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/workflows/provision.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/workflows/release.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.github/workflows/teardown.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.gitignore +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/memory/constitution.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/templates/plan-template.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/templates/spec-template.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/CLAUDE.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/CONTRIBUTING.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/Dockerfile +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/LICENSE +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/README.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/.ansible-lint +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/README.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/ansible.cfg +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/aws_configure.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/aws_resize.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/aws_site.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/aws_teardown.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/group_vars/all.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/hetzner_site.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/incus_configure.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/incus_provision.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/incus_resize.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/incus_site.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/incus_teardown.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/proxmox_site.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/requirements.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/docs/aws.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/docs/hetzner.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/docs/incus.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/docs/install.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/docs/proxmox.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/install.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/maverick.yaml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/project-menu.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/remo +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/run.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/__main__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/main.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/providers/aws.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/providers/hetzner.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/providers/incus.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/cli/providers/proxmox.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/config.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/output.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/core/version.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/models/host.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/conftest.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/integration/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/integration/orbstack.sh +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/cli/test_main.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_config.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_output.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/core/test_version.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/tests/unit/test_host_model.py +0 -0
|
@@ -33,7 +33,7 @@ def shell(
|
|
|
33
33
|
) -> None:
|
|
34
34
|
"""Connect to a remo environment (auto-detects or picker)."""
|
|
35
35
|
from remo_cli.core.ssh import check_remote_version, resolve_remo_host, shell_connect # noqa: PLC0415
|
|
36
|
-
from remo_cli.core.output import confirm, print_warning # noqa: PLC0415
|
|
36
|
+
from remo_cli.core.output import confirm, print_error, print_warning # noqa: PLC0415
|
|
37
37
|
from remo_cli.core.version import get_current_version, version_is_newer # noqa: PLC0415
|
|
38
38
|
from remo_cli.providers.aws import auto_start_aws_if_stopped # noqa: PLC0415
|
|
39
39
|
|
|
@@ -46,10 +46,20 @@ def shell(
|
|
|
46
46
|
if not no_update_check:
|
|
47
47
|
local_version = get_current_version()
|
|
48
48
|
if local_version != "unknown":
|
|
49
|
-
remote_version = check_remote_version(host)
|
|
49
|
+
remote_version, remote_err = check_remote_version(host)
|
|
50
50
|
|
|
51
51
|
should_update = False
|
|
52
|
-
if
|
|
52
|
+
if remote_err is not None:
|
|
53
|
+
# SSH itself failed — we can't tell whether the marker exists
|
|
54
|
+
# or what version is on the box. Don't prompt to update from
|
|
55
|
+
# an unknown baseline; surface the error so the user can fix
|
|
56
|
+
# it (DNS, host key, auth, ...) and re-run.
|
|
57
|
+
print_warning(
|
|
58
|
+
f"Could not check tools version on '{host.name}':\n"
|
|
59
|
+
f" {remote_err}\n"
|
|
60
|
+
f" Skipping update check; proceeding with connection."
|
|
61
|
+
)
|
|
62
|
+
elif remote_version is None:
|
|
53
63
|
# No marker file on remote
|
|
54
64
|
should_update = confirm(
|
|
55
65
|
f"Instance '{host.name}' has no version info. Update tools?",
|
|
@@ -71,29 +81,45 @@ def shell(
|
|
|
71
81
|
)
|
|
72
82
|
|
|
73
83
|
if should_update:
|
|
74
|
-
_run_provider_update(host)
|
|
84
|
+
rc = _run_provider_update(host)
|
|
85
|
+
if rc != 0:
|
|
86
|
+
# The playbook log has already been dumped, but the SSH
|
|
87
|
+
# connection (and any remote project picker) would scroll
|
|
88
|
+
# it offscreen immediately. Pause so the user can read it.
|
|
89
|
+
print_error(
|
|
90
|
+
f"Tools update for '{host.name}' failed "
|
|
91
|
+
f"(ansible-playbook exit code {rc})."
|
|
92
|
+
)
|
|
93
|
+
if not confirm(
|
|
94
|
+
"Connect anyway?",
|
|
95
|
+
default=False,
|
|
96
|
+
):
|
|
97
|
+
raise SystemExit(rc)
|
|
75
98
|
|
|
76
99
|
shell_connect(host, list(tunnels), no_open)
|
|
77
100
|
|
|
78
101
|
|
|
79
|
-
def _run_provider_update(host) ->
|
|
80
|
-
"""Run the appropriate provider update for the given host.
|
|
102
|
+
def _run_provider_update(host) -> int: # noqa: ANN001
|
|
103
|
+
"""Run the appropriate provider update for the given host.
|
|
104
|
+
|
|
105
|
+
Returns the provider update's exit code (0 on success).
|
|
106
|
+
"""
|
|
81
107
|
from remo_cli.core.output import print_info # noqa: PLC0415
|
|
82
108
|
|
|
83
109
|
print_info(f"Updating instance '{host.name}'...")
|
|
84
110
|
|
|
85
111
|
if host.type == "aws":
|
|
86
112
|
from remo_cli.providers.aws import update as aws_update # noqa: PLC0415
|
|
87
|
-
aws_update(name=host.name)
|
|
88
|
-
|
|
113
|
+
return aws_update(name=host.name)
|
|
114
|
+
if host.type == "hetzner":
|
|
89
115
|
from remo_cli.providers.hetzner import update as hetzner_update # noqa: PLC0415
|
|
90
|
-
hetzner_update(name=host.name)
|
|
91
|
-
|
|
116
|
+
return hetzner_update(name=host.name)
|
|
117
|
+
if host.type == "incus":
|
|
92
118
|
from remo_cli.providers.incus import update as incus_update # noqa: PLC0415
|
|
93
119
|
# Incus name in known_hosts is "host/container" — extract just the container name
|
|
94
120
|
container_name = host.name.split("/", maxsplit=1)[-1] if "/" in host.name else host.name
|
|
95
|
-
incus_update(name=container_name)
|
|
96
|
-
|
|
121
|
+
return incus_update(name=container_name)
|
|
122
|
+
if host.type == "proxmox":
|
|
97
123
|
from remo_cli.providers.proxmox import update as proxmox_update # noqa: PLC0415
|
|
98
124
|
# Proxmox name in known_hosts is "node/container".
|
|
99
125
|
# The proxmox SSH user is stored in the region slot (see providers.proxmox.create).
|
|
@@ -101,8 +127,9 @@ def _run_provider_update(host) -> None: # noqa: ANN001
|
|
|
101
127
|
if not container_name:
|
|
102
128
|
container_name = host.name
|
|
103
129
|
proxmox_host = ""
|
|
104
|
-
proxmox_update(
|
|
130
|
+
return proxmox_update(
|
|
105
131
|
name=container_name,
|
|
106
132
|
host=proxmox_host,
|
|
107
133
|
user=host.region or "",
|
|
108
134
|
)
|
|
135
|
+
return 0
|
|
@@ -257,24 +257,46 @@ def detect_timezone() -> str:
|
|
|
257
257
|
return ""
|
|
258
258
|
|
|
259
259
|
|
|
260
|
-
def check_remote_version(host: KnownHost) -> str | None:
|
|
260
|
+
def check_remote_version(host: KnownHost) -> tuple[str | None, str | None]:
|
|
261
261
|
"""Read the remo version marker from the remote instance.
|
|
262
262
|
|
|
263
|
-
Runs ``cat ~/.remo-version`` over SSH
|
|
264
|
-
|
|
263
|
+
Runs ``cat ~/.remo-version`` over SSH. Returns a ``(version, error)``
|
|
264
|
+
tuple where exactly one element is non-None:
|
|
265
|
+
|
|
266
|
+
* ``(version, None)`` — marker present and readable.
|
|
267
|
+
* ``(None, None)`` — SSH succeeded but the marker is missing/empty.
|
|
268
|
+
* ``(None, error)`` — SSH itself failed (DNS, host key, network,
|
|
269
|
+
timeout, ...); *error* is the underlying message so the caller can
|
|
270
|
+
surface it to the user.
|
|
265
271
|
"""
|
|
266
272
|
ssh_opts, ssh_target = build_ssh_opts(host)
|
|
267
|
-
cmd = ["ssh"] + ssh_opts + [
|
|
273
|
+
cmd = ["ssh"] + ssh_opts + [
|
|
274
|
+
"-o", "ConnectTimeout=10",
|
|
275
|
+
"-o", "BatchMode=yes",
|
|
276
|
+
ssh_target,
|
|
277
|
+
"cat ~/.remo-version 2>/dev/null",
|
|
278
|
+
]
|
|
268
279
|
|
|
269
280
|
try:
|
|
270
281
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
except subprocess.TimeoutExpired:
|
|
283
|
+
return None, "SSH timed out after 15s"
|
|
284
|
+
except OSError as e:
|
|
285
|
+
return None, f"SSH failed: {e}"
|
|
286
|
+
|
|
287
|
+
# ssh exits with 255 for its own failures (auth, host key, DNS,
|
|
288
|
+
# connection refused, ...). Any other non-zero exit code comes from
|
|
289
|
+
# the remote command — here, `cat` failing because the marker is
|
|
290
|
+
# absent — which we treat as "no marker" rather than an SSH error.
|
|
291
|
+
if result.returncode == 255:
|
|
292
|
+
stderr = result.stderr.strip()
|
|
293
|
+
return None, stderr or "SSH connection failed (exit code 255)"
|
|
294
|
+
|
|
295
|
+
version = result.stdout.strip()
|
|
296
|
+
if result.returncode == 0 and version:
|
|
297
|
+
return version, None
|
|
298
|
+
|
|
299
|
+
return None, None
|
|
278
300
|
|
|
279
301
|
|
|
280
302
|
def shell_connect(host: KnownHost, tunnels: list[str], no_open: bool) -> None:
|
|
@@ -52,7 +52,7 @@ class TestShellVersionCheck:
|
|
|
52
52
|
def test_equal_versions_proceeds_silently(self, runner, mocker):
|
|
53
53
|
"""When remote and local versions match, no prompt is shown."""
|
|
54
54
|
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.8.0")
|
|
55
|
-
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value="0.8.0")
|
|
55
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=("0.8.0", None))
|
|
56
56
|
mock_confirm = mocker.patch("remo_cli.core.output.confirm")
|
|
57
57
|
|
|
58
58
|
result = runner.invoke(shell, [])
|
|
@@ -64,7 +64,7 @@ class TestShellVersionCheck:
|
|
|
64
64
|
def test_remote_behind_prompts_update(self, runner, mocker):
|
|
65
65
|
"""When remote is behind local, user is prompted to update."""
|
|
66
66
|
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.9.0")
|
|
67
|
-
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value="0.8.0")
|
|
67
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=("0.8.0", None))
|
|
68
68
|
mock_confirm = mocker.patch("remo_cli.core.output.confirm", return_value=False)
|
|
69
69
|
|
|
70
70
|
result = runner.invoke(shell, [])
|
|
@@ -78,7 +78,7 @@ class TestShellVersionCheck:
|
|
|
78
78
|
def test_remote_behind_update_accepted(self, runner, mocker):
|
|
79
79
|
"""When user accepts update, provider update is called."""
|
|
80
80
|
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.9.0")
|
|
81
|
-
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value="0.8.0")
|
|
81
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=("0.8.0", None))
|
|
82
82
|
mocker.patch("remo_cli.core.output.confirm", return_value=True)
|
|
83
83
|
mock_update = mocker.patch("remo_cli.providers.hetzner.update", return_value=0)
|
|
84
84
|
|
|
@@ -87,11 +87,45 @@ class TestShellVersionCheck:
|
|
|
87
87
|
assert result.exit_code == 0
|
|
88
88
|
mock_update.assert_called_once_with(name="webserver")
|
|
89
89
|
|
|
90
|
+
@pytest.mark.usefixtures("_patch_shell_deps")
|
|
91
|
+
def test_update_failure_prompts_before_connect(self, runner, mocker):
|
|
92
|
+
"""When tools update fails, user is prompted to confirm connect."""
|
|
93
|
+
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.9.0")
|
|
94
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=("0.8.0", None))
|
|
95
|
+
# First confirm() = "Update?" → True; second = "Connect anyway?" → True
|
|
96
|
+
mock_confirm = mocker.patch(
|
|
97
|
+
"remo_cli.core.output.confirm", side_effect=[True, True]
|
|
98
|
+
)
|
|
99
|
+
mocker.patch("remo_cli.providers.hetzner.update", return_value=2)
|
|
100
|
+
mock_shell_connect = mocker.patch("remo_cli.core.ssh.shell_connect")
|
|
101
|
+
|
|
102
|
+
result = runner.invoke(shell, [])
|
|
103
|
+
|
|
104
|
+
assert result.exit_code == 0
|
|
105
|
+
assert mock_confirm.call_count == 2
|
|
106
|
+
assert "Connect anyway?" in mock_confirm.call_args_list[1][0][0]
|
|
107
|
+
mock_shell_connect.assert_called_once()
|
|
108
|
+
|
|
109
|
+
@pytest.mark.usefixtures("_patch_shell_deps")
|
|
110
|
+
def test_update_failure_decline_aborts(self, runner, mocker):
|
|
111
|
+
"""When user declines after failed update, shell_connect is not called."""
|
|
112
|
+
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.9.0")
|
|
113
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=("0.8.0", None))
|
|
114
|
+
# First confirm() = "Update?" → True; second = "Connect anyway?" → False
|
|
115
|
+
mocker.patch("remo_cli.core.output.confirm", side_effect=[True, False])
|
|
116
|
+
mocker.patch("remo_cli.providers.hetzner.update", return_value=2)
|
|
117
|
+
mock_shell_connect = mocker.patch("remo_cli.core.ssh.shell_connect")
|
|
118
|
+
|
|
119
|
+
result = runner.invoke(shell, [])
|
|
120
|
+
|
|
121
|
+
assert result.exit_code == 2
|
|
122
|
+
mock_shell_connect.assert_not_called()
|
|
123
|
+
|
|
90
124
|
@pytest.mark.usefixtures("_patch_shell_deps")
|
|
91
125
|
def test_remote_ahead_shows_warning(self, runner, mocker):
|
|
92
126
|
"""When remote is ahead of local, a warning is shown."""
|
|
93
127
|
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.8.0")
|
|
94
|
-
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value="0.9.0")
|
|
128
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=("0.9.0", None))
|
|
95
129
|
mock_confirm = mocker.patch("remo_cli.core.output.confirm")
|
|
96
130
|
|
|
97
131
|
result = runner.invoke(shell, [])
|
|
@@ -105,7 +139,7 @@ class TestShellVersionCheck:
|
|
|
105
139
|
def test_no_marker_prompts_update(self, runner, mocker):
|
|
106
140
|
"""When remote has no version marker, user is prompted to update."""
|
|
107
141
|
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.8.0")
|
|
108
|
-
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=None)
|
|
142
|
+
mocker.patch("remo_cli.core.ssh.check_remote_version", return_value=(None, None))
|
|
109
143
|
mock_confirm = mocker.patch("remo_cli.core.output.confirm", return_value=False)
|
|
110
144
|
|
|
111
145
|
result = runner.invoke(shell, [])
|
|
@@ -114,6 +148,25 @@ class TestShellVersionCheck:
|
|
|
114
148
|
mock_confirm.assert_called_once()
|
|
115
149
|
assert "no version info" in mock_confirm.call_args[0][0]
|
|
116
150
|
|
|
151
|
+
@pytest.mark.usefixtures("_patch_shell_deps")
|
|
152
|
+
def test_ssh_error_skips_update_prompt(self, runner, mocker):
|
|
153
|
+
"""When SSH itself fails, the user is warned and not prompted to update."""
|
|
154
|
+
mocker.patch("remo_cli.core.version.get_current_version", return_value="0.8.0")
|
|
155
|
+
mocker.patch(
|
|
156
|
+
"remo_cli.core.ssh.check_remote_version",
|
|
157
|
+
return_value=(None, "Host key verification failed."),
|
|
158
|
+
)
|
|
159
|
+
mock_confirm = mocker.patch("remo_cli.core.output.confirm")
|
|
160
|
+
mock_shell_connect = mocker.patch("remo_cli.core.ssh.shell_connect")
|
|
161
|
+
|
|
162
|
+
result = runner.invoke(shell, [])
|
|
163
|
+
|
|
164
|
+
assert result.exit_code == 0
|
|
165
|
+
mock_confirm.assert_not_called()
|
|
166
|
+
assert "Could not check tools version" in result.output
|
|
167
|
+
assert "Host key verification failed." in result.output
|
|
168
|
+
mock_shell_connect.assert_called_once()
|
|
169
|
+
|
|
117
170
|
@pytest.mark.usefixtures("_patch_shell_deps")
|
|
118
171
|
def test_unknown_local_version_skips_check(self, runner, mocker):
|
|
119
172
|
"""When local version is unknown, skip the version check."""
|
|
@@ -395,51 +395,71 @@ class TestCheckRemoteVersion:
|
|
|
395
395
|
|
|
396
396
|
@pytest.mark.usefixtures("_suppress_tz")
|
|
397
397
|
def test_returns_version_on_success(self, hetzner_host, mocker):
|
|
398
|
-
"""Returns
|
|
398
|
+
"""Returns (version, None) when SSH succeeds and marker is present."""
|
|
399
399
|
mocker.patch("remo_cli.core.ssh.get_aws_region", return_value="us-west-2")
|
|
400
400
|
mock_run = mocker.patch(
|
|
401
401
|
"subprocess.run",
|
|
402
|
-
return_value=MagicMock(returncode=0, stdout="0.8.0rc1\n"),
|
|
402
|
+
return_value=MagicMock(returncode=0, stdout="0.8.0rc1\n", stderr=""),
|
|
403
403
|
)
|
|
404
404
|
|
|
405
405
|
from remo_cli.core.ssh import check_remote_version
|
|
406
406
|
|
|
407
|
-
|
|
408
|
-
assert result == "0.8.0rc1"
|
|
407
|
+
assert check_remote_version(hetzner_host) == ("0.8.0rc1", None)
|
|
409
408
|
|
|
410
409
|
# Verify SSH command includes cat ~/.remo-version
|
|
411
410
|
call_args = mock_run.call_args[0][0]
|
|
412
411
|
assert "cat ~/.remo-version 2>/dev/null" in call_args
|
|
412
|
+
# And uses BatchMode so it can't hang on host-key/auth prompts
|
|
413
|
+
assert "BatchMode=yes" in call_args
|
|
413
414
|
|
|
414
415
|
@pytest.mark.usefixtures("_suppress_tz")
|
|
415
|
-
def
|
|
416
|
-
"""Returns None when the remote
|
|
416
|
+
def test_returns_no_marker_on_missing_file(self, hetzner_host, mocker):
|
|
417
|
+
"""Returns (None, None) when the remote command failed (cat → marker missing)."""
|
|
417
418
|
mocker.patch("remo_cli.core.ssh.get_aws_region", return_value="us-west-2")
|
|
418
419
|
mocker.patch(
|
|
419
420
|
"subprocess.run",
|
|
420
|
-
return_value=MagicMock(returncode=1, stdout=""),
|
|
421
|
+
return_value=MagicMock(returncode=1, stdout="", stderr=""),
|
|
421
422
|
)
|
|
422
423
|
|
|
423
424
|
from remo_cli.core.ssh import check_remote_version
|
|
424
425
|
|
|
425
|
-
assert check_remote_version(hetzner_host)
|
|
426
|
+
assert check_remote_version(hetzner_host) == (None, None)
|
|
426
427
|
|
|
427
428
|
@pytest.mark.usefixtures("_suppress_tz")
|
|
428
|
-
def
|
|
429
|
-
"""Returns None when SSH succeeds but
|
|
429
|
+
def test_returns_no_marker_on_empty_output(self, hetzner_host, mocker):
|
|
430
|
+
"""Returns (None, None) when SSH succeeds but the marker is empty."""
|
|
430
431
|
mocker.patch("remo_cli.core.ssh.get_aws_region", return_value="us-west-2")
|
|
431
432
|
mocker.patch(
|
|
432
433
|
"subprocess.run",
|
|
433
|
-
return_value=MagicMock(returncode=0, stdout=""),
|
|
434
|
+
return_value=MagicMock(returncode=0, stdout="", stderr=""),
|
|
434
435
|
)
|
|
435
436
|
|
|
436
437
|
from remo_cli.core.ssh import check_remote_version
|
|
437
438
|
|
|
438
|
-
assert check_remote_version(hetzner_host)
|
|
439
|
+
assert check_remote_version(hetzner_host) == (None, None)
|
|
439
440
|
|
|
440
441
|
@pytest.mark.usefixtures("_suppress_tz")
|
|
441
|
-
def
|
|
442
|
-
"""Returns None when SSH
|
|
442
|
+
def test_returns_error_on_ssh_failure(self, hetzner_host, mocker):
|
|
443
|
+
"""Returns (None, stderr) when SSH itself fails (rc 255)."""
|
|
444
|
+
mocker.patch("remo_cli.core.ssh.get_aws_region", return_value="us-west-2")
|
|
445
|
+
mocker.patch(
|
|
446
|
+
"subprocess.run",
|
|
447
|
+
return_value=MagicMock(
|
|
448
|
+
returncode=255,
|
|
449
|
+
stdout="",
|
|
450
|
+
stderr="Host key verification failed.\n",
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
from remo_cli.core.ssh import check_remote_version
|
|
455
|
+
|
|
456
|
+
version, err = check_remote_version(hetzner_host)
|
|
457
|
+
assert version is None
|
|
458
|
+
assert err == "Host key verification failed."
|
|
459
|
+
|
|
460
|
+
@pytest.mark.usefixtures("_suppress_tz")
|
|
461
|
+
def test_returns_error_on_timeout(self, hetzner_host, mocker):
|
|
462
|
+
"""Returns (None, error) when SSH times out."""
|
|
443
463
|
mocker.patch("remo_cli.core.ssh.get_aws_region", return_value="us-west-2")
|
|
444
464
|
mocker.patch(
|
|
445
465
|
"subprocess.run",
|
|
@@ -448,14 +468,20 @@ class TestCheckRemoteVersion:
|
|
|
448
468
|
|
|
449
469
|
from remo_cli.core.ssh import check_remote_version
|
|
450
470
|
|
|
451
|
-
|
|
471
|
+
version, err = check_remote_version(hetzner_host)
|
|
472
|
+
assert version is None
|
|
473
|
+
assert err is not None
|
|
474
|
+
assert "timed out" in err.lower()
|
|
452
475
|
|
|
453
476
|
@pytest.mark.usefixtures("_suppress_tz")
|
|
454
|
-
def
|
|
455
|
-
"""Returns None when
|
|
477
|
+
def test_returns_error_on_os_error(self, hetzner_host, mocker):
|
|
478
|
+
"""Returns (None, error) when the ssh binary can't be invoked."""
|
|
456
479
|
mocker.patch("remo_cli.core.ssh.get_aws_region", return_value="us-west-2")
|
|
457
480
|
mocker.patch("subprocess.run", side_effect=OSError("No ssh"))
|
|
458
481
|
|
|
459
482
|
from remo_cli.core.ssh import check_remote_version
|
|
460
483
|
|
|
461
|
-
|
|
484
|
+
version, err = check_remote_version(hetzner_host)
|
|
485
|
+
assert version is None
|
|
486
|
+
assert err is not None
|
|
487
|
+
assert "No ssh" in err
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.maverick/checkpoints/feature/implementation_complete.json
RENAMED
|
File without changes
|
|
File without changes
|
{remo_cli-2.0.0rc1 → remo_cli-2.0.0rc2}/.maverick/checkpoints/feature/validation_complete.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|