opsmith-cli 0.3.1b0__tar.gz → 0.4.0b0__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 (148) hide show
  1. opsmith_cli-0.4.0b0/CONVENTIONS.md +35 -0
  2. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/PKG-INFO +1 -1
  3. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/agent.py +4 -4
  4. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/cloud_providers/aws.py +54 -22
  5. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/cloud_providers/base.py +3 -9
  6. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/cloud_providers/gcp.py +58 -22
  7. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/deployment_strategies/base.py +63 -59
  8. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/deployment_strategies/monolithic.py +43 -50
  9. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/infra_provisioners/ansible_provisioner.py +6 -3
  10. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/main.py +5 -37
  11. opsmith_cli-0.4.0b0/opsmith/templates/container_registry/aws/main.tf +22 -0
  12. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/container_registry/aws/variables.tf +6 -0
  13. opsmith_cli-0.4.0b0/opsmith/templates/container_registry/gcp/main.tf +26 -0
  14. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/container_registry/gcp/variables.tf +6 -0
  15. opsmith_cli-0.4.0b0/opsmith/templates/docker_build_push/aws/main.yml +73 -0
  16. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_build_push/gcp/main.yml +24 -12
  17. opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_deploy/aws/inventory.yml +14 -0
  18. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_deploy/aws/main.yml +21 -14
  19. opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_deploy/gcp/inventory.yml +8 -0
  20. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_deploy/gcp/main.yml +19 -14
  21. opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_run/aws/inventory.yml +14 -0
  22. {opsmith_cli-0.3.1b0/opsmith/templates/docker_compose_run/common → opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_run/aws}/main.yml +10 -3
  23. opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_run/gcp/inventory.yml +8 -0
  24. opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_run/gcp/main.yml +26 -0
  25. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/base.yml +8 -0
  26. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/services/backend_api.yml +1 -0
  27. opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_snippets/services/backend_worker.yml +4 -0
  28. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/services/full_stack.yml +1 -0
  29. opsmith_cli-0.4.0b0/opsmith/templates/fetch_remote_files/aws/inventory.yml +14 -0
  30. opsmith_cli-0.4.0b0/opsmith/templates/fetch_remote_files/gcp/inventory.yml +8 -0
  31. opsmith_cli-0.4.0b0/opsmith/templates/fetch_remote_files/gcp/main.yml +22 -0
  32. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine/aws/main.tf +260 -0
  33. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/virtual_machine/aws/outputs.tf +2 -2
  34. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/virtual_machine/aws/variables.tf +10 -0
  35. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine/gcp/main.tf +205 -0
  36. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine/gcp/outputs.tf +19 -0
  37. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine/gcp/variables.tf +51 -0
  38. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine_setup/aws/inventory.yml +14 -0
  39. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine_setup/aws/main.yml +233 -0
  40. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine_setup/gcp/inventory.yml +8 -0
  41. opsmith_cli-0.4.0b0/opsmith/templates/virtual_machine_setup/gcp/main.yml +90 -0
  42. opsmith_cli-0.4.0b0/opsmith/tests/__init__.py +0 -0
  43. opsmith_cli-0.4.0b0/opsmith/tests/test_service_detector.py +133 -0
  44. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/types.py +15 -3
  45. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/pyproject.toml +6 -1
  46. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/uv.lock +45 -1
  47. opsmith_cli-0.3.1b0/CONVENTIONS.md +0 -16
  48. opsmith_cli-0.3.1b0/opsmith/templates/container_registry/aws/main.tf +0 -12
  49. opsmith_cli-0.3.1b0/opsmith/templates/container_registry/gcp/main.tf +0 -11
  50. opsmith_cli-0.3.1b0/opsmith/templates/docker_build_push/aws/main.yml +0 -51
  51. opsmith_cli-0.3.1b0/opsmith/templates/docker_compose_snippets/services/backend_worker.yml +0 -3
  52. opsmith_cli-0.3.1b0/opsmith/templates/docker_setup/aws/main.yml +0 -36
  53. opsmith_cli-0.3.1b0/opsmith/templates/docker_setup/gcp/main.yml +0 -38
  54. opsmith_cli-0.3.1b0/opsmith/templates/virtual_machine/aws/main.tf +0 -136
  55. opsmith_cli-0.3.1b0/opsmith/templates/virtual_machine/gcp/main.tf +0 -74
  56. opsmith_cli-0.3.1b0/opsmith/templates/virtual_machine/gcp/outputs.tf +0 -14
  57. opsmith_cli-0.3.1b0/opsmith/templates/virtual_machine/gcp/variables.tf +0 -24
  58. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/.flake8 +0 -0
  59. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/.github/workflows/python-publish.yml +0 -0
  60. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/.gitignore +0 -0
  61. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/.pre-commit-config.yaml +0 -0
  62. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/LICENSE.txt +0 -0
  63. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/README.md +0 -0
  64. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/__init__.py +0 -0
  65. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/cloud_providers/__init__.py +0 -0
  66. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/constants.py +0 -0
  67. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/deployment_strategies/__init__.py +0 -0
  68. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/exceptions.py +0 -0
  69. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/git_repo.py +0 -0
  70. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/infra_provisioners/__init__.py +0 -0
  71. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/infra_provisioners/base_provisioner.py +0 -0
  72. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/infra_provisioners/terraform_provisioner.py +0 -0
  73. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/models.py +0 -0
  74. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/prompts.py +0 -0
  75. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/README.md +0 -0
  76. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/arduino-tags.scm +0 -0
  77. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/c-tags.scm +0 -0
  78. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/c_sharp-tags.scm +0 -0
  79. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/chatito-tags.scm +0 -0
  80. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/commonlisp-tags.scm +0 -0
  81. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/cpp-tags.scm +0 -0
  82. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/csharp-tags.scm +0 -0
  83. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/d-tags.scm +0 -0
  84. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/dart-tags.scm +0 -0
  85. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/elisp-tags.scm +0 -0
  86. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/elixir-tags.scm +0 -0
  87. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/elm-tags.scm +0 -0
  88. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/gleam-tags.scm +0 -0
  89. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/go-tags.scm +0 -0
  90. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/hcl-tags.scm +0 -0
  91. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/java-tags.scm +0 -0
  92. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/javascript-tags.scm +0 -0
  93. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/kotlin-tags.scm +0 -0
  94. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/lua-tags.scm +0 -0
  95. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/ocaml-tags.scm +0 -0
  96. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/ocaml_interface-tags.scm +0 -0
  97. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/php-tags.scm +0 -0
  98. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/pony-tags.scm +0 -0
  99. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/properties-tags.scm +0 -0
  100. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/python-tags.scm +0 -0
  101. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/ql-tags.scm +0 -0
  102. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/r-tags.scm +0 -0
  103. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/racket-tags.scm +0 -0
  104. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/ruby-tags.scm +0 -0
  105. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/rust-tags.scm +0 -0
  106. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/scala-tags.scm +0 -0
  107. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/solidity-tags.scm +0 -0
  108. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/swift-tags.scm +0 -0
  109. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/typescript-tags.scm +0 -0
  110. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/queries/tree-sitter-languages/udev-tags.scm +0 -0
  111. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/repo_map.py +0 -0
  112. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/service_detector.py +0 -0
  113. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/settings.py +0 -0
  114. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/cloud_storage_cleanup/aws/main.yml +0 -0
  115. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/cloud_storage_cleanup/gcp/main.yml +0 -0
  116. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/container_registry/aws/outputs.tf +0 -0
  117. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/container_registry/gcp/outputs.tf +0 -0
  118. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/elasticsearch.yml +0 -0
  119. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/kafka.yml +0 -0
  120. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/mongodb.yml +0 -0
  121. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/mysql.yml +0 -0
  122. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/postgresql.yml +0 -0
  123. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/rabbitmq.yml +0 -0
  124. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/redis.yml +0 -0
  125. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/traefik.yml +0 -0
  126. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/docker_compose_snippets/weaviate.yml +0 -0
  127. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/dockerfiles/python_backend_api +0 -0
  128. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/dockerfiles/python_backend_worker +0 -0
  129. {opsmith_cli-0.3.1b0/opsmith/templates/fetch_remote_files/common → opsmith_cli-0.4.0b0/opsmith/templates/fetch_remote_files/aws}/main.yml +0 -0
  130. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_bucket_cert/aws/main.tf +0 -0
  131. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_bucket_cert/aws/outputs.tf +0 -0
  132. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_bucket_cert/aws/variables.tf +0 -0
  133. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_bucket_cert/gcp/main.tf +0 -0
  134. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_bucket_cert/gcp/outputs.tf +0 -0
  135. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_bucket_cert/gcp/variables.tf +0 -0
  136. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_cdn/aws/main.tf +0 -0
  137. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_cdn/aws/outputs.tf +0 -0
  138. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_cdn/aws/variables.tf +0 -0
  139. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_cdn/gcp/main.tf +0 -0
  140. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_cdn/gcp/outputs.tf +0 -0
  141. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_cdn/gcp/variables.tf +0 -0
  142. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_deploy/aws/main.yml +0 -0
  143. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/templates/frontend_deploy/gcp/main.yml +0 -0
  144. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/opsmith/utils.py +0 -0
  145. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/resources/deploy_demo.gif +0 -0
  146. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/resources/deploy_demo.svg +0 -0
  147. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/resources/setup_demo.gif +0 -0
  148. {opsmith_cli-0.3.1b0 → opsmith_cli-0.4.0b0}/resources/setup_demo.svg +0 -0
