Skip to content

Commit 651fb37

Browse files
authored
Merge pull request #180 from morph-data/release/v0.3.0
2 parents 40aa4ab + 5a3000b commit 651fb37

4 files changed

Lines changed: 184 additions & 36 deletions

File tree

core/morph/config/project.py

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515

1616
class BuildConfig(BaseModel):
17-
use_custom_dockerfile: bool = False
1817
runtime: Optional[str] = None
1918
framework: Optional[str] = "morph"
2019
package_manager: Optional[str] = None
@@ -89,9 +88,115 @@ def save_project(project_root: str, project: MorphProject) -> None:
8988
old_config_path = os.path.join(project_root, "morph_project.yaml")
9089
if os.path.exists(old_config_path):
9190
with open(old_config_path, "w") as f:
92-
yaml.safe_dump(project.model_dump(), f)
91+
f.write(dump_project_yaml(project))
9392
return
9493

9594
config_path = os.path.join(project_root, "morph_project.yml")
9695
with open(config_path, "w") as f:
97-
yaml.safe_dump(project.model_dump(), f)
96+
f.write(dump_project_yaml(project))
97+
98+
99+
def dump_project_yaml(project: MorphProject) -> str:
100+
source_paths = "\n- ".join([""] + project.source_paths)
101+
102+
# Default values
103+
build_runtime = ""
104+
build_framework = ""
105+
build_package_manager = ""
106+
build_context = "."
107+
build_args_str = "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value"
108+
deployment_provider = "aws"
109+
deployment_aws_region = "us-east-1"
110+
deployment_aws_memory = "1024"
111+
deployment_aws_timeout = "300"
112+
deployment_aws_concurrency = "1"
113+
deployment_gcp_region = "us-central1"
114+
deployment_gcp_memory = "1Gi"
115+
deployment_gcp_cpu = "1"
116+
deployment_gcp_concurrency = "80"
117+
deployment_gcp_timeout = "300"
118+
119+
# Set values if build exists
120+
if project.build:
121+
if project.build.runtime:
122+
build_runtime = project.build.runtime or ""
123+
if project.build.framework:
124+
build_framework = project.build.framework or ""
125+
if project.build.package_manager:
126+
build_package_manager = project.build.package_manager or ""
127+
if project.build.context:
128+
build_context = f"{project.build.context}" or "."
129+
if project.build.build_args:
130+
build_args_items = []
131+
for key, value in project.build.build_args.items():
132+
build_args_items.append(f"{key}={value}")
133+
build_args_str = (
134+
"\n # - ".join([""] + build_args_items)
135+
if build_args_items
136+
else "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value"
137+
)
138+
139+
# Set values if deployment exists
140+
if project.deployment:
141+
if project.deployment.provider:
142+
deployment_provider = project.deployment.provider or "aws"
143+
if project.deployment.aws:
144+
deployment_aws_region = project.deployment.aws.get("region") or "us-east-1"
145+
deployment_aws_memory = project.deployment.aws.get("memory") or "1024"
146+
deployment_aws_timeout = project.deployment.aws.get("timeout") or "300"
147+
deployment_aws_concurrency = (
148+
project.deployment.aws.get("concurrency") or "1"
149+
)
150+
if project.deployment.gcp:
151+
deployment_gcp_region = (
152+
project.deployment.gcp.get("region") or "us-central1"
153+
)
154+
deployment_gcp_memory = project.deployment.gcp.get("memory") or "1Gi"
155+
deployment_gcp_cpu = project.deployment.gcp.get("cpu") or "1"
156+
deployment_gcp_concurrency = (
157+
project.deployment.gcp.get("concurrency") or "80"
158+
)
159+
deployment_gcp_timeout = project.deployment.gcp.get("timeout") or "300"
160+
else:
161+
# Use default DeploymentConfig
162+
deployment_provider = "aws"
163+
164+
return f"""
165+
version: '1'
166+
167+
# Framework Settings
168+
default_connection: {project.default_connection}
169+
source_paths:{source_paths}
170+
171+
# Cloud Settings
172+
# profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials`
173+
# project_id: {project.project_id or "null"}
174+
175+
# Build Settings
176+
build:
177+
# These settings are required when there is no Dockerfile in the project root.
178+
# They define the environment in which the project will be built
179+
runtime: {build_runtime} # python3.9, python3.10, python3.11, python3.12
180+
framework: {build_framework}
181+
package_manager: {build_package_manager} # pip, poetry, uv
182+
# These settings are required when there is a Dockerfile in the project root.
183+
# They define how the Docker image will be built
184+
# context: {build_context}
185+
# build_args:{build_args_str}
186+
187+
# Deployment Settings
188+
deployment:
189+
provider: {deployment_provider} # aws or gcp (default is aws)
190+
# These settings are used only when you want to customize the deployment settings
191+
# aws:
192+
# region: {deployment_aws_region}
193+
# memory: {deployment_aws_memory}
194+
# timeout: {deployment_aws_timeout}
195+
# concurrency: {deployment_aws_concurrency}
196+
# gcp:
197+
# region: {deployment_gcp_region}
198+
# memory: {deployment_gcp_memory}
199+
# cpu: {deployment_gcp_cpu}
200+
# concurrency: {deployment_gcp_concurrency}
201+
# timeout: {deployment_gcp_timeout}
202+
"""

core/morph/task/deploy.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88

