deployml-core 0.0.28__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.
- deployml_core-0.0.28/LICENSE +21 -0
- deployml_core-0.0.28/PKG-INFO +111 -0
- deployml_core-0.0.28/README.md +87 -0
- deployml_core-0.0.28/pyproject.toml +36 -0
- deployml_core-0.0.28/src/deployml/__init__.py +15 -0
- deployml_core-0.0.28/src/deployml/api.py +262 -0
- deployml_core-0.0.28/src/deployml/cli/__init__.py +0 -0
- deployml_core-0.0.28/src/deployml/cli/cli.py +2181 -0
- deployml_core-0.0.28/src/deployml/diagnostics/__init__.py +14 -0
- deployml_core-0.0.28/src/deployml/diagnostics/doctor.py +629 -0
- deployml_core-0.0.28/src/deployml/enum/__init__.py +0 -0
- deployml_core-0.0.28/src/deployml/enum/cloud_provider.py +10 -0
- deployml_core-0.0.28/src/deployml/enum/deployment_type.py +13 -0
- deployml_core-0.0.28/src/deployml/notebook/__init__.py +32 -0
- deployml_core-0.0.28/src/deployml/notebook/deployment.py +303 -0
- deployml_core-0.0.28/src/deployml/notebook/display.py +85 -0
- deployml_core-0.0.28/src/deployml/notebook/stack.py +542 -0
- deployml_core-0.0.28/src/deployml/notebook/urls.py +58 -0
- deployml_core-0.0.28/src/deployml/notebook.py +19 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_run/base_main.tf.j2 +20 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_run/main.tf.j2 +370 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_run/mlflow_main.tf.j2 +406 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_run/terraform.tfvars.j2 +71 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_run/variables.tf.j2 +232 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_run/wandb_main.tf.j2 +112 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_vm/main.tf.j2 +3436 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_vm/terraform.tfvars.j2 +185 -0
- deployml_core-0.0.28/src/deployml/templates/gcp/cloud_vm/variables.tf.j2 +377 -0
- deployml_core-0.0.28/src/deployml/templates/kubernetes_local/deployment.yaml.j2 +48 -0
- deployml_core-0.0.28/src/deployml/templates/kubernetes_local/mlflow-deployment.yaml.j2 +69 -0
- deployml_core-0.0.28/src/deployml/templates/kubernetes_local/mlflow-service.yaml.j2 +16 -0
- deployml_core-0.0.28/src/deployml/templates/kubernetes_local/service.yaml.j2 +15 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/cloud_sql_postgres/main.tf +133 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/cloud_sql_postgres/outputs.tf +87 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/cloud_sql_postgres/variables.tf +69 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/main.tf +325 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/outputs.tf +38 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/variables.tf +126 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/main.tf +91 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/outputs.tf +25 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/variables.tf +74 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/main.tf +104 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/outputs.tf +35 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/variables.tf +86 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/main.tf +134 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/outputs.tf +14 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/variables.tf +96 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/main.tf +213 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/outputs.tf +46 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/variables.tf +139 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/feast_env.tpl +36 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/main.tf +96 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/outputs.tf +54 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/variables.tf +93 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/main.tf +75 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/outputs.tf +4 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/variables.tf +55 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/main.tf +104 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/outputs.tf +24 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/variables.tf +132 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/main.tf +1258 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/variables.tf +210 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/main.tf +212 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/outputs.tf +45 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/variables.tf +182 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/main.py +191 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/requirements.txt +3 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/cloudbuild.yaml +88 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/main.tf +151 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/outputs.tf +25 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/teardown_script.sh +231 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/teardown/cloud/gcp/variables.tf +38 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/main.tf +79 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/outputs.tf +24 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/variables.tf +109 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/main.tf +249 -0
- deployml_core-0.0.28/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/variables.tf +137 -0
- deployml_core-0.0.28/src/deployml/utils/__init__.py +0 -0
- deployml_core-0.0.28/src/deployml/utils/banner.py +22 -0
- deployml_core-0.0.28/src/deployml/utils/constants.py +51 -0
- deployml_core-0.0.28/src/deployml/utils/helpers.py +466 -0
- deployml_core-0.0.28/src/deployml/utils/infracost.py +323 -0
- deployml_core-0.0.28/src/deployml/utils/kubernetes_gke.py +393 -0
- deployml_core-0.0.28/src/deployml/utils/kubernetes_local.py +454 -0
- deployml_core-0.0.28/src/deployml/utils/menu.py +41 -0
- deployml_core-0.0.28/src/deployml/utils/teardown.py +52 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Drew Hoang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deployml-core
|
|
3
|
+
Version: 0.0.28
|
|
4
|
+
Summary: Infra for academia
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Drew Hoang
|
|
7
|
+
Author-email: codentell@gmail.com
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: google-cloud-storage (>=3.1.1,<4.0.0)
|
|
15
|
+
Requires-Dist: ipython (>=9.0.0)
|
|
16
|
+
Requires-Dist: jinja2
|
|
17
|
+
Requires-Dist: jupyter (>=1.0.0)
|
|
18
|
+
Requires-Dist: pandas (>=2.2.1)
|
|
19
|
+
Requires-Dist: pyyaml
|
|
20
|
+
Requires-Dist: requests (>=2.28.0)
|
|
21
|
+
Requires-Dist: rich
|
|
22
|
+
Requires-Dist: typer
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# deployml
|
|
26
|
+
Infrastructure for academia with cost analysis
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- 🏗️ **Infrastructure as Code**: Deploy ML infrastructure using Terraform
|
|
31
|
+
- 💰 **Cost Analysis**: Integrated infracost analysis before deployment
|
|
32
|
+
- ☁️ **Multi-Cloud Support**: GCP, AWS, and more
|
|
33
|
+
- 🔬 **ML-Focused**: Pre-configured for MLflow, experiment tracking, and model registry
|
|
34
|
+
- 🛡️ **Production Ready**: Security best practices and service account management
|
|
35
|
+
|
|
36
|
+
## Instructions
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
poetry install
|
|
40
|
+
poetry run deployml doctor
|
|
41
|
+
poetry run deployml deploy --config-path your-config.yaml
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
docker build --platform=linux/amd64 -t gcr.io/mlops-intro-461805/mlflow/mlflow:latest .
|
|
45
|
+
|
|
46
|
+
gcloud auth configure-docker docker push gcr.io/PROJECT_ID/mlflow-app:latest
|
|
47
|
+
|
|
48
|
+
## Cost Analysis Integration
|
|
49
|
+
|
|
50
|
+
deployml integrates with [infracost](https://www.infracost.io/) to provide cost estimates before deployment:
|
|
51
|
+
|
|
52
|
+
### Installation
|
|
53
|
+
```bash
|
|
54
|
+
brew install infracost
|
|
55
|
+
```
|
|
56
|
+
Once installed you will need to create a free [infracost](https://www.infracost.io/) account before creating your API key.
|
|
57
|
+
|
|
58
|
+
To generate your infracost API key run the following command:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
infracost auth login
|
|
62
|
+
```
|
|
63
|
+
If you want to retrieve your API key use:
|
|
64
|
+
```bash
|
|
65
|
+
infracost configure get api_key
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Cost Analysis Configuration
|
|
69
|
+
Add cost analysis settings to your YAML configuration:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
name: "my-mlops-stack"
|
|
73
|
+
cost_analysis:
|
|
74
|
+
enabled: true # Enable/disable cost analysis (default: true)
|
|
75
|
+
warning_threshold: 100.0 # Warn if monthly cost exceeds this amount
|
|
76
|
+
currency: "USD" # Currency for cost display
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Cloud Run Service Account Setup
|
|
80
|
+
|
|
81
|
+
When deploying MLflow, you must specify the service account email that Cloud Run will use. This service account must have permission to access the artifact bucket.
|
|
82
|
+
|
|
83
|
+
### How to get a service account email
|
|
84
|
+
|
|
85
|
+
1. List your service accounts:
|
|
86
|
+
```sh
|
|
87
|
+
gcloud iam service-accounts list
|
|
88
|
+
```
|
|
89
|
+
2. (Recommended) Create a dedicated service account for MLflow if you don't have one:
|
|
90
|
+
```sh
|
|
91
|
+
gcloud iam service-accounts create mlflow-cloudrun --display-name "MLflow Cloud Run Service Account"
|
|
92
|
+
```
|
|
93
|
+
3. Find the email for your service account (it will look like `mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com`).
|
|
94
|
+
|
|
95
|
+
### Grant the service account permissions
|
|
96
|
+
|
|
97
|
+
The Terraform module will automatically grant the service account the required permissions on the artifact bucket.
|
|
98
|
+
|
|
99
|
+
### Deploying with Terraform
|
|
100
|
+
|
|
101
|
+
Pass the service account email as a variable:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
terraform apply -var="cloud_run_service_account=mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Or, if using the CLI, ensure it passes this variable to Terraform.
|
|
108
|
+
|
|
109
|
+
### Why this is needed
|
|
110
|
+
|
|
111
|
+
The MLflow server (running on Cloud Run) needs permission to list and read artifacts in the GCS bucket. This setup ensures the MLflow UI works for all users without manual permission fixes.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# deployml
|
|
2
|
+
Infrastructure for academia with cost analysis
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
|
|
6
|
+
- 🏗️ **Infrastructure as Code**: Deploy ML infrastructure using Terraform
|
|
7
|
+
- 💰 **Cost Analysis**: Integrated infracost analysis before deployment
|
|
8
|
+
- ☁️ **Multi-Cloud Support**: GCP, AWS, and more
|
|
9
|
+
- 🔬 **ML-Focused**: Pre-configured for MLflow, experiment tracking, and model registry
|
|
10
|
+
- 🛡️ **Production Ready**: Security best practices and service account management
|
|
11
|
+
|
|
12
|
+
## Instructions
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
poetry install
|
|
16
|
+
poetry run deployml doctor
|
|
17
|
+
poetry run deployml deploy --config-path your-config.yaml
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
docker build --platform=linux/amd64 -t gcr.io/mlops-intro-461805/mlflow/mlflow:latest .
|
|
21
|
+
|
|
22
|
+
gcloud auth configure-docker docker push gcr.io/PROJECT_ID/mlflow-app:latest
|
|
23
|
+
|
|
24
|
+
## Cost Analysis Integration
|
|
25
|
+
|
|
26
|
+
deployml integrates with [infracost](https://www.infracost.io/) to provide cost estimates before deployment:
|
|
27
|
+
|
|
28
|
+
### Installation
|
|
29
|
+
```bash
|
|
30
|
+
brew install infracost
|
|
31
|
+
```
|
|
32
|
+
Once installed you will need to create a free [infracost](https://www.infracost.io/) account before creating your API key.
|
|
33
|
+
|
|
34
|
+
To generate your infracost API key run the following command:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
infracost auth login
|
|
38
|
+
```
|
|
39
|
+
If you want to retrieve your API key use:
|
|
40
|
+
```bash
|
|
41
|
+
infracost configure get api_key
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Cost Analysis Configuration
|
|
45
|
+
Add cost analysis settings to your YAML configuration:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
name: "my-mlops-stack"
|
|
49
|
+
cost_analysis:
|
|
50
|
+
enabled: true # Enable/disable cost analysis (default: true)
|
|
51
|
+
warning_threshold: 100.0 # Warn if monthly cost exceeds this amount
|
|
52
|
+
currency: "USD" # Currency for cost display
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Cloud Run Service Account Setup
|
|
56
|
+
|
|
57
|
+
When deploying MLflow, you must specify the service account email that Cloud Run will use. This service account must have permission to access the artifact bucket.
|
|
58
|
+
|
|
59
|
+
### How to get a service account email
|
|
60
|
+
|
|
61
|
+
1. List your service accounts:
|
|
62
|
+
```sh
|
|
63
|
+
gcloud iam service-accounts list
|
|
64
|
+
```
|
|
65
|
+
2. (Recommended) Create a dedicated service account for MLflow if you don't have one:
|
|
66
|
+
```sh
|
|
67
|
+
gcloud iam service-accounts create mlflow-cloudrun --display-name "MLflow Cloud Run Service Account"
|
|
68
|
+
```
|
|
69
|
+
3. Find the email for your service account (it will look like `mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com`).
|
|
70
|
+
|
|
71
|
+
### Grant the service account permissions
|
|
72
|
+
|
|
73
|
+
The Terraform module will automatically grant the service account the required permissions on the artifact bucket.
|
|
74
|
+
|
|
75
|
+
### Deploying with Terraform
|
|
76
|
+
|
|
77
|
+
Pass the service account email as a variable:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
terraform apply -var="cloud_run_service_account=mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or, if using the CLI, ensure it passes this variable to Terraform.
|
|
84
|
+
|
|
85
|
+
### Why this is needed
|
|
86
|
+
|
|
87
|
+
The MLflow server (running on Cloud Run) needs permission to list and read artifacts in the GCS bucket. This setup ensures the MLflow UI works for all users without manual permission fixes.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
[project]
|
|
3
|
+
name = "deployml-core"
|
|
4
|
+
version = "0.0.28"
|
|
5
|
+
description = "Infra for academia"
|
|
6
|
+
authors = [
|
|
7
|
+
{name = "Drew Hoang", email = "codentell@gmail.com"},
|
|
8
|
+
{name = "Jarvin Bayona", email = "jarvin.bayona@gmail.com"},
|
|
9
|
+
{name = "Grant Nitta", email = "gtnitta@gmail.com"},
|
|
10
|
+
{name = "Robert Clements", email = "rclements@usfca.edu"}
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"typer",
|
|
16
|
+
"pyyaml",
|
|
17
|
+
"jinja2",
|
|
18
|
+
"rich",
|
|
19
|
+
"google-cloud-storage>=3.1.1,<4.0.0",
|
|
20
|
+
"ipython>=9.0.0",
|
|
21
|
+
"jupyter>=1.0.0",
|
|
22
|
+
"requests>=2.28.0",
|
|
23
|
+
"pandas>=2.2.1"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
deployml = "deployml.cli.cli:main"
|
|
28
|
+
|
|
29
|
+
[tool.poetry]
|
|
30
|
+
packages = [{include = "deployml", from = "src"}]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
36
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Import notebook functionality for easy access
|
|
2
|
+
from .notebook import deploy, load, DeploymentStack, ServiceURLs
|
|
3
|
+
|
|
4
|
+
# Import diagnostics for easy access
|
|
5
|
+
from .diagnostics import run_doctor, check_system, DeployMLDoctor
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'deploy',
|
|
9
|
+
'load',
|
|
10
|
+
'DeploymentStack',
|
|
11
|
+
'ServiceURLs',
|
|
12
|
+
'run_doctor',
|
|
13
|
+
'check_system',
|
|
14
|
+
'DeployMLDoctor'
|
|
15
|
+
]
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeployML Python API
|
|
3
|
+
|
|
4
|
+
Programmatic interface for managing DeployML deployments and teardown schedules.
|
|
5
|
+
|
|
6
|
+
Example usage:
|
|
7
|
+
import deployml.api as api
|
|
8
|
+
|
|
9
|
+
# Get teardown status
|
|
10
|
+
status = api.get_teardown_status(
|
|
11
|
+
project_id="my-project",
|
|
12
|
+
region="us-west1",
|
|
13
|
+
workspace_name="my-stack"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Update teardown schedule
|
|
17
|
+
result = api.update_teardown_schedule(
|
|
18
|
+
project_id="my-project",
|
|
19
|
+
region="us-west1",
|
|
20
|
+
workspace_name="my-stack",
|
|
21
|
+
duration_hours=6
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Cancel teardown
|
|
25
|
+
api.cancel_teardown(
|
|
26
|
+
project_id="my-project",
|
|
27
|
+
region="us-west1",
|
|
28
|
+
workspace_name="my-stack"
|
|
29
|
+
)
|
|
30
|
+
"""
|
|
31
|
+
import json
|
|
32
|
+
import subprocess
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from datetime import datetime, timedelta, timezone
|
|
35
|
+
from typing import Optional, Dict, Any
|
|
36
|
+
|
|
37
|
+
from .utils.teardown import (
|
|
38
|
+
calculate_cron_from_timestamp,
|
|
39
|
+
load_deployment_metadata,
|
|
40
|
+
save_deployment_metadata
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_teardown_status(
|
|
45
|
+
project_id: str,
|
|
46
|
+
region: str,
|
|
47
|
+
workspace_name: str,
|
|
48
|
+
deployml_dir: Optional[Path] = None
|
|
49
|
+
) -> Dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Get teardown status from Cloud Scheduler.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
project_id: GCP project ID
|
|
55
|
+
region: GCP region
|
|
56
|
+
workspace_name: Workspace name
|
|
57
|
+
deployml_dir: Optional path to deployment directory for metadata
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary with status information including:
|
|
61
|
+
- exists: bool - Whether the scheduler job exists
|
|
62
|
+
- state: str - Job state (ENABLED, PAUSED, etc.)
|
|
63
|
+
- schedule: str - Cron schedule
|
|
64
|
+
- schedule_time: datetime - Next scheduled execution time
|
|
65
|
+
- time_zone: str - Timezone
|
|
66
|
+
- last_attempt_time: Optional[datetime] - Last execution time
|
|
67
|
+
- metadata: Optional[Dict] - Local metadata if available
|
|
68
|
+
"""
|
|
69
|
+
scheduler_job_name = f"deployml-teardown-{workspace_name}"
|
|
70
|
+
|
|
71
|
+
result = subprocess.run(
|
|
72
|
+
["gcloud", "scheduler", "jobs", "describe", scheduler_job_name,
|
|
73
|
+
"--project", project_id, "--location", region, "--format", "json"],
|
|
74
|
+
capture_output=True,
|
|
75
|
+
text=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
status = {
|
|
79
|
+
"exists": False,
|
|
80
|
+
"scheduler_job_name": scheduler_job_name,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Load local metadata if available
|
|
84
|
+
if deployml_dir:
|
|
85
|
+
metadata = load_deployment_metadata(deployml_dir)
|
|
86
|
+
status["metadata"] = metadata
|
|
87
|
+
|
|
88
|
+
if result.returncode != 0:
|
|
89
|
+
return status
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
job_info = json.loads(result.stdout)
|
|
93
|
+
status["exists"] = True
|
|
94
|
+
status["state"] = job_info.get("state", "UNKNOWN")
|
|
95
|
+
status["schedule"] = job_info.get("schedule", "")
|
|
96
|
+
status["time_zone"] = job_info.get("timeZone", "UTC")
|
|
97
|
+
|
|
98
|
+
# Parse schedule time
|
|
99
|
+
schedule_time = job_info.get("scheduleTime", "")
|
|
100
|
+
if schedule_time:
|
|
101
|
+
time_str = schedule_time.replace('Z', '+00:00')
|
|
102
|
+
status["schedule_time"] = datetime.fromisoformat(time_str)
|
|
103
|
+
|
|
104
|
+
# Parse last attempt time
|
|
105
|
+
last_attempt_time = job_info.get("lastAttemptTime", "")
|
|
106
|
+
if last_attempt_time and last_attempt_time != "1970-01-01T00:00:00Z":
|
|
107
|
+
time_str = last_attempt_time.replace('Z', '+00:00')
|
|
108
|
+
status["last_attempt_time"] = datetime.fromisoformat(time_str)
|
|
109
|
+
|
|
110
|
+
return status
|
|
111
|
+
except json.JSONDecodeError:
|
|
112
|
+
return status
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def update_teardown_schedule(
|
|
116
|
+
project_id: str,
|
|
117
|
+
region: str,
|
|
118
|
+
workspace_name: str,
|
|
119
|
+
duration_hours: int,
|
|
120
|
+
deployml_dir: Optional[Path] = None,
|
|
121
|
+
time_zone: str = "UTC"
|
|
122
|
+
) -> Dict[str, Any]:
|
|
123
|
+
"""
|
|
124
|
+
Update teardown schedule programmatically.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
project_id: GCP project ID
|
|
128
|
+
region: GCP region
|
|
129
|
+
workspace_name: Workspace name
|
|
130
|
+
duration_hours: Hours until teardown
|
|
131
|
+
deployml_dir: Optional path to deployment directory for metadata
|
|
132
|
+
time_zone: Timezone (default: UTC)
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary with update result:
|
|
136
|
+
- success: bool - Whether update succeeded
|
|
137
|
+
- scheduled_time: datetime - New scheduled time
|
|
138
|
+
- cron_schedule: str - Cron expression
|
|
139
|
+
- error: Optional[str] - Error message if failed
|
|
140
|
+
"""
|
|
141
|
+
scheduler_job_name = f"deployml-teardown-{workspace_name}"
|
|
142
|
+
|
|
143
|
+
# Check if job exists and get current timezone
|
|
144
|
+
result = subprocess.run(
|
|
145
|
+
["gcloud", "scheduler", "jobs", "describe", scheduler_job_name,
|
|
146
|
+
"--project", project_id, "--location", region, "--format", "json"],
|
|
147
|
+
capture_output=True,
|
|
148
|
+
text=True,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if result.returncode != 0:
|
|
152
|
+
return {
|
|
153
|
+
"success": False,
|
|
154
|
+
"error": f"Cloud Scheduler job not found: {scheduler_job_name}"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Get timezone from existing job if not provided
|
|
158
|
+
try:
|
|
159
|
+
job_info = json.loads(result.stdout)
|
|
160
|
+
time_zone = job_info.get("timeZone", time_zone)
|
|
161
|
+
except json.JSONDecodeError:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
# Calculate new teardown time
|
|
165
|
+
now = datetime.now(timezone.utc)
|
|
166
|
+
teardown_at = now + timedelta(hours=duration_hours)
|
|
167
|
+
teardown_scheduled_timestamp = int(teardown_at.timestamp())
|
|
168
|
+
new_cron_schedule = calculate_cron_from_timestamp(teardown_scheduled_timestamp)
|
|
169
|
+
|
|
170
|
+
# Update Cloud Scheduler job
|
|
171
|
+
update_result = subprocess.run(
|
|
172
|
+
[
|
|
173
|
+
"gcloud", "scheduler", "jobs", "update", "http", scheduler_job_name,
|
|
174
|
+
"--location", region,
|
|
175
|
+
"--schedule", new_cron_schedule,
|
|
176
|
+
"--time-zone", time_zone,
|
|
177
|
+
"--project", project_id,
|
|
178
|
+
"--quiet"
|
|
179
|
+
],
|
|
180
|
+
capture_output=True,
|
|
181
|
+
text=True,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if update_result.returncode != 0:
|
|
185
|
+
return {
|
|
186
|
+
"success": False,
|
|
187
|
+
"error": update_result.stderr,
|
|
188
|
+
"scheduled_time": teardown_at,
|
|
189
|
+
"cron_schedule": new_cron_schedule
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Update local metadata if directory provided
|
|
193
|
+
if deployml_dir:
|
|
194
|
+
metadata = load_deployment_metadata(deployml_dir) or {}
|
|
195
|
+
metadata.update({
|
|
196
|
+
"deployed_at": metadata.get("deployed_at", now.isoformat()),
|
|
197
|
+
"teardown_scheduled_at": teardown_at.isoformat(),
|
|
198
|
+
"teardown_enabled": True,
|
|
199
|
+
"duration_hours": duration_hours,
|
|
200
|
+
"scheduler_job_name": scheduler_job_name
|
|
201
|
+
})
|
|
202
|
+
save_deployment_metadata(deployml_dir, metadata)
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
"success": True,
|
|
206
|
+
"scheduled_time": teardown_at,
|
|
207
|
+
"cron_schedule": new_cron_schedule,
|
|
208
|
+
"time_zone": time_zone
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def cancel_teardown(
|
|
213
|
+
project_id: str,
|
|
214
|
+
region: str,
|
|
215
|
+
workspace_name: str,
|
|
216
|
+
deployml_dir: Optional[Path] = None
|
|
217
|
+
) -> Dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Cancel scheduled teardown programmatically.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
project_id: GCP project ID
|
|
223
|
+
region: GCP region
|
|
224
|
+
workspace_name: Workspace name
|
|
225
|
+
deployml_dir: Optional path to deployment directory for metadata
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Dictionary with cancellation result:
|
|
229
|
+
- success: bool - Whether cancellation succeeded
|
|
230
|
+
- error: Optional[str] - Error message if failed
|
|
231
|
+
"""
|
|
232
|
+
scheduler_job_name = f"deployml-teardown-{workspace_name}"
|
|
233
|
+
|
|
234
|
+
result = subprocess.run(
|
|
235
|
+
["gcloud", "scheduler", "jobs", "delete", scheduler_job_name,
|
|
236
|
+
"--project", project_id, "--location", region, "--quiet"],
|
|
237
|
+
capture_output=True,
|
|
238
|
+
text=True,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if result.returncode != 0:
|
|
242
|
+
return {
|
|
243
|
+
"success": False,
|
|
244
|
+
"error": result.stderr
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Update metadata if directory provided
|
|
248
|
+
if deployml_dir:
|
|
249
|
+
metadata = load_deployment_metadata(deployml_dir)
|
|
250
|
+
if metadata:
|
|
251
|
+
metadata["teardown_enabled"] = False
|
|
252
|
+
save_deployment_metadata(deployml_dir, metadata)
|
|
253
|
+
|
|
254
|
+
return {"success": True}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
__all__ = [
|
|
258
|
+
'get_teardown_status',
|
|
259
|
+
'update_teardown_schedule',
|
|
260
|
+
'cancel_teardown'
|
|
261
|
+
]
|
|
262
|
+
|
|
File without changes
|