@@ -0,0 +1,35 @@
1
+ # Engineering Conventions
2
+
3
+ ## Conventions
4
+ > When writing code follow these conventions
5
+
6
+ - Write simple verbose code over terse, compact, dense code
7
+ - Use types everywhere possible.
8
+ - Add logs where applicable so that debugging becomes easy
9
+ - Add docstring to functions that describe its use
10
+ - Add try/except blocks sparingly, only where it is essential
11
+ - Use pytest to write tests
12
+ - Add docstring to each test case that describes its flow clearly
13
+ - Tests should mock out external calls and command calls
14
+ - Do not assert on logs in tests as that is not a best practice
15
+
16
+ ## Project Structure
17
+
18
+ - `opsmith/` - Source code for this CLI tool
19
+ - `cloud_providers/` - Cloud provider implementations (e.g., AWS, GCP).
20
+ - `deployment_strategies/` - Application deployment strategies (e.g., monolithic).
21
+ - `infra_provisioners/` - Wrappers for infrastructure tools (e.g., Terraform, Ansible).
22
+ - `templates/` - Infrastructure-as-code templates (e.g., Terraform modules, Ansible Playbooks).
23
+ - `tests/` - Tests for all the components
24
+ - `agent.py` - AI agent logic.
25
+ - `exceptions.py` - Custom exceptions used by Opsmith
26
+ - `git_repo.py` - Connector for a git repo
27
+ - `main.py` - CLI command definitions (Typer).
28
+ - `models.py` - List of available LLMs
29
+ - `prompts.py` - Prompts used to interact with the LLM
30
+ - `repo_map.py` - Generates a ,ap of the files in the repository
31
+ - `service_detector.py` - Logic to detect services in a repository.
32
+ - `settings.py` - Configurations for Opsmith
33
+ - `types.py` - Core Pydantic data models for configuration and state.
34
+ - `utils.py` - Shared utility functions.
35
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opsmith-cli
3
- Version: 0.3.1b0
3
+ Version: 0.4.0b0
4
4
  Summary: Opsmith is an AI devops engineer in your terminal
