easyrunner-cli 0.0.8.dev128__tar.gz → 0.0.8.dev129__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.dev129}/PKG-INFO +1 -1
- easyrunner_cli-0.0.8.dev129/easyrunner/tests/test_cli_service_integration.py +348 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/pyproject.toml +1 -1
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/app_sub_command.py +28 -32
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/servers_sub_command.py +85 -72
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/README.md +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/authelia/configuration.template.yml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/authelia/configuration.yml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/authelia/local-dns-setup.sh +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/authelia/notification.txt +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/authelia/users_database.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/caddy/Caddyfile +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/caddy/Caddyfile with authcrunch +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/caddy/Caddyfile with ldap +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/docker-compose/.env.dev +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/docker-compose/.env.prod +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.server-config/infra/docker-compose/docker-compose-host.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.templates/docker-compose/docker-compose-app.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/.templates/docker-compose/docker-compose-helloworld.yaml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/README.md +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/easyrunner_build.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/poetry.lock +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/pyproject.toml +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/pytest.ini +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/cloud_providers/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/cloud_providers/cloud_provider_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/cloud_providers/cloud_providers.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/cloud_providers/hetzner_provider.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/command_executor.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/command_executor_local.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/archive_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/caddy_api_curl_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/caddy_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/command_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/curl_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/dir_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/docker_compose_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/file_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/git_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/ip_tables_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/ip_tables_persistent_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/null_command.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/os_package_manager_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/podman_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/ssh_agent_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/ssh_keygen_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/systemctl_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/user_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/base/utility_commands.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/runnable_command_string.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/archive_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/caddy_api_curl_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/caddy_commands_container_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/curl_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/dir_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/docker_compose_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/file_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/git_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/ip_tables_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/ip_tables_persistent_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/os_package_manager_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/podman_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/ssh_agent_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/ssh_keygen_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/systemctl_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/user_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/commands/ubuntu/utility_commands_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/format_utils.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/http_client.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/cloudflare/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/cloudflare/cloudflare_api_client.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/cloudflare/cloudflare_api_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/cloudflare/cloudflare_api_token_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/github/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/github/github_device_flow.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/github/github_oauth_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/github/github_token_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/hetzner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/integrations/hetzner/hetzner_api_key_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/known_host_ssh_keys.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/cloud_firewall_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/cloud_resource_api_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/cloud_resource_pulumi_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/cloud_virtual_machine_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/github/github_api_client.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/github/github_api_client_dtos.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/github/github_repo.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/hetzner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_firewall.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_firewall_rule.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_resource_factory.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_stack.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_virtual_machine.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/caddy.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/directory.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/docker_compose.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/file.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/git_repo.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/host_server_ubuntu.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/ip_tables.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/os_package_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/os_resource_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/podman.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/podman_network.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/ssh_agent.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/systemd_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/os_resources/user.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/resource_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/resources/web_security_scanner.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/services/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/services/app_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/services/deployment_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/services/license_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/services/server_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/services/service_result.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/ssh.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/ssh_key.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/data_models/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/data_models/app.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/data_models/database_dto_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/data_models/server.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/db_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/db_ctx.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/easyrunner_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/json_encoder.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/object_id.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/store/uuid7.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/tool_paths.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/caddy/caddy_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/caddy/caddy_site.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/compose_project/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/compose_project/compose_network.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/compose_project/compose_project.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/compose_project/compose_service.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/compose_project/compose_volume.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/cpu_arch_types.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/dir_info.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/dto_base.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/exec_result.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/file_info.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/json.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/jsonobject_to_dataclass.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/os_type.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/podman_network_driver.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/security_scan_result.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/ssh_key_type.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/source/types/vm_config.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/conftest.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/test_compose_to_quadlet.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/test_hello_world.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/test_secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/test_services.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/easyrunner/tests/test_user_resource.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/infrastructure_deps.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/cloudflare/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/github/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/hetzner/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/secret_store/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/secret_store/keyring_secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/integrations/secret_store/secret_store.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/license_sub_command.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/licensing/__init__.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/licensing/license_manager.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/link_sub_command.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/source/main.py +0 -0
- {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev129}/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(
|
|
@@ -18,6 +18,7 @@ from easyrunner.source.known_host_ssh_keys import KNOWN_HOST_SSH_KEYS
|
|
|
18
18
|
from easyrunner.source.resources.cloud_resources.hetzner import HetznerStack
|
|
19
19
|
from easyrunner.source.resources.os_resources import HostServerUbuntu
|
|
20
20
|
from easyrunner.source.resources.web_security_scanner import WebSecurityScanner
|
|
21
|
+
from easyrunner.source.services import LicenseService, ServerService
|
|
21
22
|
from easyrunner.source.ssh import with_ssh_retry
|
|
22
23
|
from easyrunner.source.store import EasyRunnerStore
|
|
23
24
|
from easyrunner.source.store.data_models import Server
|
|
@@ -82,43 +83,48 @@ class ServerSubCommand:
|
|
|
82
83
|
|
|
83
84
|
Displays error message to user if limit is exceeded.
|
|
84
85
|
"""
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
ServerSubCommand.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
86
|
+
# Use LicenseService to check server quota
|
|
87
|
+
license_service = LicenseService(store=ServerSubCommand._store)
|
|
88
|
+
limit_result = license_service.can_add_server()
|
|
89
|
+
|
|
90
|
+
# If there's an error with license check, use LicenseManager as fallback
|
|
91
|
+
if not limit_result.success:
|
|
92
|
+
manager = LicenseManager()
|
|
93
|
+
license_info = manager.get_license_info()
|
|
94
|
+
|
|
95
|
+
if license_info is None:
|
|
96
|
+
# This shouldn't happen as main.py checks license on startup
|
|
97
|
+
# But handle it gracefully just in case
|
|
98
|
+
ServerSubCommand._print(
|
|
99
|
+
"[red]Error:[/red] No valid license found. Run [cyan]er license status[/cyan] for details."
|
|
100
|
+
)
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
# Get current server count
|
|
104
|
+
current_servers = ServerSubCommand._store.list_servers()
|
|
105
|
+
current_count = len(current_servers)
|
|
106
|
+
|
|
107
|
+
# Check if we're at or over the limit
|
|
108
|
+
if current_count >= license_info.server_limit:
|
|
109
|
+
ServerSubCommand._print()
|
|
110
|
+
ServerSubCommand._print("[red]✗ Server limit reached[/red]")
|
|
111
|
+
ServerSubCommand._print()
|
|
112
|
+
ServerSubCommand._print(
|
|
113
|
+
f"Your license allows [bold]{license_info.server_limit}[/bold] server(s)."
|
|
114
|
+
)
|
|
115
|
+
ServerSubCommand._print(
|
|
116
|
+
f"You currently have [bold]{current_count}[/bold] server(s) registered."
|
|
117
|
+
)
|
|
118
|
+
ServerSubCommand._print()
|
|
119
|
+
ServerSubCommand._print("To add more servers:")
|
|
120
|
+
ServerSubCommand._print(
|
|
121
|
+
" • Remove an existing server: [cyan]er server remove <name>[/cyan]"
|
|
122
|
+
)
|
|
123
|
+
ServerSubCommand._print(
|
|
124
|
+
" • Upgrade your license at [link]https://easyrunner.xyz[/link]"
|
|
125
|
+
)
|
|
126
|
+
ServerSubCommand._print()
|
|
127
|
+
return False
|
|
122
128
|
|
|
123
129
|
return True
|
|
124
130
|
|
|
@@ -189,38 +195,34 @@ class ServerSubCommand:
|
|
|
189
195
|
Returns:
|
|
190
196
|
Server object if successfully added, None if validation failed
|
|
191
197
|
"""
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
# Use ServerService to add server with validation
|
|
199
|
+
server_service = ServerService(store=ServerSubCommand._store)
|
|
200
|
+
result = server_service.add_server(name=name, hostname_or_ip=hostname_or_ip)
|
|
201
|
+
|
|
202
|
+
if not result.success:
|
|
203
|
+
# Display appropriate error message based on error code
|
|
204
|
+
if result.error_code == "DUPLICATE_NAME":
|
|
205
|
+
existing_server = result.details.get("existing_server") if result.details else name
|
|
206
|
+
ServerSubCommand._print(
|
|
207
|
+
f"⚠️ Server with the name '[bold yellow]{existing_server}[/bold yellow]' already exists in EasyRunner."
|
|
208
|
+
)
|
|
209
|
+
elif result.error_code == "DUPLICATE_ADDRESS":
|
|
210
|
+
existing_server = result.details.get("existing_server") if result.details else hostname_or_ip
|
|
211
|
+
ServerSubCommand._print(
|
|
212
|
+
f"⚠️ Server with the address '[bold yellow]{existing_server}[/bold yellow]' already exists in EasyRunner."
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
ServerSubCommand._print(f"⚠️ {result.message}")
|
|
200
216
|
return None
|
|
201
217
|
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
hostname_or_ip=hostname_or_ip
|
|
206
|
-
)
|
|
207
|
-
)
|
|
208
|
-
if server_address_exists:
|
|
218
|
+
# Display success message
|
|
219
|
+
server = result.data
|
|
220
|
+
if server:
|
|
209
221
|
ServerSubCommand._print(
|
|
210
|
-
f"
|
|
222
|
+
f"✅ Server [bold green]{server.name}[/bold green]([italic cyan]{server.hostname_or_ip}[/italic cyan]) added to EasyRunner."
|
|
211
223
|
)
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
# Create and persist the server
|
|
215
|
-
server: Server = Server(
|
|
216
|
-
name=name,
|
|
217
|
-
hostname_or_ip=hostname_or_ip,
|
|
218
|
-
)
|
|
219
|
-
ServerSubCommand._store.add_server(server=server)
|
|
220
|
-
ServerSubCommand._print(
|
|
221
|
-
f"✅ Server [bold green]{server.name}[/bold green]([italic cyan]{server.hostname_or_ip}[/italic cyan]) added to EasyRunner."
|
|
222
|
-
)
|
|
223
|
-
return server
|
|
224
|
+
return server
|
|
225
|
+
return None
|
|
224
226
|
|
|
225
227
|
@staticmethod
|
|
226
228
|
def _cancel_stack(name: str, cloud_provider_name: CloudProviders) -> None:
|
|
@@ -584,20 +586,31 @@ class ServerSubCommand:
|
|
|
584
586
|
)
|
|
585
587
|
@staticmethod
|
|
586
588
|
def remove_server(name: str = NAME_ARG) -> None:
|
|
587
|
-
|
|
588
|
-
|
|
589
|
+
# Use ServerService to get server
|
|
590
|
+
server_service = ServerService(store=ServerSubCommand._store)
|
|
591
|
+
server_result = server_service.get_server(name=name)
|
|
592
|
+
|
|
593
|
+
if not server_result.success:
|
|
594
|
+
echo(message=f"Server {name} not found in EasyRunner.")
|
|
595
|
+
return
|
|
596
|
+
|
|
597
|
+
server = server_result.data
|
|
598
|
+
if server is None:
|
|
589
599
|
echo(message=f"Server {name} not found in EasyRunner.")
|
|
590
600
|
return
|
|
591
601
|
|
|
592
602
|
# Remove SSH key files for the server
|
|
593
603
|
ServerSubCommand._cleanup_ssh_keys(server.hostname_or_ip)
|
|
594
604
|
|
|
595
|
-
#
|
|
596
|
-
|
|
605
|
+
# Use ServerService to remove the server
|
|
606
|
+
delete_result = server_service.delete_server(name=name)
|
|
597
607
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
608
|
+
if delete_result.success:
|
|
609
|
+
echo(
|
|
610
|
+
message=f"Server {server.name}({server.hostname_or_ip}) removed from EasyRunner."
|
|
611
|
+
)
|
|
612
|
+
else:
|
|
613
|
+
echo(message=f"Failed to remove server: {delete_result.message}")
|
|
601
614
|
|
|
602
615
|
@typer_app.command(
|
|
603
616
|
name="create",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|