opsmith-cli 0.4.0b0__tar.gz → 0.4.1b0__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 (137) hide show
  1. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/.gitignore +2 -1
  2. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/PKG-INFO +2 -2
  3. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/deployment_strategies/base.py +11 -2
  4. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/deployment_strategies/monolithic.py +332 -54
  5. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/main.py +91 -51
  6. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/models.py +61 -3
  7. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_deploy/aws/inventory.yml +3 -2
  8. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_deploy/aws/main.yml +5 -0
  9. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_deploy/gcp/main.yml +5 -0
  10. {opsmith_cli-0.4.0b0/opsmith/templates/fetch_remote_files → opsmith_cli-0.4.1b0/opsmith/templates/docker_compose_run}/aws/inventory.yml +3 -2
  11. {opsmith_cli-0.4.0b0/opsmith/templates/docker_compose_run → opsmith_cli-0.4.1b0/opsmith/templates/fetch_remote_files}/aws/inventory.yml +3 -2
  12. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine/aws/main.tf +11 -1
  13. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine/aws/outputs.tf +5 -0
  14. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine_setup/aws/inventory.yml +3 -2
  15. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine_setup/aws/main.yml +5 -2
  16. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/types.py +6 -0
  17. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/pyproject.toml +2 -2
  18. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/uv.lock +1533 -100
  19. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/.flake8 +0 -0
  20. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/.github/workflows/python-publish.yml +0 -0
  21. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/.pre-commit-config.yaml +0 -0
  22. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/CONVENTIONS.md +0 -0
  23. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/LICENSE.txt +0 -0
  24. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/README.md +0 -0
  25. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/__init__.py +0 -0
  26. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/agent.py +0 -0
  27. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/cloud_providers/__init__.py +0 -0
  28. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/cloud_providers/aws.py +0 -0
  29. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/cloud_providers/base.py +0 -0
  30. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/cloud_providers/gcp.py +0 -0
  31. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/constants.py +0 -0
  32. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/deployment_strategies/__init__.py +0 -0
  33. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/exceptions.py +0 -0
  34. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/git_repo.py +0 -0
  35. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/infra_provisioners/__init__.py +0 -0
  36. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/infra_provisioners/ansible_provisioner.py +0 -0
  37. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/infra_provisioners/base_provisioner.py +0 -0
  38. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/infra_provisioners/terraform_provisioner.py +0 -0
  39. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/prompts.py +0 -0
  40. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/README.md +0 -0
  41. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/arduino-tags.scm +0 -0
  42. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/c-tags.scm +0 -0
  43. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/c_sharp-tags.scm +0 -0
  44. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/chatito-tags.scm +0 -0
  45. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/commonlisp-tags.scm +0 -0
  46. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/cpp-tags.scm +0 -0
  47. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/csharp-tags.scm +0 -0
  48. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/d-tags.scm +0 -0
  49. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/dart-tags.scm +0 -0
  50. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/elisp-tags.scm +0 -0
  51. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/elixir-tags.scm +0 -0
  52. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/elm-tags.scm +0 -0
  53. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/gleam-tags.scm +0 -0
  54. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/go-tags.scm +0 -0
  55. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/hcl-tags.scm +0 -0
  56. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/java-tags.scm +0 -0
  57. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/javascript-tags.scm +0 -0
  58. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/kotlin-tags.scm +0 -0
  59. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/lua-tags.scm +0 -0
  60. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/ocaml-tags.scm +0 -0
  61. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/ocaml_interface-tags.scm +0 -0
  62. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/php-tags.scm +0 -0
  63. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/pony-tags.scm +0 -0
  64. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/properties-tags.scm +0 -0
  65. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/python-tags.scm +0 -0
  66. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/ql-tags.scm +0 -0
  67. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/r-tags.scm +0 -0
  68. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/racket-tags.scm +0 -0
  69. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/ruby-tags.scm +0 -0
  70. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/rust-tags.scm +0 -0
  71. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/scala-tags.scm +0 -0
  72. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/solidity-tags.scm +0 -0
  73. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/swift-tags.scm +0 -0
  74. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/typescript-tags.scm +0 -0
  75. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/queries/tree-sitter-languages/udev-tags.scm +0 -0
  76. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/repo_map.py +0 -0
  77. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/service_detector.py +0 -0
  78. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/settings.py +0 -0
  79. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/cloud_storage_cleanup/aws/main.yml +0 -0
  80. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/cloud_storage_cleanup/gcp/main.yml +0 -0
  81. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/container_registry/aws/main.tf +0 -0
  82. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/container_registry/aws/outputs.tf +0 -0
  83. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/container_registry/aws/variables.tf +0 -0
  84. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/container_registry/gcp/main.tf +0 -0
  85. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/container_registry/gcp/outputs.tf +0 -0
  86. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/container_registry/gcp/variables.tf +0 -0
  87. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_build_push/aws/main.yml +0 -0
  88. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_build_push/gcp/main.yml +0 -0
  89. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_deploy/gcp/inventory.yml +0 -0
  90. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_run/aws/main.yml +0 -0
  91. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_run/gcp/inventory.yml +0 -0
  92. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_run/gcp/main.yml +0 -0
  93. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/base.yml +0 -0
  94. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/elasticsearch.yml +0 -0
  95. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/kafka.yml +0 -0
  96. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/mongodb.yml +0 -0
  97. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/mysql.yml +0 -0
  98. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/postgresql.yml +0 -0
  99. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/rabbitmq.yml +0 -0
  100. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/redis.yml +0 -0
  101. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/services/backend_api.yml +0 -0
  102. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/services/backend_worker.yml +0 -0
  103. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/services/full_stack.yml +0 -0
  104. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/traefik.yml +0 -0
  105. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/docker_compose_snippets/weaviate.yml +0 -0
  106. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/dockerfiles/python_backend_api +0 -0
  107. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/dockerfiles/python_backend_worker +0 -0
  108. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/fetch_remote_files/aws/main.yml +0 -0
  109. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/fetch_remote_files/gcp/inventory.yml +0 -0
  110. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/fetch_remote_files/gcp/main.yml +0 -0
  111. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_bucket_cert/aws/main.tf +0 -0
  112. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_bucket_cert/aws/outputs.tf +0 -0
  113. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_bucket_cert/aws/variables.tf +0 -0
  114. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_bucket_cert/gcp/main.tf +0 -0
  115. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_bucket_cert/gcp/outputs.tf +0 -0
  116. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_bucket_cert/gcp/variables.tf +0 -0
  117. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_cdn/aws/main.tf +0 -0
  118. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_cdn/aws/outputs.tf +0 -0
  119. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_cdn/aws/variables.tf +0 -0
  120. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_cdn/gcp/main.tf +0 -0
  121. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_cdn/gcp/outputs.tf +0 -0
  122. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_cdn/gcp/variables.tf +0 -0
  123. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_deploy/aws/main.yml +0 -0
  124. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/frontend_deploy/gcp/main.yml +0 -0
  125. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine/aws/variables.tf +0 -0
  126. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine/gcp/main.tf +0 -0
  127. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine/gcp/outputs.tf +0 -0
  128. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine/gcp/variables.tf +0 -0
  129. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine_setup/gcp/inventory.yml +0 -0
  130. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/templates/virtual_machine_setup/gcp/main.yml +0 -0
  131. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/tests/__init__.py +0 -0
  132. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/tests/test_service_detector.py +0 -0
  133. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/opsmith/utils.py +0 -0
  134. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/resources/deploy_demo.gif +0 -0
  135. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/resources/deploy_demo.svg +0 -0
  136. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/resources/setup_demo.gif +0 -0
  137. {opsmith_cli-0.4.0b0 → opsmith_cli-0.4.1b0}/resources/setup_demo.svg +0 -0