5
5
  License-Expression: GPL-3.0-only
6
6
  License-File: LICENSE.txt
@@ -45,9 +45,9 @@ def build_agent(model_config: Type[BaseAiModel], instrument: bool = False) -> Ag
45
45
  )
46
46
 
47
47
  @agent.tool(retries=5)
48
- def read_file_content(ctx: RunContext[AgentDeps], filenames: List[str]) -> List[str]:
48
+ def read_repo_files(ctx: RunContext[AgentDeps], filenames: List[str]) -> List[str]:
49
49
  """
50
- Reads and returns the content of specified files from the repository.
50
+ Reads and returns the content of specified files from the repo.
51
51
  Use this to understand file structures, dependencies, or specific configurations.
52
52
  Provide the relative file paths from the repository root.
53
53
 
@@ -59,9 +59,9 @@ def build_agent(model_config: Type[BaseAiModel], instrument: bool = False) -> Ag
59
59
  A list of strings, where each string is the content of the corresponding file.
60
60
  The order of contents in the list matches the order of filenames in the input.
61
61
  """
62
- if is_duplicate_tool_call(ctx, "read_file_content"):
62
+ if is_duplicate_tool_call(ctx, "read_repo_files"):
63
63
  raise ModelRetry(
64
- "The tool 'read_file_content' has already been called with the exact same list of "
64
+ "The tool 'read_repo_files' has already been called with the exact same list of "
65
65
  "files in this conversation."
66
66
  )
67
67
 
@@ -1,7 +1,9 @@
1
+ import shutil
1
2
  from typing import Literal, Type
2
3
 
3
4
  import boto3
4
5
  import botocore.session
6
+ import inquirer
5
7
  from botocore.exceptions import ClientError, NoCredentialsError
6
8
  from pydantic import Field
7
9
 
