easyrunner-cli 0.0.8.dev128__tar.gz → 0.0.8.dev130__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.
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/PKG-INFO +1 -1
- easyrunner_cli-0.0.8.dev130/easyrunner/tests/test_cli_service_integration.py +348 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/pyproject.toml +1 -1
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/app_sub_command.py +28 -32
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/secret_store/keyring_secret_store.py +7 -7
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/link_sub_command.py +16 -3
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/servers_sub_command.py +85 -72
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/README.md +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/configuration.template.yml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/configuration.yml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/local-dns-setup.sh +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/notification.txt +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/users_database.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/caddy/Caddyfile +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/caddy/Caddyfile with authcrunch +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/caddy/Caddyfile with ldap +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/docker-compose/.env.dev +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/docker-compose/.env.prod +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/docker-compose/docker-compose-host.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.templates/docker-compose/docker-compose-app.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.templates/docker-compose/docker-compose-helloworld.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/README.md +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/easyrunner_build.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/poetry.lock +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/pyproject.toml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/pytest.ini +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/cloud_provider_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/cloud_providers.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/hetzner_provider.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/command_executor.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/command_executor_local.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/archive_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/caddy_api_curl_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/caddy_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/command_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/curl_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/dir_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/docker_compose_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/file_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/git_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ip_tables_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ip_tables_persistent_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/null_command.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/os_package_manager_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/podman_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ssh_agent_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ssh_keygen_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/systemctl_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/user_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/utility_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/runnable_command_string.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/archive_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/caddy_api_curl_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/caddy_commands_container_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/curl_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/dir_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/docker_compose_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/file_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/git_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ip_tables_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ip_tables_persistent_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/os_package_manager_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/podman_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ssh_agent_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ssh_keygen_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/systemctl_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/user_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/utility_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/format_utils.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/http_client.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/cloudflare_api_client.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/cloudflare_api_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/cloudflare_api_token_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/github_device_flow.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/github_oauth_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/github_token_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/hetzner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/hetzner/hetzner_api_key_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/known_host_ssh_keys.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_firewall_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_resource_api_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_resource_pulumi_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_virtual_machine_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/github/github_api_client.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/github/github_api_client_dtos.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/github/github_repo.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_firewall.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_firewall_rule.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_resource_factory.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_stack.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_virtual_machine.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/caddy.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/directory.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/docker_compose.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/file.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/git_repo.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/host_server_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/ip_tables.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/os_package_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/os_resource_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/podman.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/podman_network.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/ssh_agent.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/systemd_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/user.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/resource_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/web_security_scanner.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/app_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/deployment_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/license_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/server_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/service_result.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/ssh.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/ssh_key.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/app.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/database_dto_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/server.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/db_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/db_ctx.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/easyrunner_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/json_encoder.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/object_id.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/uuid7.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/tool_paths.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/caddy/caddy_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/caddy/caddy_site.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_network.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_project.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_volume.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/cpu_arch_types.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/dir_info.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/dto_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/exec_result.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/file_info.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/json.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/jsonobject_to_dataclass.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/os_type.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/podman_network_driver.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/security_scan_result.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/ssh_key_type.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/vm_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/conftest.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_compose_to_quadlet.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_hello_world.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_services.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_user_resource.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/infrastructure_deps.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/cloudflare/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/github/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/hetzner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/secret_store/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/secret_store/secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/license_sub_command.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/licensing/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/licensing/license_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/main.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/ssh_config.py +0 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""CLI + Services Integration Tests.
|
|
2
|
+
|
|
3
|
+
Tests that CLI commands properly use the service layer and maintain
|
|
4
|
+
consistent behavior between CLI and service layer interfaces.
|
|
5
|
+
|
|
6
|
+
These tests verify:
|
|
7
|
+
- CLI ServerSubCommand uses ServerService
|
|
8
|
+
- CLI AppSubCommand uses AppService and DeploymentService
|
|
9
|
+
- End-to-end workflows work correctly
|
|
10
|
+
- Error handling is consistent across layers
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from unittest.mock import Mock
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
from easyrunner.source.services import (
|
|
19
|
+
AppService,
|
|
20
|
+
DeploymentService,
|
|
21
|
+
ServerService,
|
|
22
|
+
ServiceResult,
|
|
23
|
+
)
|
|
24
|
+
from easyrunner.source.store.data_models import App, Server
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def mock_store() -> Mock:
|
|
31
|
+
"""Create a mock EasyRunnerStore."""
|
|
32
|
+
store = Mock()
|
|
33
|
+
store.list_servers.return_value = []
|
|
34
|
+
store.add_server.return_value = None
|
|
35
|
+
store.update_server.return_value = None
|
|
36
|
+
# By default, return None for server lookups (server doesn't exist)
|
|
37
|
+
store.get_server_by_name.return_value = None
|
|
38
|
+
store.get_server_by_hostname_or_ip.return_value = None
|
|
39
|
+
return store
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def mock_secret_store() -> Mock:
|
|
44
|
+
"""Create a mock SecretStore."""
|
|
45
|
+
return Mock()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def server_service(mock_store: Mock) -> ServerService:
|
|
50
|
+
"""Create a ServerService with mocked store."""
|
|
51
|
+
return ServerService(store=mock_store)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.fixture
|
|
55
|
+
def app_service(mock_store: Mock) -> AppService:
|
|
56
|
+
"""Create an AppService with mocked store."""
|
|
57
|
+
return AppService(store=mock_store)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def deployment_service(
|
|
62
|
+
mock_store: Mock, mock_secret_store: Mock
|
|
63
|
+
) -> DeploymentService:
|
|
64
|
+
"""Create a DeploymentService with mocked dependencies."""
|
|
65
|
+
return DeploymentService(store=mock_store, secret_store=mock_secret_store)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestServerServiceUsage:
|
|
69
|
+
"""Test that ServerService is properly used for server operations."""
|
|
70
|
+
|
|
71
|
+
def test_server_service_add_server_called_from_cli(
|
|
72
|
+
self, server_service: ServerService
|
|
73
|
+
):
|
|
74
|
+
"""Test ServerService.add_server is called correctly."""
|
|
75
|
+
# Arrange
|
|
76
|
+
test_server_name = "test-server"
|
|
77
|
+
test_server_ip = "192.168.1.1"
|
|
78
|
+
|
|
79
|
+
# Act
|
|
80
|
+
result = server_service.add_server(
|
|
81
|
+
name=test_server_name, hostname_or_ip=test_server_ip
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Assert
|
|
85
|
+
assert result.success
|
|
86
|
+
assert result.data is not None
|
|
87
|
+
assert result.data.name == test_server_name
|
|
88
|
+
assert result.data.hostname_or_ip == test_server_ip
|
|
89
|
+
assert result.message == f"Server '{test_server_name}' added successfully"
|
|
90
|
+
|
|
91
|
+
def test_server_service_delete_server_called_from_cli(
|
|
92
|
+
self, server_service: ServerService, mock_store: Mock
|
|
93
|
+
):
|
|
94
|
+
"""Test ServerService.delete_server removes server correctly."""
|
|
95
|
+
# Arrange
|
|
96
|
+
test_server = Server(name="test-server", hostname_or_ip="192.168.1.1")
|
|
97
|
+
mock_store.get_server_by_name.return_value = test_server
|
|
98
|
+
|
|
99
|
+
# Act
|
|
100
|
+
result = server_service.delete_server(name=test_server.name)
|
|
101
|
+
|
|
102
|
+
# Assert
|
|
103
|
+
assert result.success
|
|
104
|
+
assert result.message == f"Server '{test_server.name}' deleted successfully"
|
|
105
|
+
mock_store.remove_server.assert_called_once_with(server_id=test_server.id)
|
|
106
|
+
|
|
107
|
+
def test_server_service_respects_uniqueness_constraints(
|
|
108
|
+
self, server_service: ServerService, mock_store: Mock
|
|
109
|
+
):
|
|
110
|
+
"""Test ServerService enforces name/address uniqueness."""
|
|
111
|
+
# Arrange
|
|
112
|
+
existing_server = Server(
|
|
113
|
+
name="existing", hostname_or_ip="192.168.1.1"
|
|
114
|
+
)
|
|
115
|
+
mock_store.get_server_by_name.return_value = existing_server
|
|
116
|
+
|
|
117
|
+
# Act - try to add server with duplicate name
|
|
118
|
+
result = server_service.add_server(
|
|
119
|
+
name="existing", hostname_or_ip="192.168.1.2"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Assert
|
|
123
|
+
assert not result.success
|
|
124
|
+
assert result.error_code == "DUPLICATE_NAME"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestAppServiceUsage:
|
|
128
|
+
"""Test that AppService is properly used for app operations."""
|
|
129
|
+
|
|
130
|
+
def test_app_service_validate_for_deployment(
|
|
131
|
+
self, app_service: AppService, mock_store: Mock
|
|
132
|
+
):
|
|
133
|
+
"""Test AppService validates app for deployment."""
|
|
134
|
+
# Arrange
|
|
135
|
+
test_server = Server(name="test-server", hostname_or_ip="192.168.1.1")
|
|
136
|
+
test_app = App(
|
|
137
|
+
name="test-app",
|
|
138
|
+
repo_url="https://github.com/user/repo.git",
|
|
139
|
+
custom_domain="app.example.com",
|
|
140
|
+
)
|
|
141
|
+
test_server.apps = [test_app]
|
|
142
|
+
mock_store.get_server_by_name.return_value = test_server
|
|
143
|
+
|
|
144
|
+
# Act
|
|
145
|
+
result = app_service.validate_app_for_deployment(
|
|
146
|
+
server_name="test-server", app_name="test-app"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Assert
|
|
150
|
+
assert result.success
|
|
151
|
+
assert result.data == test_app
|
|
152
|
+
assert (
|
|
153
|
+
result.message
|
|
154
|
+
== "App 'test-app' is ready for deployment"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def test_app_service_rejects_missing_custom_domain(
|
|
158
|
+
self, app_service: AppService, mock_store: Mock
|
|
159
|
+
):
|
|
160
|
+
"""Test AppService rejects app without custom domain."""
|
|
161
|
+
# Arrange
|
|
162
|
+
test_server = Server(name="test-server", hostname_or_ip="192.168.1.1")
|
|
163
|
+
test_app = App(
|
|
164
|
+
name="test-app",
|
|
165
|
+
repo_url="https://github.com/user/repo.git",
|
|
166
|
+
custom_domain=None,
|
|
167
|
+
)
|
|
168
|
+
test_server.apps = [test_app]
|
|
169
|
+
mock_store.get_server_by_name.return_value = test_server
|
|
170
|
+
|
|
171
|
+
# Act
|
|
172
|
+
result = app_service.validate_app_for_deployment(
|
|
173
|
+
server_name="test-server", app_name="test-app"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Assert
|
|
177
|
+
assert not result.success
|
|
178
|
+
assert result.error_code == "MISSING_CUSTOM_DOMAIN"
|
|
179
|
+
|
|
180
|
+
def test_app_service_rejects_missing_repo_url(
|
|
181
|
+
self, app_service: AppService, mock_store: Mock
|
|
182
|
+
):
|
|
183
|
+
"""Test AppService rejects app without repo URL."""
|
|
184
|
+
# Arrange
|
|
185
|
+
test_server = Server(name="test-server", hostname_or_ip="192.168.1.1")
|
|
186
|
+
test_app = App(
|
|
187
|
+
name="test-app",
|
|
188
|
+
repo_url="",
|
|
189
|
+
custom_domain="app.example.com",
|
|
190
|
+
)
|
|
191
|
+
test_server.apps = [test_app]
|
|
192
|
+
mock_store.get_server_by_name.return_value = test_server
|
|
193
|
+
|
|
194
|
+
# Act
|
|
195
|
+
result = app_service.validate_app_for_deployment(
|
|
196
|
+
server_name="test-server", app_name="test-app"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Assert
|
|
200
|
+
assert not result.success
|
|
201
|
+
assert result.error_code == "MISSING_REPO_URL"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class TestDeploymentServiceUsage:
|
|
205
|
+
"""Test that DeploymentService is properly used for deployments."""
|
|
206
|
+
|
|
207
|
+
def test_deployment_service_validates_app_before_deploy(
|
|
208
|
+
self, deployment_service: DeploymentService, mock_store: Mock
|
|
209
|
+
):
|
|
210
|
+
"""Test DeploymentService validates app before attempting deployment."""
|
|
211
|
+
# Arrange
|
|
212
|
+
mock_store.get_server_by_name.return_value = None
|
|
213
|
+
|
|
214
|
+
# Act
|
|
215
|
+
result = deployment_service.deploy_app(
|
|
216
|
+
server_name="nonexistent",
|
|
217
|
+
app_name="test-app",
|
|
218
|
+
ssh_username="easyrunner",
|
|
219
|
+
ssh_key_path="/path/to/key",
|
|
220
|
+
github_token="dummy_token",
|
|
221
|
+
debug=False,
|
|
222
|
+
silent=False,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Assert
|
|
226
|
+
assert not result.success
|
|
227
|
+
assert "not found" in result.message.lower()
|
|
228
|
+
|
|
229
|
+
def test_deployment_service_returns_structured_error(
|
|
230
|
+
self, deployment_service: DeploymentService, mock_store: Mock
|
|
231
|
+
):
|
|
232
|
+
"""Test DeploymentService returns structured error information."""
|
|
233
|
+
# Arrange
|
|
234
|
+
mock_store.get_server_by_name.return_value = None
|
|
235
|
+
|
|
236
|
+
# Act
|
|
237
|
+
result = deployment_service.deploy_app(
|
|
238
|
+
server_name="nonexistent",
|
|
239
|
+
app_name="test-app",
|
|
240
|
+
ssh_username="easyrunner",
|
|
241
|
+
ssh_key_path="/path/to/key",
|
|
242
|
+
debug=False,
|
|
243
|
+
silent=False,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Assert - verify ServiceResult structure
|
|
247
|
+
assert isinstance(result, ServiceResult)
|
|
248
|
+
assert result.error_code is not None
|
|
249
|
+
assert result.message is not None
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class TestCLIServiceConsistency:
|
|
253
|
+
"""Test that CLI and service layer behaviors are consistent."""
|
|
254
|
+
|
|
255
|
+
def test_service_result_success_field_matches_cli_expectations(
|
|
256
|
+
self, server_service: ServerService
|
|
257
|
+
):
|
|
258
|
+
"""Test ServiceResult.success matches CLI error handling expectations."""
|
|
259
|
+
# Arrange
|
|
260
|
+
test_server = Server(name="test-server", hostname_or_ip="192.168.1.1")
|
|
261
|
+
|
|
262
|
+
# Act
|
|
263
|
+
result = server_service.add_server(
|
|
264
|
+
name=test_server.name, hostname_or_ip=test_server.hostname_or_ip
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Assert - CLI checks 'if result.success:'
|
|
268
|
+
assert isinstance(result.success, bool)
|
|
269
|
+
assert hasattr(result, "message")
|
|
270
|
+
assert hasattr(result, "data")
|
|
271
|
+
assert hasattr(result, "error_code")
|
|
272
|
+
|
|
273
|
+
def test_service_result_contains_cli_required_fields(
|
|
274
|
+
self, server_service: ServerService, mock_store: Mock
|
|
275
|
+
):
|
|
276
|
+
"""Test ServiceResult has all fields CLI needs for error display."""
|
|
277
|
+
# Arrange
|
|
278
|
+
mock_store.get_server_by_name.return_value = Server(
|
|
279
|
+
name="existing", hostname_or_ip="192.168.1.1"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Act
|
|
283
|
+
result = server_service.add_server(
|
|
284
|
+
name="existing", hostname_or_ip="192.168.1.2"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Assert - CLI uses these fields for error messages
|
|
288
|
+
assert not result.success
|
|
289
|
+
assert result.error_code in ["DUPLICATE_NAME", "DUPLICATE_ADDRESS"]
|
|
290
|
+
assert "already exists" in result.message.lower()
|
|
291
|
+
if result.details:
|
|
292
|
+
assert "existing_server" in result.details
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class TestEndToEndWorkflows:
|
|
296
|
+
"""Test complete workflows from CLI through services to store."""
|
|
297
|
+
|
|
298
|
+
def test_add_server_workflow(
|
|
299
|
+
self, server_service: ServerService, mock_store: Mock
|
|
300
|
+
):
|
|
301
|
+
"""Test complete add server workflow."""
|
|
302
|
+
# Arrange
|
|
303
|
+
server_name = "production"
|
|
304
|
+
server_ip = "192.168.1.100"
|
|
305
|
+
|
|
306
|
+
# Act - add server
|
|
307
|
+
add_result = server_service.add_server(
|
|
308
|
+
name=server_name, hostname_or_ip=server_ip
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Assert
|
|
312
|
+
assert add_result.success
|
|
313
|
+
assert add_result.data is not None
|
|
314
|
+
assert add_result.data.name == server_name
|
|
315
|
+
assert add_result.data.hostname_or_ip == server_ip
|
|
316
|
+
mock_store.add_server.assert_called_once()
|
|
317
|
+
|
|
318
|
+
def test_add_and_validate_app_workflow(
|
|
319
|
+
self,
|
|
320
|
+
app_service: AppService,
|
|
321
|
+
mock_store: Mock,
|
|
322
|
+
):
|
|
323
|
+
"""Test complete add and validate app workflow."""
|
|
324
|
+
# Arrange
|
|
325
|
+
test_server = Server(name="prod", hostname_or_ip="192.168.1.1")
|
|
326
|
+
test_app = App(
|
|
327
|
+
name="webapp",
|
|
328
|
+
repo_url="https://github.com/user/webapp.git",
|
|
329
|
+
custom_domain="app.prod.com",
|
|
330
|
+
)
|
|
331
|
+
test_server.apps = [test_app]
|
|
332
|
+
mock_store.get_server_by_name.return_value = test_server
|
|
333
|
+
|
|
334
|
+
# Act - get app
|
|
335
|
+
get_result = app_service.get_app(server_name="prod", app_name="webapp")
|
|
336
|
+
|
|
337
|
+
# Assert - got app
|
|
338
|
+
assert get_result.success
|
|
339
|
+
assert get_result.data == test_app
|
|
340
|
+
|
|
341
|
+
# Act - validate for deployment
|
|
342
|
+
validate_result = app_service.validate_app_for_deployment(
|
|
343
|
+
server_name="prod", app_name="webapp"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Assert - app is valid
|
|
347
|
+
assert validate_result.success
|
|
348
|
+
assert validate_result.data == test_app
|
|
@@ -13,6 +13,7 @@ from easyrunner.source.commands.ubuntu.caddy_commands_container_ubuntu import (
|
|
|
13
13
|
CaddyCommandsContainerUbuntu,
|
|
14
14
|
)
|
|
15
15
|
from easyrunner.source.resources.os_resources import Caddy, HostServerUbuntu
|
|
16
|
+
from easyrunner.source.services import AppService, DeploymentService
|
|
16
17
|
from easyrunner.source.store.data_models.app import App
|
|
17
18
|
from easyrunner.source.store.data_models.server import Server
|
|
18
19
|
from easyrunner.source.store.easyrunner_store import EasyRunnerStore
|
|
@@ -97,26 +98,13 @@ class AppSubCommand:
|
|
|
97
98
|
) -> None:
|
|
98
99
|
|
|
99
100
|
try:
|
|
101
|
+
# Get server to validate it exists and get SSH key path
|
|
100
102
|
server: Server | None = AppSubCommand._store.get_server_by_name(
|
|
101
103
|
name=server_name
|
|
102
104
|
)
|
|
103
105
|
if not server:
|
|
104
106
|
AppSubCommand._print(f"Server with name '{server_name}' not found.")
|
|
105
107
|
return
|
|
106
|
-
app = next((app for app in server.apps if app.name == name), None)
|
|
107
|
-
if not app:
|
|
108
|
-
AppSubCommand._print(
|
|
109
|
-
f"App with name '{name}' not found on server '{server_name}'."
|
|
110
|
-
)
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
if not app.custom_domain:
|
|
114
|
-
AppSubCommand._print(
|
|
115
|
-
f"App with name '{name}' must have a custom domain name set because this is used to uniquely identify this app in configuration. The custom domain name must be unique amongst all the apps on each server."
|
|
116
|
-
)
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
hostname_or_ipv4: str = server.hostname_or_ip
|
|
120
108
|
|
|
121
109
|
# Initial deployment info
|
|
122
110
|
AppSubCommand._print("🚀 [bold blue]Starting deployment...[/bold blue]")
|
|
@@ -130,27 +118,26 @@ class AppSubCommand:
|
|
|
130
118
|
)
|
|
131
119
|
return
|
|
132
120
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
121
|
+
# Use DeploymentService to orchestrate deployment
|
|
122
|
+
deployment_service = DeploymentService(
|
|
123
|
+
store=AppSubCommand._store,
|
|
124
|
+
secret_store=KeyringSecretStore(),
|
|
125
|
+
)
|
|
126
|
+
result = deployment_service.deploy_app(
|
|
127
|
+
server_name=server_name,
|
|
128
|
+
app_name=name,
|
|
129
|
+
ssh_username=ssh_config.username,
|
|
130
|
+
ssh_key_path=build_private_key_path(server.hostname_or_ip),
|
|
131
|
+
github_token=github_access_token,
|
|
137
132
|
debug=AppSubCommand.debug,
|
|
138
133
|
silent=AppSubCommand.silent,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
silent=AppSubCommand.silent,
|
|
146
|
-
progress_callback=AppSubCommand._progress_callback,
|
|
147
|
-
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if result.success:
|
|
137
|
+
AppSubCommand._print(f"✅ {result.message}")
|
|
138
|
+
else:
|
|
139
|
+
AppSubCommand._print(f"❌ Deployment failed: {result.message}")
|
|
148
140
|
|
|
149
|
-
host_server_instance.deploy_app_flow_a(
|
|
150
|
-
repo_url=app.repo_url,
|
|
151
|
-
custom_app_domain_name=app.custom_domain,
|
|
152
|
-
github_access_token=github_access_token,
|
|
153
|
-
)
|
|
154
141
|
except Exception as e:
|
|
155
142
|
AppSubCommand._print(
|
|
156
143
|
f"[red]Oops, something went wrong while deploying the app:[/red] {str(e)}"
|
|
@@ -286,6 +273,14 @@ class AppSubCommand:
|
|
|
286
273
|
help="The HTTPS URL of the application repository. Currently only supports GitHub repositories.",
|
|
287
274
|
),
|
|
288
275
|
) -> None:
|
|
276
|
+
# Get server
|
|
277
|
+
app_service = AppService(store=AppSubCommand._store)
|
|
278
|
+
server_result = app_service.list_apps(server_name=server_name)
|
|
279
|
+
|
|
280
|
+
if not server_result.success:
|
|
281
|
+
echo(message=f"Server with name '{server_name}' not found.")
|
|
282
|
+
return
|
|
283
|
+
|
|
289
284
|
server: Server | None = AppSubCommand._store.get_server_by_name(
|
|
290
285
|
name=server_name
|
|
291
286
|
)
|
|
@@ -293,6 +288,7 @@ class AppSubCommand:
|
|
|
293
288
|
echo(message=f"Server with name '{server_name}' not found.")
|
|
294
289
|
return
|
|
295
290
|
|
|
291
|
+
# Check if app already exists
|
|
296
292
|
if not any(app.name == name for app in server.apps):
|
|
297
293
|
server.apps.append(
|
|
298
294
|
App(
|
|
@@ -171,13 +171,13 @@ class KeyringSecretStore(SecretStore):
|
|
|
171
171
|
)
|
|
172
172
|
return True
|
|
173
173
|
else:
|
|
174
|
-
logger.
|
|
174
|
+
logger.debug(
|
|
175
175
|
f"Failed to store secret (status: {status}), falling back to standard keyring"
|
|
176
176
|
)
|
|
177
177
|
return self._store_secret_standard(service, account, value)
|
|
178
178
|
|
|
179
179
|
except Exception as e:
|
|
180
|
-
logger.
|
|
180
|
+
logger.debug(
|
|
181
181
|
f"Failed to store secret securely: {e}, falling back to standard keyring"
|
|
182
182
|
)
|
|
183
183
|
return self._store_secret_standard(service, account, value)
|
|
@@ -210,7 +210,7 @@ class KeyringSecretStore(SecretStore):
|
|
|
210
210
|
kSecReturnData: True,
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
result =
|
|
213
|
+
result = []
|
|
214
214
|
status = SecItemCopyMatching(query, result)
|
|
215
215
|
|
|
216
216
|
if status == errSecSuccess and result:
|
|
@@ -224,13 +224,13 @@ class KeyringSecretStore(SecretStore):
|
|
|
224
224
|
logger.error("Authentication failed when retrieving secret")
|
|
225
225
|
return None
|
|
226
226
|
else:
|
|
227
|
-
logger.
|
|
227
|
+
logger.debug(
|
|
228
228
|
f"Failed to retrieve secret (status: {status}), falling back to standard keyring"
|
|
229
229
|
)
|
|
230
230
|
return self._get_secret_standard(service, account)
|
|
231
231
|
|
|
232
232
|
except Exception as e:
|
|
233
|
-
logger.
|
|
233
|
+
logger.debug(
|
|
234
234
|
f"Failed to retrieve secret securely: {e}, falling back to standard keyring"
|
|
235
235
|
)
|
|
236
236
|
return self._get_secret_standard(service, account)
|
|
@@ -264,13 +264,13 @@ class KeyringSecretStore(SecretStore):
|
|
|
264
264
|
logger.debug(f"Secret deleted from keyring: {service}/{account}")
|
|
265
265
|
return True
|
|
266
266
|
else:
|
|
267
|
-
logger.
|
|
267
|
+
logger.debug(
|
|
268
268
|
f"Failed to delete secret (status: {status}), falling back to standard keyring"
|
|
269
269
|
)
|
|
270
270
|
return self._delete_secret_standard(service, account)
|
|
271
271
|
|
|
272
272
|
except Exception as e:
|
|
273
|
-
logger.
|
|
273
|
+
logger.debug(
|
|
274
274
|
f"Failed to delete secret securely: {e}, falling back to standard keyring"
|
|
275
275
|
)
|
|
276
276
|
return self._delete_secret_standard(service, account)
|
|
@@ -438,9 +438,7 @@ class LinkSubCommand:
|
|
|
438
438
|
@staticmethod
|
|
439
439
|
def link_status() -> None:
|
|
440
440
|
"""Show link status for all services."""
|
|
441
|
-
LinkSubCommand._progress_callback(
|
|
442
|
-
"[bold blue]� Link Status[/bold blue]\n", "\n"
|
|
443
|
-
)
|
|
441
|
+
LinkSubCommand._progress_callback("[bold blue]Link Status[/bold blue]\n", "\n")
|
|
444
442
|
|
|
445
443
|
# Check GitHub
|
|
446
444
|
token_manager = GitHubTokenManager(secret_store=KeyringSecretStore())
|
|
@@ -481,6 +479,21 @@ class LinkSubCommand:
|
|
|
481
479
|
"Hetzner (default): [yellow]⚠️ Not linked[/yellow]", "\n"
|
|
482
480
|
)
|
|
483
481
|
|
|
482
|
+
# Check Cloudflare (default account)
|
|
483
|
+
api_token_manager = CloudflareApiTokenManager(
|
|
484
|
+
account_name="default", secret_store=KeyringSecretStore()
|
|
485
|
+
)
|
|
486
|
+
stored_api_token = api_token_manager.get_api_token()
|
|
487
|
+
|
|
488
|
+
if stored_api_token:
|
|
489
|
+
LinkSubCommand._progress_callback(
|
|
490
|
+
"Cloudflare (default): [green]✅ Linked[/green]", "\n"
|
|
491
|
+
)
|
|
492
|
+
else:
|
|
493
|
+
LinkSubCommand._progress_callback(
|
|
494
|
+
"Cloudflare (default): [yellow]⚠️ Not linked[/yellow]", "\n"
|
|
495
|
+
)
|
|
496
|
+
|
|
484
497
|
LinkSubCommand._progress_callback(
|
|
485
498
|
"\n💡 Use 'er link github' to authenticate with GitHub", "\n"
|
|
486
499
|
)
|