99
import click
1010
import requests
11+
from tqdm import tqdm
12+
1113
from morph.api.cloud.client import MorphApiKeyClientImpl
1214
from morph.api.cloud.types import EnvVarObject
1315
from morph.cli.flags import Flags
1416
from morph.config.project import load_project
1517
from morph.task.base import BaseTask
1618
from morph.task.utils.file_upload import FileWithProgress
19+
from morph.task.utils.load_dockerfile import get_dockerfile_from_api
1720
from morph.task.utils.morph import find_project_root_dir
18-
from tqdm import tqdm
1921

2022

2123
class DeployTask(BaseTask):
@@ -48,10 +50,31 @@ def __init__(self, args: Flags):
4850
self.package_manager = self.project.package_manager
4951

5052
# Check Dockerfile existence
51-
self.dockerfile = os.path.join(self.project_root, "Dockerfile")
52-
if not os.path.exists(self.dockerfile):
53-
click.echo(click.style(f"Error: {self.dockerfile} not found", fg="red"))
54-
sys.exit(1)
53+
self.dockerfile_path = os.path.join(self.project_root, "Dockerfile")
54+
self.use_custom_dockerfile = os.path.exists(self.dockerfile_path)
55+
if self.use_custom_dockerfile:
56+
provider = "aws"
57+
if (
58+
self.project.deployment is not None
59+
and self.project.deployment.provider is not None
60+
):
61+
provider = self.project.deployment.provider or "aws"
62+
if self.project.build is None:
63+
dockerfile, dockerignore = get_dockerfile_from_api(
64+
"morph", provider, None, None
65+
)
66+
else:
67+
dockerfile, dockerignore = get_dockerfile_from_api(
68+
self.project.build.framework or "morph",
69+
provider,
70+
self.project.build.package_manager,
71+
self.project.build.runtime,
72+
)
73+
with open(self.dockerfile_path, "w") as f:
74+
f.write(dockerfile)
75+
dockerignore_path = os.path.join(self.project_root, ".dockerignore")
76+
with open(dockerignore_path, "w") as f:
77+
f.write(dockerignore)
5578

5679
# Check Docker availability
5780
try:
@@ -105,7 +128,7 @@ def run(self):
105128
click.echo(click.style("Initiating deployment sequence...", fg="blue"))
106129

107130
# 1. Build the source code
108-
self._copy_and_build_source()
131+
self._build_source()
109132

110133
# 2. Build the Docker image
111134
click.echo(click.style("Building Docker image...", fg="blue"))
@@ -387,31 +410,8 @@ def _validate_api_key(self):
387410
)
388411
sys.exit(1)
389412

390-
def _copy_and_build_source(self):
391-
click.echo(click.style("Building frontend...", fg="blue"))
392-
try:
393-
# Run npm install and build
394-
subprocess.run(
395-
["npm", "install"],
396-
cwd=self.project_root,
397-
check=True,
398-
shell=True if sys.platform == "win32" else False,
399-
)
400-
subprocess.run(
401-
["npm", "run", "build"],
402-
cwd=self.project_root,
403-
check=True,
404-
shell=True if sys.platform == "win32" else False,
405-
)
406-
407-
except subprocess.CalledProcessError as e:
408-
click.echo(click.style(f"Error building frontend: {str(e)}", fg="red"))
409-
sys.exit(1)
410-
except Exception as e:
411-
click.echo(click.style(f"Unexpected error: {str(e)}", fg="red"))
412-
sys.exit(1)
413-
414-
click.echo(click.style("Building backend...", fg="blue"))
413+
def _build_source(self):
414+
click.echo(click.style("Compiling morph project...", fg="blue"))
415415
try:
416416
# Compile the morph project
417417
subprocess.run(
@@ -437,7 +437,7 @@ def _build_docker_image(self) -> str:
437437
"-t",
438438
self.image_name,
439439
"-f",
440-
self.dockerfile,
440+
self.dockerfile_path,
441441
self.project_root,
442442
]
443443
if self.no_cache:
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import Any, Dict, Optional, Tuple
2+
3+
import requests
4+
5+
6+
def get_dockerfile_from_api(
7+
framework: str,
8+
provider: str,
9+
package_manager: Optional[str] = None,
10+
runtime: Optional[str] = None,
11+
) -> Tuple[str, str]:
12+
"""
13+
Fetch dockerfile and dockerignore from the Morph API.
14+
15+
Args:
16+
framework: The framework to get the dockerfile for
17+
provider: The provider to get the dockerfile for
18+
package_manager: Optional package manager to use
19+
runtime: Optional runtime to use
20+
21+
Returns:
22+
Tuple containing (dockerfile, dockerignore)
23+
"""
24+
url = f"https://dockerfile-template.morph-cb9.workers.dev/dockerfile/{framework}"
25+
26+
params: Dict[str, Any] = {
27+
"provider": provider,
28+
}
29+
if package_manager:
30+
params["packageManager"] = package_manager
31+
if runtime:
32+
params["runtime"] = runtime
33+
34+
response = requests.get(url, params=params)
35+
36+
response.raise_for_status()
37+
38+
data = response.json()
39+
40+
if "error" in data:
41+
raise ValueError(data["error"])
42+
43+
return data["dockerfile"], data["dockerignore"]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "morph-data"
3-
version = "0.2.1"
3+
version = "0.3.0"
44
description = "Morph is a python-centric full-stack framework for building and deploying data apps."
55
authors = ["Morph <contact@morphdb.io>"]
66
packages = [

0 commit comments

Comments
 (0)