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.
Files changed (179) hide show
  1. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/PKG-INFO +1 -1
  2. easyrunner_cli-0.0.8.dev130/easyrunner/tests/test_cli_service_integration.py +348 -0
  3. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/pyproject.toml +1 -1
  4. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/app_sub_command.py +28 -32
  5. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/secret_store/keyring_secret_store.py +7 -7
  6. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/link_sub_command.py +16 -3
  7. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/servers_sub_command.py +85 -72
  8. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/README.md +0 -0
  9. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/configuration.template.yml +0 -0
  10. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/configuration.yml +0 -0
  11. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/local-dns-setup.sh +0 -0
  12. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/notification.txt +0 -0
  13. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/authelia/users_database.yaml +0 -0
  14. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/caddy/Caddyfile +0 -0
  15. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/caddy/Caddyfile with authcrunch +0 -0
  16. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/caddy/Caddyfile with ldap +0 -0
  17. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/docker-compose/.env.dev +0 -0
  18. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/docker-compose/.env.prod +0 -0
  19. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.server-config/infra/docker-compose/docker-compose-host.yaml +0 -0
  20. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.templates/docker-compose/docker-compose-app.yaml +0 -0
  21. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/.templates/docker-compose/docker-compose-helloworld.yaml +0 -0
  22. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/README.md +0 -0
  23. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/__init__.py +0 -0
  24. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/easyrunner_build.py +0 -0
  25. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/poetry.lock +0 -0
  26. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/pyproject.toml +0 -0
  27. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/pytest.ini +0 -0
  28. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/__init__.py +0 -0
  29. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/cloud_provider_base.py +0 -0
  30. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/cloud_providers.py +0 -0
  31. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/cloud_providers/hetzner_provider.py +0 -0
  32. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/command_executor.py +0 -0
  33. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/command_executor_local.py +0 -0
  34. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/__init__.py +0 -0
  35. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/__init__.py +0 -0
  36. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/archive_commands.py +0 -0
  37. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/caddy_api_curl_commands.py +0 -0
  38. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/caddy_commands.py +0 -0
  39. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/command_base.py +0 -0
  40. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/curl_commands.py +0 -0
  41. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/dir_commands.py +0 -0
  42. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/docker_compose_commands.py +0 -0
  43. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/file_commands.py +0 -0
  44. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/git_commands.py +0 -0
  45. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ip_tables_commands.py +0 -0
  46. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ip_tables_persistent_commands.py +0 -0
  47. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/null_command.py +0 -0
  48. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/os_package_manager_commands.py +0 -0
  49. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/podman_commands.py +0 -0
  50. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ssh_agent_commands.py +0 -0
  51. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/ssh_keygen_commands.py +0 -0
  52. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/systemctl_commands.py +0 -0
  53. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/user_commands.py +0 -0
  54. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/base/utility_commands.py +0 -0
  55. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/runnable_command_string.py +0 -0
  56. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/__init__.py +0 -0
  57. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/archive_commands_ubuntu.py +0 -0
  58. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/caddy_api_curl_commands_ubuntu.py +0 -0
  59. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/caddy_commands_container_ubuntu.py +0 -0
  60. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/curl_commands_ubuntu.py +0 -0
  61. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/dir_commands_ubuntu.py +0 -0
  62. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/docker_compose_commands_ubuntu.py +0 -0
  63. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/file_commands_ubuntu.py +0 -0
  64. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/git_commands_ubuntu.py +0 -0
  65. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ip_tables_commands_ubuntu.py +0 -0
  66. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ip_tables_persistent_commands_ubuntu.py +0 -0
  67. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/os_package_manager_commands_ubuntu.py +0 -0
  68. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/podman_commands_ubuntu.py +0 -0
  69. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ssh_agent_commands_ubuntu.py +0 -0
  70. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/ssh_keygen_commands_ubuntu.py +0 -0
  71. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/systemctl_commands_ubuntu.py +0 -0
  72. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/user_commands_ubuntu.py +0 -0
  73. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/commands/ubuntu/utility_commands_ubuntu.py +0 -0
  74. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/format_utils.py +0 -0
  75. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/http_client.py +0 -0
  76. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/__init__.py +0 -0
  77. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/__init__.py +0 -0
  78. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/cloudflare_api_client.py +0 -0
  79. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/cloudflare_api_config.py +0 -0
  80. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/cloudflare/cloudflare_api_token_manager.py +0 -0
  81. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/__init__.py +0 -0
  82. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/github_device_flow.py +0 -0
  83. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/github_oauth_config.py +0 -0
  84. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/github/github_token_manager.py +0 -0
  85. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/hetzner/__init__.py +0 -0
  86. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/integrations/hetzner/hetzner_api_key_manager.py +0 -0
  87. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/known_host_ssh_keys.py +0 -0
  88. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/__init__.py +0 -0
  89. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_firewall_base.py +0 -0
  90. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_resource_api_base.py +0 -0
  91. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_resource_pulumi_base.py +0 -0
  92. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/cloud_virtual_machine_base.py +0 -0
  93. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/github/github_api_client.py +0 -0
  94. {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
  95. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/github/github_repo.py +0 -0
  96. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/__init__.py +0 -0
  97. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_firewall.py +0 -0
  98. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_firewall_rule.py +0 -0
  99. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_resource_factory.py +0 -0
  100. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_stack.py +0 -0
  101. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/cloud_resources/hetzner/hetzner_virtual_machine.py +0 -0
  102. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/__init__.py +0 -0
  103. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/caddy.py +0 -0
  104. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/directory.py +0 -0
  105. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/docker_compose.py +0 -0
  106. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/file.py +0 -0
  107. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/git_repo.py +0 -0
  108. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/host_server_ubuntu.py +0 -0
  109. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/ip_tables.py +0 -0
  110. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/os_package_manager.py +0 -0
  111. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/os_resource_base.py +0 -0
  112. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/podman.py +0 -0
  113. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/podman_network.py +0 -0
  114. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/ssh_agent.py +0 -0
  115. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/systemd_service.py +0 -0
  116. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/os_resources/user.py +0 -0
  117. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/resource_base.py +0 -0
  118. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/resources/web_security_scanner.py +0 -0
  119. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/__init__.py +0 -0
  120. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/app_service.py +0 -0
  121. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/deployment_service.py +0 -0
  122. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/license_service.py +0 -0
  123. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/server_service.py +0 -0
  124. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/services/service_result.py +0 -0
  125. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/ssh.py +0 -0
  126. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/ssh_key.py +0 -0
  127. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/__init__.py +0 -0
  128. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/__init__.py +0 -0
  129. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/app.py +0 -0
  130. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/database_dto_base.py +0 -0
  131. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/data_models/server.py +0 -0
  132. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/db_config.py +0 -0
  133. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/db_ctx.py +0 -0
  134. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/easyrunner_store.py +0 -0
  135. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/json_encoder.py +0 -0
  136. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/object_id.py +0 -0
  137. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/secret_store.py +0 -0
  138. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/store/uuid7.py +0 -0
  139. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/tool_paths.py +0 -0
  140. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/__init__.py +0 -0
  141. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/caddy/caddy_config.py +0 -0
  142. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/caddy/caddy_site.py +0 -0
  143. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/__init__.py +0 -0
  144. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_network.py +0 -0
  145. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_project.py +0 -0
  146. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_service.py +0 -0
  147. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/compose_project/compose_volume.py +0 -0
  148. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/cpu_arch_types.py +0 -0
  149. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/dir_info.py +0 -0
  150. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/dto_base.py +0 -0
  151. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/exec_result.py +0 -0
  152. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/file_info.py +0 -0
  153. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/json.py +0 -0
  154. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/jsonobject_to_dataclass.py +0 -0
  155. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/os_type.py +0 -0
  156. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/podman_network_driver.py +0 -0
  157. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/security_scan_result.py +0 -0
  158. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/ssh_key_type.py +0 -0
  159. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/source/types/vm_config.py +0 -0
  160. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/__init__.py +0 -0
  161. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/conftest.py +0 -0
  162. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_compose_to_quadlet.py +0 -0
  163. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_hello_world.py +0 -0
  164. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_secret_store.py +0 -0
  165. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_services.py +0 -0
  166. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/easyrunner/tests/test_user_resource.py +0 -0
  167. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/__init__.py +0 -0
  168. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/infrastructure_deps.py +0 -0
  169. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/__init__.py +0 -0
  170. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/cloudflare/__init__.py +0 -0
  171. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/github/__init__.py +0 -0
  172. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/hetzner/__init__.py +0 -0
  173. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/secret_store/__init__.py +0 -0
  174. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/integrations/secret_store/secret_store.py +0 -0
  175. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/license_sub_command.py +0 -0
  176. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/licensing/__init__.py +0 -0
  177. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/licensing/license_manager.py +0 -0
  178. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/main.py +0 -0
  179. {easyrunner_cli-0.0.8.dev128 → easyrunner_cli-0.0.8.dev130}/source/ssh_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyrunner_cli
3
- Version: 0.0.8.dev128
3
+ Version: 0.0.8.dev130
4
4
  Summary: EasyRunner CLI.
5
5
  License: Proprietary
6
6
  Author: Janaka Abeywardhana
@@ -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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "easyrunner_cli"
3
- version = "0.0.8.dev128"
3
+ version = "0.0.8.dev130"
4
4
  description = "EasyRunner CLI."
5
5
  authors = ["Janaka Abeywardhana <janaka@easyrunner.xyz>"]
6
6
  license = "Proprietary"
@@ -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
- with Ssh(
134
- hostname_or_ipv4=hostname_or_ipv4,
135
- username=ssh_config.username,
136
- key_filename=build_private_key_path(hostname_or_ipv4),
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
- ) as ssh_client:
140
- executor = CommandExecutor(ssh_client=ssh_client)
141
- host_server_instance = HostServerUbuntu(
142
- easyrunner_username=ssh_config.username,
143
- executor=executor,
144
- debug=AppSubCommand.debug,
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.warning(
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.warning(
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 = None
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.warning(
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.warning(
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.warning(
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.warning(
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
  )