@@ -193,8 +193,9 @@ cython_debug/
193
193
  .cursorignore
194
194
  .cursorindexingignore
195
195
 
196
- # Aider
196
+ # Local
197
197
  .aider*
198
+ .DS_Store
198
199
 
199
200
  # Asciinema
200
201
  *.cast
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opsmith-cli
3
- Version: 0.4.0b0
3
+ Version: 0.4.1b0
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
@@ -28,7 +28,7 @@ Requires-Dist: inquirer>=3.4.0
28
28
  Requires-Dist: jinja2>=3.1.6
29
29
  Requires-Dist: networkx>=3.4.2
30
30
  Requires-Dist: pick>=2.4.0
31
- Requires-Dist: pydantic-ai[logfire]==0.4.2
31
+ Requires-Dist: pydantic-ai[logfire]==1.27.0
32
32
  Requires-Dist: pydantic>=2.11.5
33
33
  Requires-Dist: python-dotenv>=1.1.0
34
34
  Requires-Dist: pyyaml>=6.0.2
@@ -101,9 +101,9 @@ class BaseDeploymentStrategy(abc.ABC):
101
101
 
102
102
  def __init__(self, agent: Agent, src_dir: Path):
103
103
  self.agent = agent
104
- self.agent_deps = AgentDeps(src_dir=Path(src_dir))
104
+ self.agent_deps = AgentDeps(src_dir=src_dir)
105
105
  self.src_dir = src_dir