@@ -13,11 +15,13 @@ from opsmith.cloud_providers.base import (
13
15
  MachineType,
14
16
  MachineTypeList,
15
17
  )
18
+ from opsmith.utils import WaitingSpinner
16
19
 
17
20
 
18
21
  class AWSCloudDetail(BaseCloudProviderDetail):
19
22
  name: Literal["AWS"] = Field(default="AWS", description="Provider name, 'AWS'")
20
23
  account_id: str = Field(..., description="AWS Account ID.")
24
+ ssm_plugin: str = Field(..., description="Path to session-manager-plugin executable.")
21
25
 
22
26
 
23
27
  class AWSProvider(BaseCloudProvider):
@@ -38,33 +42,35 @@ class AWSProvider(BaseCloudProvider):
38
42
  """The cloud provider detail model."""
39
43
  return AWSCloudDetail
40
44
 
41
- def get_regions(self) -> list[tuple[str, str]]:
45
+ @staticmethod
46
+ def get_regions() -> list[tuple[str, str]]:
42
47
  """
43
48
  Retrieves a list of available AWS regions with their display names.
44
49
  """
45
- # Get available region codes from EC2
46
- ec2_client = boto3.client("ec2", region_name="us-east-1")
47
- response = ec2_client.describe_regions()
48
- available_region_codes = {region["RegionName"] for region in response["Regions"]}
49
-
50
- # Get region descriptions from botocore's packaged data
51
- session = botocore.session.get_session()
52
- # The first partition is 'aws' which contains all standard regions
53
- region_data = session.get_data("endpoints")["partitions"][0]["regions"]
54
-
55
- regions = []
56
- for code in available_region_codes:
57
- data = region_data[code]
58
- description = data.get("description", code.replace("-", " ").title())
59
- regions.append((f"{description} ({code})", code))
60
-
61
- return sorted(regions, key=lambda x: x[1])
62
-
63
- def get_instance_types(self, region: str) -> MachineTypeList:
50
+ with WaitingSpinner(text="Fetching available regions from AWS Cloud Provider..."):
51
+ # Get available region codes from EC2
52
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
53
+ response = ec2_client.describe_regions()
54
+ available_region_codes = {region["RegionName"] for region in response["Regions"]}
55
+
56
+ # Get region descriptions from botocore's packaged data
57
+ session = botocore.session.get_session()
58
+ # The first partition is 'aws' which contains all standard regions
59
+ region_data = session.get_data("endpoints")["partitions"][0]["regions"]
60
+
61
+ regions = []
62
+ for code in available_region_codes:
63
+ data = region_data[code]
64
+ description = data.get("description", code.replace("-", " ").title())
65
+ regions.append((f"{description} ({code})", code))
66
+
67
+ return sorted(regions, key=lambda x: x[1])
68
+
69
+ def get_instance_types(self) -> MachineTypeList:
64
70
  """
65
71
  Retrieves a list of available instance types for the given region using heuristics.
66
72
  """
67
- ec2_client = boto3.client("ec2", region_name=region)
73
+ ec2_client = boto3.client("ec2", region_name=self.provider_detail.region)
68
74
 
69
75
  # Prioritize newer generation, general-purpose and compute-optimized instance families
