opsmith-cli 0.2.2b0__tar.gz → 0.3.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.
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.gitignore +4 -1
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/PKG-INFO +10 -17
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/README.md +9 -16
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/agent.py +6 -1
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/deployment_strategies/base.py +4 -4
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/deployment_strategies/monolithic.py +9 -12
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/git_repo.py +2 -1
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/base_provisioner.py +1 -3
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/main.py +14 -8
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/models.py +3 -2
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/prompts.py +2 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/repo_map.py +4 -3
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/service_detector.py +46 -23
- opsmith_cli-0.3.0b0/opsmith/templates/dockerfiles/python_backend_api +79 -0
- opsmith_cli-0.3.0b0/opsmith/templates/dockerfiles/python_backend_worker +77 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/pyproject.toml +4 -1
- opsmith_cli-0.3.0b0/resources/deploy_demo.svg +1 -0
- opsmith_cli-0.3.0b0/resources/setup_demo.gif +0 -0
- opsmith_cli-0.3.0b0/resources/setup_demo.svg +1 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.flake8 +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.github/workflows/python-publish.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.pre-commit-config.yaml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/CONVENTIONS.md +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/LICENSE.txt +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/__init__.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/__init__.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/aws.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/base.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/gcp.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/constants.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/deployment_strategies/__init__.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/exceptions.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/__init__.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/ansible_provisioner.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/terraform_provisioner.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/README.md +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/arduino-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/c-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/c_sharp-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/chatito-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/commonlisp-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/cpp-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/csharp-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/d-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/dart-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/elisp-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/elixir-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/elm-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/gleam-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/go-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/hcl-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/java-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/javascript-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/kotlin-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/lua-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ocaml-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ocaml_interface-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/php-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/pony-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/properties-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/python-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ql-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/r-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/racket-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ruby-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/rust-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/scala-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/solidity-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/swift-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/typescript-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/udev-tags.scm +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/settings.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/cloud_storage_cleanup/aws/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/cloud_storage_cleanup/gcp/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/aws/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/aws/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/aws/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/gcp/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/gcp/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/gcp/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_build_push/aws/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_build_push/gcp/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_deploy/aws/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_deploy/gcp/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_run/common/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/base.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/elasticsearch.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/kafka.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/mongodb.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/mysql.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/postgresql.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/rabbitmq.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/redis.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/services/backend_api.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/services/backend_worker.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/services/full_stack.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/traefik.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/weaviate.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_setup/aws/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_setup/gcp/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/fetch_remote_files/common/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/aws/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/aws/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/aws/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/gcp/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/gcp/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/gcp/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/aws/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/aws/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/aws/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/gcp/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/gcp/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/gcp/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_deploy/aws/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_deploy/gcp/main.yml +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/aws/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/aws/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/aws/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/gcp/main.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/gcp/outputs.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/gcp/variables.tf +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/types.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/utils.py +0 -0
- {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opsmith-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -109,13 +109,10 @@ Deploying your application with Opsmith follows a straightforward workflow:
|
|
|
109
109
|
```shell
|
|
110
110
|
opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> setup
|
|
111
111
|
```
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- Review and confirm the services and infrastructure dependencies detected by the AI.
|
|
117
|
-
- Opsmith will then generate a `Dockerfile` for each of your services.
|
|
118
|
-
|
|
112
|
+
<p >
|
|
113
|
+
<img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/setup_demo.svg" alt="Opsmith Setup">
|
|
114
|
+
</p>
|
|
115
|
+
|
|
119
116
|
2. **Deploy Your Application**
|
|
120
117
|
|
|
121
118
|
After setting up the configuration, deploy your application using the `deploy` command:
|
|
@@ -124,13 +121,9 @@ Deploying your application with Opsmith follows a straightforward workflow:
|
|
|
124
121
|
opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> deploy
|
|
125
122
|
```
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- Choosing a deployment strategy (e.g., `Monolithic`).
|
|
131
|
-
- Configuring domain names for your services if needed.
|
|
132
|
-
|
|
133
|
-
Opsmith will then provision all the necessary cloud infrastructure and deploy your application.
|
|
124
|
+
<p >
|
|
125
|
+
<img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/deploy_demo.svg" alt="Opsmith Setup">
|
|
126
|
+
</p>
|
|
134
127
|
|
|
135
128
|
3. **Manage Your Deployments**
|
|
136
129
|
|
|
@@ -149,14 +142,14 @@ Opsmith leverages Large Language Models (LLMs) to analyze your codebase, generat
|
|
|
149
142
|
|
|
150
143
|
Opsmith supports a variety of models from different providers. Here is a list of the currently supported models:
|
|
151
144
|
|
|
145
|
+
- **Google** (Recommended):
|
|
146
|
+
- `google-gla:gemini-2.5-pro`
|
|
152
147
|
- **OpenAI**:
|
|
153
148
|
- `openai:gpt-4.1`
|
|
154
149
|
- `openai:gpt-o3`
|
|
155
150
|
- **Anthropic**:
|
|
156
151
|
- `anthropic:claude-3-7-sonnet-20250219`
|
|
157
152
|
- `anthropic:claude-sonnet-4-20250514`
|
|
158
|
-
- **Google**:
|
|
159
|
-
- `google-gla:gemini-2.5-pro`
|
|
160
153
|
|
|
161
154
|
#### Usage
|
|
162
155
|
|
|
@@ -70,13 +70,10 @@ Deploying your application with Opsmith follows a straightforward workflow:
|
|
|
70
70
|
```shell
|
|
71
71
|
opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> setup
|
|
72
72
|
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- Review and confirm the services and infrastructure dependencies detected by the AI.
|
|
78
|
-
- Opsmith will then generate a `Dockerfile` for each of your services.
|
|
79
|
-
|
|
73
|
+
<p >
|
|
74
|
+
<img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/setup_demo.svg" alt="Opsmith Setup">
|
|
75
|
+
</p>
|
|
76
|
+
|
|
80
77
|
2. **Deploy Your Application**
|
|
81
78
|
|
|
82
79
|
After setting up the configuration, deploy your application using the `deploy` command:
|
|
@@ -85,13 +82,9 @@ Deploying your application with Opsmith follows a straightforward workflow:
|
|
|
85
82
|
opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> deploy
|
|
86
83
|
```
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- Choosing a deployment strategy (e.g., `Monolithic`).
|
|
92
|
-
- Configuring domain names for your services if needed.
|
|
93
|
-
|
|
94
|
-
Opsmith will then provision all the necessary cloud infrastructure and deploy your application.
|
|
85
|
+
<p >
|
|
86
|
+
<img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/deploy_demo.svg" alt="Opsmith Setup">
|
|
87
|
+
</p>
|
|
95
88
|
|
|
96
89
|
3. **Manage Your Deployments**
|
|
97
90
|
|
|
@@ -110,14 +103,14 @@ Opsmith leverages Large Language Models (LLMs) to analyze your codebase, generat
|
|
|
110
103
|
|
|
111
104
|
Opsmith supports a variety of models from different providers. Here is a list of the currently supported models:
|
|
112
105
|
|
|
106
|
+
- **Google** (Recommended):
|
|
107
|
+
- `google-gla:gemini-2.5-pro`
|
|
113
108
|
- **OpenAI**:
|
|
114
109
|
- `openai:gpt-4.1`
|
|
115
110
|
- `openai:gpt-o3`
|
|
116
111
|
- **Anthropic**:
|
|
117
112
|
- `anthropic:claude-3-7-sonnet-20250219`
|
|
118
113
|
- `anthropic:claude-sonnet-4-20250514`
|
|
119
|
-
- **Google**:
|
|
120
|
-
- `google-gla:gemini-2.5-pro`
|
|
121
114
|
|
|
122
115
|
#### Usage
|
|
123
116
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from dataclasses import dataclass
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import List, Type
|
|
5
5
|
|
|
@@ -13,6 +13,7 @@ from opsmith.utils import generate_secret_string
|
|
|
13
13
|
@dataclass
|
|
14
14
|
class AgentDeps:
|
|
15
15
|
src_dir: Path
|
|
16
|
+
tracked_files: List[Path] = field(default_factory=list)
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def is_duplicate_tool_call(ctx: RunContext[AgentDeps], tool_name: str) -> bool:
|
|
@@ -64,6 +65,7 @@ def build_agent(model_config: Type[BaseAiModel], instrument: bool = False) -> Ag
|
|
|
64
65
|
"files in this conversation."
|
|
65
66
|
)
|
|
66
67
|
|
|
68
|
+
allowed_files_abs = {str(p) for p in ctx.deps.tracked_files}
|
|
67
69
|
contents = []
|
|
68
70
|
for filename in filenames:
|
|
69
71
|
if Path(filename).is_absolute():
|
|
@@ -74,6 +76,9 @@ def build_agent(model_config: Type[BaseAiModel], instrument: bool = False) -> Ag
|
|
|
74
76
|
|
|
75
77
|
absolute_file_path = ctx.deps.src_dir.joinpath(filename).resolve()
|
|
76
78
|
|
|
79
|
+
if str(absolute_file_path) not in allowed_files_abs:
|
|
80
|
+
raise ModelRetry(f"File '{filename}' is not in the repo map and cannot be read.")
|
|
81
|
+
|
|
77
82
|
if not str(absolute_file_path).startswith(str(ctx.deps.src_dir)):
|
|
78
83
|
raise ModelRetry(
|
|
79
84
|
f"Access denied. File '{filename}' is outside the repository root."
|
|
@@ -162,7 +162,7 @@ class BaseDeploymentStrategy(abc.ABC):
|
|
|
162
162
|
registry_url: str,
|
|
163
163
|
) -> Dict[str, str]:
|
|
164
164
|
"""Builds and pushes Docker images for each service."""
|
|
165
|
-
print("\n[bold blue]
|
|
165
|
+
print("\n[bold blue]Build and push container images to the registry[/bold blue]")
|
|
166
166
|
|
|
167
167
|
images = {}
|
|
168
168
|
|
|
@@ -237,7 +237,7 @@ class BaseDeploymentStrategy(abc.ABC):
|
|
|
237
237
|
:return: The content of the found SSH public key as a string.
|
|
238
238
|
:rtype: str
|
|
239
239
|
"""
|
|
240
|
-
print("\n[bold blue]
|
|
240
|
+
print("\n[bold blue]Looking up the local SSH public key[/bold blue]")
|
|
241
241
|
system = platform.system().lower()
|
|
242
242
|
|
|
243
243
|
# Common SSH key names
|
|
@@ -342,10 +342,10 @@ class BaseDeploymentStrategy(abc.ABC):
|
|
|
342
342
|
print("[bold red]Could not find 'ansible_user' in Terraform outputs.[/bold red]")
|
|
343
343
|
raise ValueError("Could not find 'ansible_user' in Terraform outputs.")
|
|
344
344
|
|
|
345
|
-
print("
|
|
345
|
+
print("[bold blue]Waiting 15 seconds for instance to become ready...[/bold blue]")
|
|
346
346
|
time.sleep(15)
|
|
347
347
|
|
|
348
|
-
print("\n[bold blue]Setting up Docker on the
|
|
348
|
+
print("\n[bold blue]Setting up Docker on the newly created VM[/bold blue]")
|
|
349
349
|
|
|
350
350
|
setup_docker_path = (
|
|
351
351
|
self.deployments_path / "environments" / environment.name / "docker_setup"
|
|
@@ -153,14 +153,13 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
|
|
|
153
153
|
"""
|
|
154
154
|
Deploys the docker-compose stack and returns container logs for validation.
|
|
155
155
|
"""
|
|
156
|
-
print("\n[bold blue]Deploying docker-compose stack
|
|
156
|
+
print("\n[bold blue]Deploying docker-compose stack to the VM \n[/bold blue]")
|
|
157
157
|
ansible_user, instance_public_ip = (
|
|
158
158
|
environment_state.virtual_machine.user,
|
|
159
159
|
environment_state.virtual_machine.public_ip,
|
|
160
160
|
)
|
|
161
161
|
deploy_compose_path, docker_compose_path = self._get_deploy_docker_compose_path(environment)
|
|
162
162
|
|
|
163
|
-
print("\n[bold blue]Attempting to deploy and get logs...[/bold blue]")
|
|
164
163
|
ansible_runner = AnsibleProvisioner(working_dir=deploy_compose_path)
|
|
165
164
|
ansible_runner.copy_template(
|
|
166
165
|
"docker_compose_deploy", deployment_config.cloud_provider_instance.name().lower()
|
|
@@ -241,8 +240,6 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
|
|
|
241
240
|
images: Dict[str, str],
|
|
242
241
|
environment_state: MonolithicDeploymentState,
|
|
243
242
|
):
|
|
244
|
-
print("\n[bold blue]Generating docker-compose file...[/bold blue]")
|
|
245
|
-
|
|
246
243
|
base_compose_template = self.docker_compose_snippets_env.get_template("base.yml")
|
|
247
244
|
base_compose = base_compose_template.render(app_name=deployment_config.app_name_slug)
|
|
248
245
|
|
|
@@ -297,8 +294,8 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
|
|
|
297
294
|
messages = []
|
|
298
295
|
for attempt in range(settings.max_docker_compose_gen_attempts):
|
|
299
296
|
print(
|
|
300
|
-
"\n[bold]Attempt"
|
|
301
|
-
f" {attempt + 1}/{settings.max_docker_compose_gen_attempts}
|
|
297
|
+
"\n[bold blue]Generating docker-compose file, Attempt"
|
|
298
|
+
f" {attempt + 1}/{settings.max_docker_compose_gen_attempts}[/bold blue]"
|
|
302
299
|
)
|
|
303
300
|
prompt = DOCKER_COMPOSE_GENERATION_PROMPT_TEMPLATE.format(
|
|
304
301
|
base_compose=base_compose,
|
|
@@ -338,7 +335,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
|
|
|
338
335
|
if is_successful:
|
|
339
336
|
print("[bold green]Docker compose deployment was successful.[/bold green]")
|
|
340
337
|
break
|
|
341
|
-
print(f"Docker compose validation 'failed' with reason: \n {reason}.")
|
|
338
|
+
print(f"[red]Docker compose validation 'failed' with reason[/red]: \n {reason}.")
|
|
342
339
|
|
|
343
340
|
messages = docker_compose_response.new_messages() + validation_messages
|
|
344
341
|
else:
|
|
@@ -699,7 +696,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
|
|
|
699
696
|
|
|
700
697
|
print(
|
|
701
698
|
"\n[bold blue]Setting up container registry for region"
|
|
702
|
-
f" '{environment.region}'...[/bold blue]"
|
|
699
|
+
f" '{environment.region}'... \n[/bold blue]"
|
|
703
700
|
)
|
|
704
701
|
registry_url = self._setup_container_registry(deployment_config, environment)
|
|
705
702
|
images = self._build_and_push_images(deployment_config, environment, registry_url)
|
|
@@ -743,16 +740,16 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
|
|
|
743
740
|
)
|
|
744
741
|
self._confirm_dns_records(dns_records)
|
|
745
742
|
|
|
743
|
+
env_state.registry_url = registry_url
|
|
744
|
+
env_state.virtual_machine = virtual_machine_state
|
|
745
|
+
self._generate_docker_compose(deployment_config, environment, images, env_state)
|
|
746
|
+
|
|
746
747
|
for domain in environment.get_domains_for_services(other_services):
|
|
747
748
|
print(
|
|
748
749
|
"\n[bold green]Your website is available at:"
|
|
749
750
|
f" https://{domain.domain_name}[/bold green]"
|
|
750
751
|
)
|
|
751
752
|
|
|
752
|
-
env_state.registry_url = registry_url
|
|
753
|
-
env_state.virtual_machine = virtual_machine_state
|
|
754
|
-
self._generate_docker_compose(deployment_config, environment, images, env_state)
|
|
755
|
-
|
|
756
753
|
env_state.save(env_state_path)
|
|
757
754
|
print(f"Monolithic deployment state saved to {env_state_path}")
|
|
758
755
|
|
|
@@ -45,14 +45,12 @@ class BaseInfrastructureProvisioner:
|
|
|
45
45
|
# Use shutil.copytree to copy the contents of the template directory
|
|
46
46
|
shutil.copytree(template_dir, self.working_dir, dirs_exist_ok=True)
|
|
47
47
|
|
|
48
|
-
print(f"[
|
|
48
|
+
print(f"[grey50]{self.command_name} files copied to: {self.working_dir}[/grey50]")
|
|
49
49
|
|
|
50
50
|
def _run_command(
|
|
51
51
|
self, command: List[str], env: Optional[Dict[str, str]] = None
|
|
52
52
|
) -> Dict[str, str]:
|
|
53
53
|
"""Runs a command and streams its output."""
|
|
54
|
-
print(f"\n[bold]Running `{self.command_name}` in {self.working_dir}...[/bold]")
|
|
55
|
-
|
|
56
54
|
process_env = os.environ.copy()
|
|
57
55
|
process_env.update(env or {})
|
|
58
56
|
outputs = {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Annotated, Optional, Type
|
|
3
|
+
from typing import Annotated, Optional, Type, Union
|
|
4
4
|
|
|
5
5
|
import inquirer
|
|
6
6
|
import logfire
|
|
@@ -33,7 +33,7 @@ from opsmith.utils import (
|
|
|
33
33
|
slugify,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
app = typer.Typer(pretty_exceptions_show_locals=
|
|
36
|
+
app = typer.Typer(pretty_exceptions_show_locals=False)
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _check_external_dependencies():
|
|
@@ -52,7 +52,7 @@ def _check_external_dependencies():
|
|
|
52
52
|
raise typer.Exit(code=1)
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def _parse_model_arg(model: Union[str, BaseAiModel]) -> BaseAiModel:
|
|
56
56
|
"""
|
|
57
57
|
Fetches the class corresponding to the given model name.
|
|
58
58
|
|
|
@@ -69,6 +69,8 @@ def parse_model_arg(model: str) -> Type[BaseAiModel]:
|
|
|
69
69
|
:raises ValueError: If the given model name is not found in the model registry.
|
|
70
70
|
:raises typer.BadParameter: If the provided model name is unsupported.
|
|
71
71
|
"""
|
|
72
|
+
if isinstance(model, BaseAiModel):
|
|
73
|
+
return model
|
|
72
74
|
try:
|
|
73
75
|
return MODEL_REGISTRY.get_model_class(model)()
|
|
74
76
|
except ValueError:
|
|
@@ -77,7 +79,7 @@ def parse_model_arg(model: str) -> Type[BaseAiModel]:
|
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def
|
|
82
|
+
def _api_key_callback(ctx: typer.Context, value: str):
|
|
81
83
|
"""
|
|
82
84
|
This function serves as a callback for validating and processing an API key when
|
|
83
85
|
used in conjunction with a command-line interface. The function checks whether
|
|
@@ -111,18 +113,21 @@ def main(
|
|
|
111
113
|
model: Annotated[
|
|
112
114
|
Type[BaseAiModel],
|
|
113
115
|
typer.Option(
|
|
114
|
-
parser=
|
|
116
|
+
parser=_parse_model_arg,
|
|
115
117
|
help="The LLM model to be used for by the AI Agent.",
|
|
118
|
+
prompt="Select the LLM model to be used for by the AI Agent",
|
|
116
119
|
),
|
|
117
120
|
],
|
|
118
121
|
api_key: Annotated[
|
|
119
122
|
str,
|
|
120
123
|
typer.Option(
|
|
121
|
-
callback=
|
|
124
|
+
callback=_api_key_callback,
|
|
122
125
|
help=(
|
|
123
126
|
"The API KEY to be used for by the AI Agent. This is the API key for the specified"
|
|
124
127
|
" model."
|
|
125
128
|
),
|
|
129
|
+
prompt="Enter the API KEY for the specified model",
|
|
130
|
+
hide_input=True,
|
|
126
131
|
),
|
|
127
132
|
],
|
|
128
133
|
logfire_token: Optional[str] = typer.Option(
|
|
@@ -135,6 +140,7 @@ def main(
|
|
|
135
140
|
src_dir: Optional[str] = typer.Option(
|
|
136
141
|
default=None,
|
|
137
142
|
help="Source directory to be used by the command. Defaults to current working directory.",
|
|
143
|
+
envvar="OPSMITH_SRC_DIR",
|
|
138
144
|
),
|
|
139
145
|
verbose: bool = typer.Option(
|
|
140
146
|
False, "--verbose", "-v", help="Enable verbose output for repo map generation."
|
|
@@ -227,7 +233,7 @@ def setup(ctx: typer.Context):
|
|
|
227
233
|
scan_services = True
|
|
228
234
|
|
|
229
235
|
else:
|
|
230
|
-
print("No existing deployment configuration found. Starting analysis
|
|
236
|
+
print("No existing deployment configuration found. Starting analysis...\n")
|
|
231
237
|
app_name_questions = [
|
|
232
238
|
inquirer.Text("app_name", message="Enter the application name"),
|
|
233
239
|
]
|
|
@@ -252,7 +258,7 @@ def setup(ctx: typer.Context):
|
|
|
252
258
|
selected_provider_value = provider_answers["cloud_provider"]
|
|
253
259
|
|
|
254
260
|
# Get cloud details if new or changed
|
|
255
|
-
print(f"Initializing {selected_provider_value} provider
|
|
261
|
+
print(f"Initializing {selected_provider_value} provider...\n")
|
|
256
262
|
provider_class = CLOUD_PROVIDER_REGISTRY.get_provider_class(selected_provider_value)
|
|
257
263
|
try:
|
|
258
264
|
cloud_details = provider_class.get_account_details().model_dump(mode="json")
|
|
@@ -101,6 +101,7 @@ class ModelRegistry:
|
|
|
101
101
|
"""Load built-in models"""
|
|
102
102
|
for model_cls in [
|
|
103
103
|
OpenAIGPT41,
|
|
104
|
+
OpenAIGPTO3,
|
|
104
105
|
AnthropicClaudeSonnet37,
|
|
105
106
|
AnthropicClaudeSonnet4,
|
|
106
107
|
GoogleGlaGemini25Pro,
|
|
@@ -139,13 +140,13 @@ class OpenAIGPT41(BaseAiModel):
|
|
|
139
140
|
@classmethod
|
|
140
141
|
def get_model_settings(cls) -> ModelSettings:
|
|
141
142
|
"""Returns model-specific settings."""
|
|
142
|
-
return OpenAIModelSettings(
|
|
143
|
+
return OpenAIModelSettings()
|
|
143
144
|
|
|
144
145
|
|
|
145
146
|
class OpenAIGPTO3(BaseAiModel):
|
|
146
147
|
@classmethod
|
|
147
148
|
def name(cls) -> str:
|
|
148
|
-
return "
|
|
149
|
+
return "o3"
|
|
149
150
|
|
|
150
151
|
@classmethod
|
|
151
152
|
def provider(cls) -> str:
|
|
@@ -88,6 +88,9 @@ class RepoMap:
|
|
|
88
88
|
self.src_dir = Path(src_dir).resolve()
|
|
89
89
|
self.git_repo = GitRepo(self.src_dir)
|
|
90
90
|
self.verbose = verbose
|
|
91
|
+
self.tracked_files: List[Path] = self.git_repo.get_git_tracked_files(
|
|
92
|
+
[str(self.src_dir), ":!**/*test*"]
|
|
93
|
+
)
|
|
91
94
|
|
|
92
95
|
# Initialize tracking variables
|
|
93
96
|
self.warned_files = set()
|
|
@@ -146,9 +149,7 @@ class RepoMap:
|
|
|
146
149
|
if self.max_map_tokens <= 0:
|
|
147
150
|
return None # Repo map is disabled
|
|
148
151
|
|
|
149
|
-
all_tracked_files_paths = self.
|
|
150
|
-
[str(self.src_dir), ":!**/*test*"]
|
|
151
|
-
)
|
|
152
|
+
all_tracked_files_paths = self.tracked_files
|
|
152
153
|
if not all_tracked_files_paths:
|
|
153
154
|
if self.verbose:
|
|
154
155
|
typer.echo("RepoMap: No git-tracked files found.", err=True)
|
|
@@ -67,12 +67,14 @@ class ServiceDetector:
|
|
|
67
67
|
verbose: bool = False,
|
|
68
68
|
):
|
|
69
69
|
self.deployments_path = Path(src_dir).joinpath(settings.deployments_dir)
|
|
70
|
-
self.agent_deps = AgentDeps(src_dir=Path(src_dir))
|
|
71
70
|
self.agent = agent
|
|
72
71
|
self.repo_map = RepoMap(
|
|
73
72
|
src_dir=src_dir,
|
|
74
73
|
verbose=verbose,
|
|
75
74
|
)
|
|
75
|
+
self.agent_deps = AgentDeps(
|
|
76
|
+
src_dir=Path(src_dir), tracked_files=self.repo_map.tracked_files
|
|
77
|
+
)
|
|
76
78
|
self.verbose = verbose
|
|
77
79
|
|
|
78
80
|
def detect_services(self, existing_config: Optional[ServiceList] = None) -> ServiceList:
|
|
@@ -130,19 +132,29 @@ class ServiceDetector:
|
|
|
130
132
|
service_dir_path = self.deployments_path / "docker" / service.name_slug
|
|
131
133
|
service_dir_path.mkdir(parents=True, exist_ok=True)
|
|
132
134
|
dockerfile_path_abs = service_dir_path / "Dockerfile"
|
|
133
|
-
print(
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
print(f"\n[bold]Generating Dockerfile for service: {service.name_slug}...[/bold]")
|
|
136
|
+
|
|
137
|
+
template_name = f"{service.language.lower()}_{service.service_type.value.lower()}"
|
|
138
|
+
template_path = Path(__file__).parent / "templates" / "dockerfiles" / template_name
|
|
139
|
+
dockerfile_template = None
|
|
140
|
+
if template_path.exists():
|
|
141
|
+
with open(template_path, "r", encoding="utf-8") as f:
|
|
142
|
+
dockerfile_template = f.read()
|
|
143
|
+
print(f"[green]Using Dockerfile template: {template_name}[/green]")
|
|
144
|
+
|
|
145
|
+
dockerfile_content = self._generate_and_validate_dockerfile(
|
|
146
|
+
service, dockerfile_path_abs, dockerfile_template
|
|
136
147
|
)
|
|
137
148
|
|
|
138
|
-
dockerfile_content = self._generate_and_validate_dockerfile(service, dockerfile_path_abs)
|
|
139
|
-
|
|
140
149
|
with open(dockerfile_path_abs, "w", encoding="utf-8") as f:
|
|
141
150
|
f.write(dockerfile_content)
|
|
142
151
|
print(f"[green]Dockerfile saved to: {dockerfile_path_abs}[/green]")
|
|
143
152
|
|
|
144
153
|
def _generate_and_validate_dockerfile(
|
|
145
|
-
self,
|
|
154
|
+
self,
|
|
155
|
+
service: ServiceInfo,
|
|
156
|
+
dockerfile_path_abs: Path,
|
|
157
|
+
dockerfile_template: Optional[str] = None,
|
|
146
158
|
) -> str:
|
|
147
159
|
"""Generates and validates a Dockerfile for a given service."""
|
|
148
160
|
existing_dockerfile_content = "N/A"
|
|
@@ -159,9 +171,21 @@ class ServiceDetector:
|
|
|
159
171
|
while attempt < settings.max_dockerfile_gen_attempts:
|
|
160
172
|
attempt += 1
|
|
161
173
|
print(f"\n[bold]Attempt {attempt}/{settings.max_dockerfile_gen_attempts}...[/bold]")
|
|
174
|
+
|
|
175
|
+
repo_map_str = self.repo_map.get_repo_map()
|
|
176
|
+
template_section = ""
|
|
177
|
+
if dockerfile_template:
|
|
178
|
+
template_section = (
|
|
179
|
+
"A Dockerfile template is provided below. Use it as a guide.\n"
|
|
180
|
+
"```\n"
|
|
181
|
+
f"{dockerfile_template}\n"
|
|
182
|
+
"```\n"
|
|
183
|
+
)
|
|
184
|
+
|
|
162
185
|
prompt = DOCKERFILE_GENERATION_PROMPT_TEMPLATE.format(
|
|
163
186
|
service_info_yaml=service_info_yaml,
|
|
164
|
-
|
|
187
|
+
template_section=template_section,
|
|
188
|
+
repo_map_str=repo_map_str,
|
|
165
189
|
existing_dockerfile_content=existing_dockerfile_content,
|
|
166
190
|
)
|
|
167
191
|
with WaitingSpinner(text="Waiting for the LLM to generate the Dockerfile"):
|
|
@@ -182,10 +206,9 @@ class ServiceDetector:
|
|
|
182
206
|
)
|
|
183
207
|
break
|
|
184
208
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
209
|
+
is_successful, reason, validation_messages = self._validate_dockerfile(
|
|
210
|
+
dockerfile_content
|
|
211
|
+
)
|
|
189
212
|
|
|
190
213
|
if is_successful:
|
|
191
214
|
print(f"[bold green]Dockerfile validation successful: \n {reason}.[/bold green]")
|
|
@@ -209,12 +232,11 @@ class ServiceDetector:
|
|
|
209
232
|
raise OpsmithException("Dockerfile generation aborted by user.")
|
|
210
233
|
dockerfile_content = editor_answers["dockerfile"]
|
|
211
234
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)
|
|
235
|
+
completed, reason, _ = self._validate_dockerfile(dockerfile_content)
|
|
236
|
+
print(
|
|
237
|
+
f"Dockerfile validation {'succeeded' if completed else 'failed'} "
|
|
238
|
+
f"with reason: \n {reason}."
|
|
239
|
+
)
|
|
218
240
|
|
|
219
241
|
return dockerfile_content
|
|
220
242
|
|
|
@@ -328,11 +350,12 @@ class ServiceDetector:
|
|
|
328
350
|
build_output=build_output_str,
|
|
329
351
|
run_output=run_output_str,
|
|
330
352
|
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
353
|
+
with WaitingSpinner(text="Waiting for the LLM to validate the Docker build output"):
|
|
354
|
+
validation_response = self.agent.run_sync(
|
|
355
|
+
validation_prompt,
|
|
356
|
+
output_type=DockerfileValidation,
|
|
357
|
+
deps=self.agent_deps,
|
|
358
|
+
)
|
|
336
359
|
return (
|
|
337
360
|
validation_response.output.is_successful,
|
|
338
361
|
validation_response.output.reason,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Multi-stage Docker build for Python Backend API
|
|
2
|
+
|
|
3
|
+
# Build stage - for compiling dependencies and reducing final image size
|
|
4
|
+
FROM PYTHON_SLIM_IMAGE as builder
|
|
5
|
+
|
|
6
|
+
# Set build arguments for customization
|
|
7
|
+
ARG APP_USER=appuser
|
|
8
|
+
ARG APP_DIR=/app
|
|
9
|
+
|
|
10
|
+
# Create application directory
|
|
11
|
+
WORKDIR ${APP_DIR}
|
|
12
|
+
|
|
13
|
+
# Copy dependency files first (for better layer caching)
|
|
14
|
+
# TEMPLATE_INSTRUCTION: Copy all possible dependency files your project might have
|
|
15
|
+
|
|
16
|
+
# Install Python dependencies
|
|
17
|
+
# TEMPLATE_INSTRUCTION: Use the configured dependency manager to install the requirements
|
|
18
|
+
# This list covers common package managers but is not exhaustive - adapt based on project structure
|
|
19
|
+
|
|
20
|
+
# For pip with requirements.txt:
|
|
21
|
+
# RUN pip install --no-cache-dir --user -r requirements.txt
|
|
22
|
+
|
|
23
|
+
# For Poetry:
|
|
24
|
+
# RUN pip install poetry && \
|
|
25
|
+
# poetry config virtualenvs.create false && \
|
|
26
|
+
# poetry install --only=main --no-dev
|
|
27
|
+
|
|
28
|
+
# For PDM:
|
|
29
|
+
# RUN pip install pdm && \
|
|
30
|
+
# pdm install --prod --no-editable
|
|
31
|
+
|
|
32
|
+
# For uv:
|
|
33
|
+
# RUN pip install uv && \
|
|
34
|
+
# uv pip install --system .
|
|
35
|
+
|
|
36
|
+
# For Pipenv:
|
|
37
|
+
# RUN pip install pipenv && \
|
|
38
|
+
# pipenv install --system --deploy
|
|
39
|
+
|
|
40
|
+
# TEMPLATE_INSTRUCTION: Copy the rest of the application code
|
|
41
|
+
|
|
42
|
+
# Production stage - minimal runtime image
|
|
43
|
+
FROM PYTHON_SLIM_IMAGE as runner
|
|
44
|
+
|
|
45
|
+
# Set build arguments
|
|
46
|
+
ARG APP_USER=appuser
|
|
47
|
+
ARG APP_DIR=/app
|
|
48
|
+
|
|
49
|
+
# Set environment variables for Python optimization and security
|
|
50
|
+
ENV PYTHONUNBUFFERED=1 \
|
|
51
|
+
PYTHONDONTWRITEBYTECODE=1 \
|
|
52
|
+
PYTHONHASHSEED=random \
|
|
53
|
+
PIP_NO_CACHE_DIR=1 \
|
|
54
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
55
|
+
|
|
56
|
+
# Create non-root user for security
|
|
57
|
+
RUN groupadd -r ${APP_USER} && \
|
|
58
|
+
useradd -r -g ${APP_USER} -d ${APP_DIR} -s /sbin/nologin ${APP_USER}
|
|
59
|
+
|
|
60
|
+
# Create application directory and set permissions
|
|
61
|
+
RUN mkdir -p ${APP_DIR} && chown -R ${APP_USER}:${APP_USER} ${APP_DIR}
|
|
62
|
+
|
|
63
|
+
# Set working directory
|
|
64
|
+
WORKDIR ${APP_DIR}
|
|
65
|
+
|
|
66
|
+
# TEMPLATE_INSTRUCTION: Copy the installed Python packages and binaries from the builder stage
|
|
67
|
+
|
|
68
|
+
# TEMPLATE_INSTRUCTION: Copy the application code from the builder stage and set ownership
|
|
69
|
+
|
|
70
|
+
# TEMPLATE_INSTRUCTION: Add framework-specific build steps if any here
|
|
71
|
+
|
|
72
|
+
# Switch to non-root user
|
|
73
|
+
USER ${APP_USER}
|
|
74
|
+
|
|
75
|
+
# TEMPLATE_INSTRUCTION: Update PATH to include user local binaries and set PYTHONPATH
|
|
76
|
+
|
|
77
|
+
# TEMPLATE_INSTRUCTION: Expose port
|
|
78
|
+
|
|
79
|
+
# TEMPLATE_INSTRUCTION: Choose the appropriate command to startup the API
|