106
- self.deployments_path = Path(src_dir).joinpath(settings.deployments_dir)
106
+ self.deployments_path = src_dir.joinpath(settings.deployments_dir)
107
107
  self.templates_dir = Path(__file__).parent.parent / "templates"
108
108
 
109
109
  def _get_env_state_path(self, environment_name: str) -> Path:
@@ -476,3 +476,12 @@ class BaseDeploymentStrategy(abc.ABC):
476
476
  ):
477
477
  """Runs a command on a specific service."""
478
478
  raise NotImplementedError
479
+
480
+ @abc.abstractmethod
481
+ def update(
482
+ self,
483
+ deployment_config: DeploymentConfig,
484
+ environment: DeploymentEnvironment,
485
+ ):
486
+ """Updates service configuration for an existing deployment."""
487
+ raise NotImplementedError
@@ -237,6 +237,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
237
237
  environment: DeploymentEnvironment,
238
238
  images: Dict[str, str],
239
239
  environment_state: MonolithicDeploymentState,
240
+ existing_env_content: Optional[str] = None,
240
241
  ):
241
242
  base_compose_template = self.docker_compose_snippets_env.get_template("base.yml")
242
243
  base_compose = base_compose_template.render(app_name=deployment_config.app_name_slug)
@@ -276,7 +277,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
276
277
 
277
278
  infra_snippets_list = []
278
279
  for infra in deployment_config.infra_deps:
279
- template = self.docker_compose_snippets_env.get_template(f"{infra.provider}.yml")
280
+ template = self.docker_compose_snippets_env.get_template(f"{infra.provider.value}.yml")
280
281
 
281
282
  content = template.render(
282
283
  version=infra.version,
@@ -289,7 +290,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
289
290
 
290
291
  services_info_yaml = yaml.dump(services_info)
291
292
 
292
- confirmed_env_content = "N/A"
293
+ confirmed_env_content = existing_env_content or "N/A"
293
294
  is_successful = False
294
295
  docker_compose_content = None
295
296
  messages = []
@@ -372,6 +373,112 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
372
373
  f"with reason: \n {reason}."
373
374
  )
374
375
 