70
76
  instance_families = [
@@ -129,6 +135,16 @@ class AWSProvider(BaseCloudProvider):
129
135
  Retrieves structured AWS account details.
130
136
  """
131
137
  try:
138
+ ssm_plugin_path = shutil.which("session-manager-plugin")
139
+ if not ssm_plugin_path:
140
+ raise CloudCredentialsError(
141
+ message=(
142
+ "'session-manager-plugin' not found. Please install the AWS Session Manager"
143
+ " plugin."
144
+ ),
145
+ help_url="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html",
146
+ )
147
+
132
148
  sts_client = boto3.client("sts")
133
149
  identity = sts_client.get_caller_identity()
134
150
  account_id = identity.get("Account")
@@ -140,7 +156,23 @@ class AWSProvider(BaseCloudProvider):
140
156
  ),
141
157
  help_url="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html",
142
158
  )
143
- return AWSCloudDetail(account_id=account_id)
159
+
160
+ regions = AWSProvider.get_regions()
161
+ questions = [
162
+ inquirer.List(
163
+ "region",
164
+ message="Select an AWS region",
165
+ choices=regions,
166
+ ),
167
+ ]
168
+ answers = inquirer.prompt(questions)
169
+ if not answers or not answers.get("region"):
170
+ raise ValueError("AWS region selection is required. Aborting.")
171
+ selected_region = answers["region"]
172
+
173
+ return AWSCloudDetail(
174
+ account_id=account_id, ssm_plugin=ssm_plugin_path, region=selected_region
175
+ )
144
176
  except (NoCredentialsError, ClientError) as e:
145
177
  raise CloudCredentialsError(
146
178
  message=f"AWS credentials error: {e}",
@@ -57,6 +57,7 @@ class MachineTypeList(BaseModel):
57
57
 
58
58
  class BaseCloudProviderDetail(BaseModel):
59
59
  name: str = Field(..., description="Provider name")
60
+ region: str = Field(..., description="The cloud provider region for this environment.")
60
61
 
61
62
 
62
63
  class CloudProviderRegistry:
@@ -160,7 +161,7 @@ class BaseCloudProvider(abc.ABC):
160
161
  raise NotImplementedError
161
162
 
162
163
  @abc.abstractmethod
163
- def get_instance_types(self, region: str) -> "MachineTypeList":
164
+ def get_instance_types(self) -> "MachineTypeList":
164
165
  """
165
166
  Retrieves a list of available instance types for the given region.
166
167
  """
@@ -172,11 +173,4 @@ class BaseCloudProvider(abc.ABC):
172
173
  Subclasses should implement specific authentication and setup.
173
174
  """
174
175
  self.provider_detail = TypeAdapter(self.get_detail_model()).validate_python(provider_detail)
175
-
176
- @abc.abstractmethod
177
- def get_regions(self) -> List[Tuple[str, str]]:
178
- """
179
- Retrieves a list of available regions for the cloud provider.
180
- Returns a list of tuples, where each tuple contains the display name and the region code.
181
- """
182
- raise NotImplementedError
176
+ self.provider_detail_dump = self.provider_detail.model_dump(mode="json", exclude={"name"})
@@ -15,6 +15,7 @@ from opsmith.cloud_providers.base import (
15
15
  MachineType,
16
16
  MachineTypeList,
17
17
  )
18
+ from opsmith.utils import WaitingSpinner
18
19
 
19
20
  GCP_REGION_DESCRIPTIONS = {
20
21
  "africa-south1": "Johannesburg, South Africa",
@@ -62,6 +63,7 @@ GCP_REGION_DESCRIPTIONS = {
62
63
  class GCPCloudDetail(BaseCloudProviderDetail):
63
64
  name: Literal["GCP"] = Field(default="GCP", description="Provider name, 'GCP'")
64
65
  project_id: str = Field(..., description="GCP Project ID.")
66
+ zone: str = Field(..., description="The GCP zone for this environment.")
65
67
 
66
68
 
67
69
  class GCPProvider(BaseCloudProvider):
@@ -101,37 +103,47 @@ class GCPProvider(BaseCloudProvider):
101
103
  self._credentials, _ = google.auth.default()
102
104
  return self._credentials
103
105
 
104
- def get_regions(self) -> list[tuple[str, str]]:
106
+ @staticmethod
107
+ def get_regions(project_id: str, credentials: Credentials) -> list[tuple[str, str]]:
105
108
  """
106
109
  Retrieves a list of available GCP regions using the GCP API.
107
110
  """
108
- client = compute_v1.RegionsClient(credentials=self.get_credentials())
109
- project_id = self.provider_detail.project_id
110
-
111
- request = compute_v1.ListRegionsRequest(project=project_id)
112
- pager = client.list(request=request)
111
+ with WaitingSpinner(text="Fetching available regions from GCP Cloud Provider..."):
112
+ client = compute_v1.RegionsClient(credentials=credentials)
113
+
114
+ request = compute_v1.ListRegionsRequest(project=project_id)
115
+ pager = client.list(request=request)
116
+
117
+ regions = []
118
+ for region in pager:
119
+ code = region.name
120
+ name = (
121
+ GCP_REGION_DESCRIPTIONS.get(code)
122
+ or region.description
123
+ or code.replace("-", " ").title()
124
+ )
125
+ regions.append((f"{name} ({code})", code))
113
126
 
114
- regions = []
115
- for region in pager:
116
- code = region.name
117
- name = (
118
- GCP_REGION_DESCRIPTIONS.get(code)
119
- or region.description
120
- or code.replace("-", " ").title()
121
- )
122
- regions.append((f"{name} ({code})", code))
127
+ return sorted(regions, key=lambda x: x[1])
123
128
 
124
- return sorted(regions, key=lambda x: x[1])
129
+ @staticmethod
130
+ def get_zones(project_id: str, region_name: str, credentials: Credentials) -> list[str]:
131
+ """
132
+ Retrieves a list of available GCP zones for a given region.
133
+ """
134
+ client = compute_v1.RegionsClient(credentials=credentials)
135
+ request = compute_v1.GetRegionRequest(project=project_id, region=region_name)
136
+ region_details = client.get(request=request)
137
+ zones = [zone.split("/")[-1] for zone in region_details.zones]
138
+ return sorted(zones)
125
139
 
126
- def get_instance_types(self, region: str) -> MachineTypeList:
140
+ def get_instance_types(self) -> MachineTypeList:
127
141
  """
128
- Retrieves a list of available instance types for the given region using the GCP API.
142
+ Retrieves a list of available instance types for the given zone using the GCP API.
129
143
  """
130
144
  client = compute_v1.MachineTypesClient(credentials=self.get_credentials())
131
145
  project_id = self.provider_detail.project_id
132
-
133
- # We need a zone to list machine types. We'll pick a zone in the region, assuming standard naming.
134
- zone = f"{region}-a"
146
+ zone = self.provider_detail.zone
135
147
 
136
148
  request = compute_v1.ListMachineTypesRequest(project=project_id, zone=zone)
137
149
  pager = client.list(request=request)
@@ -183,7 +195,31 @@ class GCPProvider(BaseCloudProvider):
183
195
  raise ValueError("GCP project selection is required. Aborting.")
184
196
 
185
197
  selected_project_id = answers["project_id"]
186
- return GCPCloudDetail(project_id=selected_project_id)
198
+
199
+ regions = cls.get_regions(selected_project_id, credentials)
200
+ region_questions = [
201
+ inquirer.List(
202
+ "region",
203
+ message="Select a GCP region",
204
+ choices=regions,
205
+ ),
206
+ ]
207
+ answers = inquirer.prompt(region_questions)
208
+ if not answers or not answers.get("region"):
209
+ raise ValueError("GCP region selection is required. Aborting.")
210
+
211
+ selected_region = answers["region"]
212
+
213
+ zones = cls.get_zones(selected_project_id, selected_region, credentials)
214
+ if not zones:
215
+ raise ValueError(f"No zones found for region '{selected_region}'.")
216
+
217
+ # For now, just use the first zone.
218
+ selected_zone = zones[0]
219
+
220
+ return GCPCloudDetail(
221
+ project_id=selected_project_id, region=selected_region, zone=selected_zone
222
+ )
187
223
 
188
224
  except DefaultCredentialsError as e:
189
225
  raise CloudCredentialsError(
@@ -9,16 +9,20 @@ from importlib.metadata import entry_points
9
9
  from pathlib import Path
10
10
  from typing import Dict, List, Optional, Tuple, Type
11
11
 
12
- import yaml
13
12
  from pydantic_ai import Agent
14
13
  from rich import print
15
14
 
16
15
  from opsmith.agent import AgentDeps
17
- from opsmith.cloud_providers.base import BaseCloudProvider
16
+ from opsmith.cloud_providers.base import BaseCloudProvider, MachineType
18
17
  from opsmith.infra_provisioners.ansible_provisioner import AnsibleProvisioner
19
18
  from opsmith.infra_provisioners.terraform_provisioner import TerraformProvisioner
20
19
  from opsmith.settings import settings
21
- from opsmith.types import DeploymentConfig, DeploymentEnvironment, ServiceTypeEnum
20
+ from opsmith.types import (
21
+ DeploymentConfig,
22
+ DeploymentEnvironment,
23
+ ServiceTypeEnum,
24
+ VirtualMachineState,
25
+ )
22
26
  from opsmith.utils import slugify
23
27
 
24
28
 
@@ -111,7 +115,7 @@ class BaseDeploymentStrategy(abc.ABC):
111
115
  """Sets up a container registry for the given region."""
112
116
  app_name = deployment_config.app_name_slug
113
117
  # Registry name should probably be unique per region for the app
114
- registry_name = slugify(f"{app_name}-{environment.region}")
118
+ registry_name = slugify(f"{app_name}-{environment.cloud_provider_detail.region}")
115
119
 
116
120
  cloud_provider_instance = environment.cloud_provider_instance
117
121
  provider_name = cloud_provider_instance.name()
@@ -120,7 +124,7 @@ class BaseDeploymentStrategy(abc.ABC):
120
124
  self.deployments_path
121
125
  / "environments"
122
126
  / "global"
123
- / f"{cloud_provider_instance.name()}-{environment.region}"
127
+ / f"{cloud_provider_instance.name()}-{environment.cloud_provider_detail.region}"
124
128
  / "container_registry"
125
129
  )
126
130
  tf = TerraformProvisioner(working_dir=registry_infra_path)
@@ -128,7 +132,7 @@ class BaseDeploymentStrategy(abc.ABC):
128
132
  variables = {
129
133
  "app_name": app_name,
130
134
  "registry_name": registry_name,
131
- "region": environment.region,
135
+ "force_delete": "true",
132
136
  }
133
137
  env_vars = cloud_provider_instance.provider_detail.model_dump(mode="json")
134
138
 
@@ -201,17 +205,16 @@ class BaseDeploymentStrategy(abc.ABC):
201
205
 
202
206
  ansible_runner = AnsibleProvisioner(working_dir=build_infra_path)
203
207
 
204
- provider_name = environment.cloud_provider["name"].lower()
208
+ provider_name = environment.cloud_provider_detail.name.lower()
205
209
 
206
210
  extra_vars = {
207
211
  "docker_path": str(self.agent_deps.src_dir),
208
- "dockerfile_name": str(dockerfile_path_abs),
212
+ "dockerfile_path": str(dockerfile_path_abs),
209
213
  "image_name_slug": image_name_slug,
210
214
  "image_tag_name": "latest",
211
- "region": environment.region,
212
215
  "registry_url": registry_url,
213
216
  }
214
-
217
+ extra_vars.update(environment.cloud_provider_instance.provider_detail_dump)
215
218
  ansible_runner.copy_template("docker_build_push", provider_name)
216
219
  outputs = ansible_runner.run_playbook("main.yml", extra_vars)
217
220
 
@@ -303,9 +306,9 @@ class BaseDeploymentStrategy(abc.ABC):
303
306
  self,
304
307
  deployment_config: DeploymentConfig,
305
308
  environment: DeploymentEnvironment,
306
- instance_type: str,
309
+ machine_type: MachineType,
307
310
  cloud_provider: BaseCloudProvider,
308
- ) -> Tuple[str, str]:
311
+ ) -> VirtualMachineState:
309
312
  """Creates a new virtual machine for the deployment."""
310
313
  provider_name = cloud_provider.name().lower()
311
314
  infra_path = self.deployments_path / "environments" / environment.name / "virtual_machine"
@@ -313,63 +316,61 @@ class BaseDeploymentStrategy(abc.ABC):
313
316
 
314
317
  variables = {
315
318
  "app_name": deployment_config.app_name_slug,
316
- "instance_type": instance_type,
317
- "region": environment.region,
319
+ "environment": environment.name,
320
+ "instance_type": machine_type.name,
321
+ "instance_arch": machine_type.architecture.value,
318
322
  "ssh_pub_key": self._get_ssh_public_key(),
319
323
  }
320
- env_vars = cloud_provider.provider_detail.model_dump(mode="json")
324
+ env_vars = cloud_provider.provider_detail_dump
321
325
 
322
- try:
323
- tf.copy_template("virtual_machine", provider_name)
326
+ tf.copy_template("virtual_machine", provider_name)
324
327
 
325
- tf.init_and_apply(variables, env_vars=env_vars)
328
+ tf.init_and_apply(variables, env_vars=env_vars)
326
329
 
327
- outputs = tf.get_output()
328
- print("[bold green]Monolithic infrastructure provisioned successfully.[/bold green]")
329
- if outputs:
330
- print("[bold green]Outputs:[/bold green]")
331
- print(yaml.dump(outputs))
330
+ outputs = tf.get_output()
331
+ print("[bold green]Monolithic infrastructure provisioned successfully.[/bold green]")
332
332
 
333
- instance_public_ip = outputs.get("instance_public_ip")
334
- if not instance_public_ip:
335
- print(
336
- "[bold red]Could not find 'instance_public_ip' in Terraform outputs.[/bold red]"
337
- )
338
- raise ValueError("Could not find 'instance_public_ip' in Terraform outputs.")
333
+ virtual_machine_state = VirtualMachineState(
334
+ ram_gb=machine_type.ram_gb,
335
+ cpu=machine_type.cpu,
336
+ instance_type=machine_type.name,
337
+ architecture=machine_type.architecture,
338
+ **outputs,
339
+ )
339
340
 
340
- ansible_user = outputs.get("ansible_user")
341
- if not ansible_user:
342
- print("[bold red]Could not find 'ansible_user' in Terraform outputs.[/bold red]")
343
- raise ValueError("Could not find 'ansible_user' in Terraform outputs.")
341
+ print("[bold blue]Waiting 15 seconds for instance to become ready...[/bold blue]")
342
+ time.sleep(15)
344
343
 
345
- print("[bold blue]Waiting 15 seconds for instance to become ready...[/bold blue]")
346
- time.sleep(15)
344
+ print("\n[bold blue]Setting up Docker on the newly created VM[/bold blue]")
347
345
 
348
- print("\n[bold blue]Setting up Docker on the newly created VM[/bold blue]")
346
+ setup_docker_path = (
347
+ self.deployments_path / "environments" / environment.name / "virtual_machine_setup"
348
+ )
349
+ ansible_runner = AnsibleProvisioner(working_dir=setup_docker_path)
350
+ ansible_runner.copy_template("virtual_machine_setup", provider_name)
349
351
 
350
- setup_docker_path = (
351
- self.deployments_path / "environments" / environment.name / "docker_setup"
352
- )
353
- ansible_runner = AnsibleProvisioner(working_dir=setup_docker_path)
354
- ansible_runner.copy_template("docker_setup", provider_name)
355
- ansible_runner.run_playbook(
356
- "main.yml",
357
- extra_vars={},
358
- inventory=instance_public_ip,
359
- user=ansible_user,
360
- )
361
- print("[bold green]Docker setup complete.[/bold green]")
352
+ extra_vars = {
353
+ "environment_name": environment.name,
354
+ "app_name": deployment_config.app_name_slug,
355
+ **cloud_provider.provider_detail_dump,
356
+ **virtual_machine_state.model_dump(mode="json"),
357
+ }
358
+ # extra_vars.update(virtual_machine_state.model_dump(mode="json"))
359
+ # extra_vars.update(cloud_provider.provider_detail_dump)
362
360
 
363
- return instance_public_ip, ansible_user
364
- except (FileNotFoundError, subprocess.CalledProcessError) as e:
365
- print(f"[bold red]Failed to set up monolithic infrastructure: {e}[/bold red]")
366
- raise
361
+ ansible_runner.run_playbook(
362
+ "main.yml",
363
+ extra_vars=extra_vars,
364
+ )
365
+ print("[bold green]Docker setup complete.[/bold green]")
366
+
367
+ return virtual_machine_state
367
368
 
368
369
  def _fetch_remote_deployment_files(
369
370
  self,
371
+ deployment_config: DeploymentConfig,
370
372
  environment: DeploymentEnvironment,
371
- instance_public_ip: str,
372
- ansible_user: str,
373
+ virtual_machine: VirtualMachineState,
373
374
  remote_files: List[str],
374
375
  ) -> List[str]:
375
376
  print("\n[bold blue]Fetching current deployment files from server...[/bold blue]")
@@ -378,17 +379,20 @@ class BaseDeploymentStrategy(abc.ABC):
378
379
  )