376
+ @staticmethod
377
+ def _detect_configuration_changes(
378
+ current_config: DeploymentConfig,
379
+ deployed_state: MonolithicDeploymentState,
380
+ ) -> Tuple[bool, Dict[str, List]]:
381
+ """
382
+ Detects changes between current config and deployed state.
383
+
384
+ Returns:
385
+ (has_changes, change_details)
386
+ """
387
+ changes = {
388
+ "services_added": [],
389
+ "services_removed": [],
390
+ "services_modified": [],
391
+ "infra_added": [],
392
+ "infra_removed": [],
393
+ "infra_modified": [],
394
+ }
395
+
396
+ # Get deployed snapshots
397
+ deployed_services = deployed_state.deployed_services or []
398
+ deployed_infra_deps = deployed_state.deployed_infra_deps or []
399
+
400
+ # Create lookup maps for deployed state
401
+ deployed_services_map = {s["name_slug"]: s for s in deployed_services}
402
+ deployed_infra_map = {i["provider"]: i for i in deployed_infra_deps}
403
+
404
+ # Create lookup maps for current config
405
+ current_services_map = {
406
+ s.name_slug: s.model_dump(mode="json") for s in current_config.services
407
+ }
408
+ current_infra_map = {
409
+ i.provider.value: i.model_dump(mode="json") for i in current_config.infra_deps
410
+ }
411
+
412
+ # Detect service changes
413
+ for name_slug, current_service in current_services_map.items():
414
+ if name_slug not in deployed_services_map:
415
+ changes["services_added"].append(name_slug)
416
+ else:
417
+ deployed_service = deployed_services_map[name_slug]
418
+ # Check for modifications (port, service_type, env_vars)
419
+ if (
420
+ current_service.get("service_port") != deployed_service.get("service_port")
421
+ or current_service.get("service_type") != deployed_service.get("service_type")
422
+ or current_service.get("env_vars") != deployed_service.get("env_vars")
423
+ ):
424
+ changes["services_modified"].append(name_slug)
425
+
426
+ for name_slug in deployed_services_map:
427
+ if name_slug not in current_services_map:
428
+ changes["services_removed"].append(name_slug)
429
+
430
+ # Detect infrastructure changes
431
+ for provider, current_infra in current_infra_map.items():
432
+ if provider not in deployed_infra_map:
433
+ changes["infra_added"].append(provider)
434
+ else:
435
+ deployed_infra = deployed_infra_map[provider]
436
+ # Check for version changes
437
+ if current_infra.get("version") != deployed_infra.get("version"):
438
+ changes["infra_modified"].append(provider)
439
+
440
+ for provider in deployed_infra_map:
441
+ if provider not in current_infra_map:
442
+ changes["infra_removed"].append(provider)
443
+
444
+ # Determine if there are any changes
445
+ has_changes = any(
446
+ changes["services_added"]
447
+ or changes["services_removed"]
448
+ or changes["services_modified"]
449
+ or changes["infra_added"]
450
+ or changes["infra_removed"]
451
+ or changes["infra_modified"]
452
+ )
453
+
454
+ return has_changes, changes
455
+
456
+ @staticmethod
457
+ def _get_frontend_services(deployment_config: DeploymentConfig) -> List[ServiceInfo]:
458
+ """Returns list of frontend services."""
459
+ return [s for s in deployment_config.services if s.service_type == ServiceTypeEnum.FRONTEND]
460
+
461
+ @staticmethod
462
+ def _get_backend_services(deployment_config: DeploymentConfig) -> List[ServiceInfo]:
463
+ """Returns list of non-frontend services."""
464
+ return [s for s in deployment_config.services if s.service_type != ServiceTypeEnum.FRONTEND]
465
+
466
+ @staticmethod
467
+ def _save_deployment_state(
468
+ env_state: MonolithicDeploymentState,
469
+ deployment_config: DeploymentConfig,
470
+ env_state_path: Path,
471
+ ):
472
+ """Saves deployment state with current config snapshots."""
473
+ env_state.deployed_services = [
474
+ s.model_dump(mode="json") for s in deployment_config.services
475
+ ]
476
+ env_state.deployed_infra_deps = [
477
+ i.model_dump(mode="json") for i in deployment_config.infra_deps
478
+ ]
479
+ env_state.save(env_state_path)
480
+ print(f"\n[bold green]Deployment state saved to {env_state_path}[/bold green]")
481
+
375
482
  @staticmethod
376
483
  def _prompt_for_build_env_vars(
377
484
  service: ServiceInfo, existing_vars: Optional[dict] = None
@@ -605,6 +712,55 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
605
712
  print("[bold red]DNS configuration not confirmed. Aborting deployment.[/bold red]")
606
713
  raise MonolithicDeploymentError("User did not confirm DNS configuration.")
607
714
 
715
+ def _deploy_frontend_service(
716
+ self,
717
+ deployment_config: DeploymentConfig,
718
+ environment: DeploymentEnvironment,
719
+ domain_info: DomainInfo,
720
+ cloud_provider: BaseCloudProvider,
721
+ service: ServiceInfo,
722
+ env_state: MonolithicDeploymentState,
723
+ ):
724
+ """Deploys a frontend service to a new deployment environment."""
725
+ cdn_part1_outputs = self._create_frontend_bucket_cert(
726
+ deployment_config, environment, service, domain_info, cloud_provider
727
+ )
728
+
729
+ cdn_part2_outputs = self._create_frontend_cdn(
730
+ deployment_config,
731
+ environment,
732
+ service,
733
+ domain_info,
734
+ cloud_provider,
735
+ cdn_part1_outputs,
736
+ )
737
+ cdn_outputs = {**cdn_part1_outputs, **cdn_part2_outputs}
738
+
739
+ build_env_vars = self._prompt_for_build_env_vars(service)
740
+ cdn_state = FrontendCDNState(
741
+ service_name_slug=service.name_slug,
742
+ domain_name=domain_info.domain_name,
743
+ bucket_name=cdn_outputs.get("bucket_name"),
744
+ cdn_domain_name=cdn_outputs.get("cdn_domain_name"),
745
+ cdn_ip_address=cdn_outputs.get("cdn_ip_address"),
746
+ cdn_distribution_id=cdn_outputs.get("cdn_distribution_id"),
747
+ cdn_url_map=cdn_outputs.get("cdn_url_map"),
748
+ certificate_id=cdn_outputs.get("certificate_id"),
749
+ build_env_vars=build_env_vars,
750
+ )
751
+ env_state.frontend_cdn.append(cdn_state)
752
+
753
+ self._build_and_upload_frontend_assets(
754
+ service,
755
+ cloud_provider,
756
+ environment,
757
+ cdn_state,
758
+ )
759
+ print(
760
+ "\n[bold green]Your website is available at:"
761
+ f" https://{domain_info.domain_name}[/bold green]"
762
+ )
763
+
608
764
  def deploy(
609
765
  self,
610
766
  deployment_config: DeploymentConfig,
@@ -624,12 +780,8 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
624
780
  :type environment: DeploymentEnvironment
625
781
  :return: None
626
782
  """
627
- frontend_services = [
628
- s for s in deployment_config.services if s.service_type == ServiceTypeEnum.FRONTEND
629
- ]
630
- other_services = [
631
- s for s in deployment_config.services if s.service_type != ServiceTypeEnum.FRONTEND
632
- ]
783
+ frontend_services = self._get_frontend_services(deployment_config)
784
+ other_services = self._get_backend_services(deployment_config)
633
785
 
634
786
  cloud_provider = environment.cloud_provider_instance
635
787
  env_state_path = self._get_env_state_path(environment.name)
@@ -647,44 +799,8 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
647
799
  " Skipping.[/bold red]"
648
800
  )
649
801
  continue
650
-
651
- cdn_part1_outputs = self._create_frontend_bucket_cert(
652
- deployment_config, environment, service, domain_info, cloud_provider
653
- )
654
-
655
- cdn_part2_outputs = self._create_frontend_cdn(
656
- deployment_config,
657
- environment,
658
- service,
659
- domain_info,
660
- cloud_provider,
661
- cdn_part1_outputs,
662
- )
663
- cdn_outputs = {**cdn_part1_outputs, **cdn_part2_outputs}
664
-
665
- build_env_vars = self._prompt_for_build_env_vars(service)
666
- cdn_state = FrontendCDNState(
667
- service_name_slug=service.name_slug,
668
- domain_name=domain_info.domain_name,
669
- bucket_name=cdn_outputs.get("bucket_name"),
670
- cdn_domain_name=cdn_outputs.get("cdn_domain_name"),
671
- cdn_ip_address=cdn_outputs.get("cdn_ip_address"),
672
- cdn_distribution_id=cdn_outputs.get("cdn_distribution_id"),
673
- cdn_url_map=cdn_outputs.get("cdn_url_map"),
674
- certificate_id=cdn_outputs.get("certificate_id"),
675
- build_env_vars=build_env_vars,
676
- )
677
- env_state.frontend_cdn.append(cdn_state)
678
-
679
- self._build_and_upload_frontend_assets(
680
- service,
681
- cloud_provider,
682
- environment,
683
- cdn_state,
684
- )
685
- print(
686
- "\n[bold green]Your website is available at:"
687
- f" https://{domain_info.domain_name}[/bold green]"
802
+ self._deploy_frontend_service(
803
+ deployment_config, environment, domain_info, cloud_provider, service, env_state
688
804
  )
689
805
 
690
806
  if other_services:
@@ -738,8 +854,8 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
738
854
  f" https://{domain.domain_name}[/bold green]"
739
855
  )
740
856
 
741
- env_state.save(env_state_path)
742
- print(f"Monolithic deployment state saved to {env_state_path}")
857
+ # Save config snapshots for change detection
858
+ self._save_deployment_state(env_state, deployment_config, env_state_path)
743
859
 
744
860
  def release(
745
861
  self,
@@ -753,9 +869,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
753
869
  state_updated = False
754
870
 
755
871
  # Release frontend services
756
- frontend_services = [
757
- s for s in deployment_config.services if s.service_type == ServiceTypeEnum.FRONTEND
758
- ]
872
+ frontend_services = self._get_frontend_services(deployment_config)
759
873
  if frontend_services:
760
874
  print("\n[bold blue]Releasing frontend services...[/bold blue]")
761
875
  cdn_state_map = {cdn.service_name_slug: cdn for cdn in env_state.frontend_cdn}
@@ -788,9 +902,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
788
902
  )
789
903
 
790
904
  # Release other services
791
- other_services = [
792
- s for s in deployment_config.services if s.service_type != ServiceTypeEnum.FRONTEND
793
- ]
905
+ other_services = self._get_backend_services(deployment_config)
794
906
 
795
907
  if other_services:
796
908
  if env_state.virtual_machine:
@@ -1025,3 +1137,169 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
1025
1137
  "main.yml",
1026
1138
  extra_vars=extra_vars,
1027
1139
  )
1140
+
1141
+ def update(
1142
+ self,
1143
+ deployment_config: DeploymentConfig,
1144
+ environment: DeploymentEnvironment,
1145
+ ):
1146
+ """
1147
+ Updates service configuration for an existing deployment.
1148
+
1149
+ Handles:
1150
+ - Service additions/removals
1151
+ - Infrastructure dependency changes
1152
+ - Port/configuration changes
1153
+ """
1154
+ print("\n[bold blue]Starting configuration update...[/bold blue]")
1155
+
1156
+ # Load existing state
1157
+ env_state_path = self._get_env_state_path(environment.name)
1158
+ if not env_state_path.exists():
1159
+ print(
1160
+ "[bold red]No deployment found for this environment. Please run 'deploy'"
1161
+ " first.[/bold red]"
1162
+ )
1163
+ raise MonolithicDeploymentError(
1164
+ f"No state file found at {env_state_path}. Run 'deploy' first."
1165
+ )
1166
+
1167
+ env_state = MonolithicDeploymentState.load(env_state_path)
1168
+
1169
+ # Detect changes
1170
+ has_changes, changes = self._detect_configuration_changes(deployment_config, env_state)
1171
+
1172
+ if not has_changes:
1173
+ print("[bold green]No configuration changes detected. Nothing to update.[/bold green]")
1174
+ return
1175
+
1176
+ # Display detected changes
1177
+ print("\n[bold]Configuration changes detected:[/bold]")
1178
+ if changes["services_added"]:
1179
+ print(f" [green]Services added:[/green] {', '.join(changes['services_added'])}")
1180
+ if changes["services_removed"]:
1181
+ print(f" [red]Services removed:[/red] {', '.join(changes['services_removed'])}")
1182
+ if changes["services_modified"]:
1183
+ print(
1184
+ f" [yellow]Services modified:[/yellow] {', '.join(changes['services_modified'])}"
1185
+ )
1186
+ if changes["infra_added"]:
1187
+ print(f" [green]Infrastructure added:[/green] {', '.join(changes['infra_added'])}")
1188
+ if changes["infra_removed"]:
1189
+ print(f" [red]Infrastructure removed:[/red] {', '.join(changes['infra_removed'])}")
1190
+ if changes["infra_modified"]:
1191
+ print(
1192
+ " [yellow]Infrastructure modified:[/yellow]"
1193
+ f" {', '.join(changes['infra_modified'])}"
1194
+ )
1195
+
1196
+ # Warn about infrastructure changes
1197
+ infra_changes = (
1198
+ changes["infra_added"] or changes["infra_removed"] or changes["infra_modified"]
1199
+ )
1200
+ if infra_changes:
1201
+ print(
1202
+ "\n[bold yellow]WARNING: Infrastructure changes detected. Existing data in affected"
1203
+ " services may be lost.[/bold yellow]"
1204
+ )
1205
+ confirm_questions = [
1206
+ inquirer.Confirm(
1207
+ "continue",
1208
+ message="Do you want to continue with the update?",
1209
+ default=False,
1210
+ )
1211
+ ]
1212
+ confirm_answers = inquirer.prompt(confirm_questions)
1213
+ if not confirm_answers or not confirm_answers.get("continue"):
1214
+ print("[bold yellow]Update cancelled by user.[/bold yellow]")
1215
+ return
1216
+
1217
+ cloud_provider = environment.cloud_provider_instance
1218
+
1219
+ # Handle frontend services
1220
+ frontend_services = self._get_frontend_services(deployment_config)
1221
+ if frontend_services:
1222
+ print("\n[bold blue]Updating frontend services...[/bold blue]")
1223
+ cdn_state_map = {cdn.service_name_slug: cdn for cdn in env_state.frontend_cdn}
1224
+ domains_map = {d.service_name_slug: d for d in environment.domains}
1225
+
1226
+ for service in frontend_services:
1227
+ cdn_state = cdn_state_map.get(service.name_slug)
1228
+ if not cdn_state:
1229
+ # New frontend service - create CDN
1230
+ domain_info = domains_map.get(service.name_slug)
1231
+ if not domain_info:
1232
+ # This should not happen if main.py did its job
1233
+ print(
1234
+ f"[bold yellow]No domain configured for '{service.name_slug}'. "
1235
+ "Skipping CDN creation.[/bold yellow]"
1236
+ )
1237
+ continue
1238
+ self._deploy_frontend_service(
1239
+ deployment_config,
1240
+ environment,
1241
+ domain_info,
1242
+ cloud_provider,
1243
+ service,
1244
+ env_state,
1245
+ )
1246
+ else:
1247
+ # Existing service - update build env vars and assets
1248
+ build_env_vars = self._prompt_for_build_env_vars(
1249
+ service, existing_vars=cdn_state.build_env_vars
1250
+ )
1251
+ if cdn_state.build_env_vars != build_env_vars:
1252
+ cdn_state.build_env_vars = build_env_vars
1253
+
1254
+ self._build_and_upload_frontend_assets(
1255
+ service, cloud_provider, environment, cdn_state
1256
+ )
1257
+ print(
1258
+ f"[bold green]Frontend service '{service.name_slug}' updated"
1259
+ " successfully.[/bold green]"
1260
+ )
1261
+
1262
+ # Handle backend services
1263
+ other_services = [
1264
+ s for s in deployment_config.services if s.service_type != ServiceTypeEnum.FRONTEND
1265
+ ]
1266
+
1267
+ if other_services:
1268
+ if not env_state.virtual_machine:
1269
+ print(
1270
+ "[bold yellow]No virtual machine provisioned for this environment. Cannot"
1271
+ " update backend services.[/bold yellow]"
1272
+ )
1273
+ return
1274
+
1275
+ print("\n[bold blue]Updating backend services...[/bold blue]")
1276
+
1277
+ # Rebuild and push images
1278
+ images = self._build_and_push_images(
1279
+ deployment_config, environment, env_state.registry_url
1280
+ )
1281
+
1282
+ # Fetch existing .env file
1283
+ env_file_path = f"/home/{env_state.virtual_machine.user}/app/.env"
1284
+ fetched_files = self._fetch_remote_deployment_files(
1285
+ deployment_config,
1286
+ environment,
1287
+ env_state.virtual_machine,
1288
+ [env_file_path],
1289
+ )
1290
+ existing_env_content = fetched_files[0]
1291
+
1292
+ # Regenerate docker-compose with existing env as starting point
1293
+ print("\n[bold blue]Regenerating docker-compose configuration...[/bold blue]")
1294
+ self._generate_docker_compose(
1295
+ deployment_config,
1296
+ environment,
1297
+ images,
1298
+ env_state,
1299
+ existing_env_content=existing_env_content,
1300
+ )
1301
+
1302
+ print("[bold green]Backend services updated successfully.[/bold green]")
1303
+
1304
+ # Update state with new config snapshots
1305
+ self._save_deployment_state(env_state, deployment_config, env_state_path)