379
380
 
380
381
  ansible_runner = AnsibleProvisioner(working_dir=fetch_files_path)
381
- # We use 'common' as provider, since fetching is cloud-agnostic
382
- ansible_runner.copy_template("fetch_remote_files", "common")
382
+ ansible_runner.copy_template(
383
+ "fetch_remote_files", environment.cloud_provider_instance.name()
384
+ )
383
385
  extra_vars = {
386
+ "app_name": deployment_config.app_name_slug,
387
+ "environment_name": environment.name,
384
388
  "remote_files": remote_files,
389
+ **virtual_machine.model_dump(mode="json"),
390
+ **environment.cloud_provider_instance.provider_detail_dump,
385
391
  }
386
392
 
387
393
  outputs = ansible_runner.run_playbook(
388
394
  "main.yml",
389
395
  extra_vars=extra_vars,
390
- inventory=instance_public_ip,
391
- user=ansible_user,
392
396
  )
393
397
 
394
398
  fetched_files_b64 = outputs.get("fetched_files", "")
@@ -430,8 +434,8 @@ class BaseDeploymentStrategy(abc.ABC):
430
434
 
431
435
  extra_vars = {
432
436
  "bucket_name": bucket_name,
433
- "region": environment.region,
434
437
  }
438
+ extra_vars.update(environment.cloud_provider_instance.provider_detail_dump)
435
439
  ansible_runner.run_playbook("main.yml", extra_vars=extra_vars, inventory="localhost")
436
440
  print(f"[bold green]Bucket '{bucket_name}' emptied successfully.[/bold green]")
437
441