Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f8b3643
get dockerfile by calling api
shibatanaoto Mar 20, 2025
5e699bf
update version as v0.3.0rc1
shibatanaoto Mar 21, 2025
e6632e4
Merge pull request #168 from morph-data/chore/issue-version-v0.3.0rc1
tslcls Mar 21, 2025
fa313f8
Merge pull request #169 from morph-data/enhancement/use-custom-docker…
tslcls Mar 21, 2025
b111712
fix initial morph_project.yml
shibatanaoto Mar 21, 2025
a8e639b
Merge pull request #171 from morph-data/enhancement/use-custom-docker…
tslcls Mar 21, 2025
54bdda5
fix initial morph_project.yml
shibatanaoto Mar 21, 2025
959793c
Merge pull request #172 from morph-data/fix/initial-morph-project-yml
tslcls Mar 21, 2025
86622ec
change dockerfile api call
shibatanaoto Mar 21, 2025
b40590b
Merge pull request #173 from morph-data/fix/dockerfile-api
tslcls Mar 21, 2025
c3a49fe
issue v0.3.0rc2
shibatanaoto Mar 21, 2025
9ff4cac
Merge pull request #174 from morph-data/chore/update-version-v0.3.0rc2
tslcls Mar 21, 2025
79746ed
Merge pull request #176 from morph-data/develop
atsuki44 Mar 21, 2025
dd7f183
change logic for checking use_custom_dockerfile
shibatanaoto Mar 22, 2025
b508198
change initial morph_project.yml
shibatanaoto Mar 22, 2025
0552e28
fix version value as string
shibatanaoto Mar 22, 2025
d0626bd
Merge pull request #177 from morph-data/fix/change-use-custom-dockerfile
KT83 Mar 22, 2025
521044b
update version as v0.3.0rc3
shibatanaoto Mar 23, 2025
7e5896e
Merge pull request #178 from morph-data/chore/update-v0.3.0rc3
KT83 Mar 23, 2025
52e1ceb
update version as v0.3.0
shibatanaoto Mar 24, 2025
5a3000b
Merge pull request #179 from morph-data/chore/update-version-v0.3.0
tslcls Mar 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 108 additions & 3 deletions core/morph/config/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@


class BuildConfig(BaseModel):
use_custom_dockerfile: bool = False
runtime: Optional[str] = None
framework: Optional[str] = "morph"
package_manager: Optional[str] = None
Expand Down Expand Up @@ -89,9 +88,115 @@ def save_project(project_root: str, project: MorphProject) -> None:
old_config_path = os.path.join(project_root, "morph_project.yaml")
if os.path.exists(old_config_path):
with open(old_config_path, "w") as f:
yaml.safe_dump(project.model_dump(), f)
f.write(dump_project_yaml(project))
return

config_path = os.path.join(project_root, "morph_project.yml")
with open(config_path, "w") as f:
yaml.safe_dump(project.model_dump(), f)
f.write(dump_project_yaml(project))


def dump_project_yaml(project: MorphProject) -> str:
source_paths = "\n- ".join([""] + project.source_paths)

# Default values
build_runtime = ""
build_framework = ""
build_package_manager = ""
build_context = "."
build_args_str = "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value"
deployment_provider = "aws"
deployment_aws_region = "us-east-1"
deployment_aws_memory = "1024"
deployment_aws_timeout = "300"
deployment_aws_concurrency = "1"
deployment_gcp_region = "us-central1"
deployment_gcp_memory = "1Gi"
deployment_gcp_cpu = "1"
deployment_gcp_concurrency = "80"
deployment_gcp_timeout = "300"

# Set values if build exists
if project.build:
if project.build.runtime:
build_runtime = project.build.runtime or ""
if project.build.framework:
build_framework = project.build.framework or ""
if project.build.package_manager:
build_package_manager = project.build.package_manager or ""
if project.build.context:
build_context = f"{project.build.context}" or "."
if project.build.build_args:
build_args_items = []
for key, value in project.build.build_args.items():
build_args_items.append(f"{key}={value}")
build_args_str = (
"\n # - ".join([""] + build_args_items)
if build_args_items
else "\n # - ARG_NAME=value\n # - ANOTHER_ARG=value"
)

# Set values if deployment exists
if project.deployment:
if project.deployment.provider:
deployment_provider = project.deployment.provider or "aws"
if project.deployment.aws:
deployment_aws_region = project.deployment.aws.get("region") or "us-east-1"
deployment_aws_memory = project.deployment.aws.get("memory") or "1024"
deployment_aws_timeout = project.deployment.aws.get("timeout") or "300"
deployment_aws_concurrency = (
project.deployment.aws.get("concurrency") or "1"
)
if project.deployment.gcp:
deployment_gcp_region = (
project.deployment.gcp.get("region") or "us-central1"
)
deployment_gcp_memory = project.deployment.gcp.get("memory") or "1Gi"
deployment_gcp_cpu = project.deployment.gcp.get("cpu") or "1"
deployment_gcp_concurrency = (
project.deployment.gcp.get("concurrency") or "80"
)
deployment_gcp_timeout = project.deployment.gcp.get("timeout") or "300"
else:
# Use default DeploymentConfig
deployment_provider = "aws"

return f"""
version: '1'

# Framework Settings
default_connection: {project.default_connection}
source_paths:{source_paths}

# Cloud Settings
# profile: {project.profile} # Defined in the Profile Section in `~/.morph/credentials`
# project_id: {project.project_id or "null"}

# Build Settings
build:
# These settings are required when there is no Dockerfile in the project root.
# They define the environment in which the project will be built
runtime: {build_runtime} # python3.9, python3.10, python3.11, python3.12
framework: {build_framework}
package_manager: {build_package_manager} # pip, poetry, uv
# These settings are required when there is a Dockerfile in the project root.
# They define how the Docker image will be built
# context: {build_context}
# build_args:{build_args_str}

# Deployment Settings
deployment:
provider: {deployment_provider} # aws or gcp (default is aws)
# These settings are used only when you want to customize the deployment settings
# aws:
# region: {deployment_aws_region}
# memory: {deployment_aws_memory}
# timeout: {deployment_aws_timeout}
# concurrency: {deployment_aws_concurrency}
# gcp:
# region: {deployment_gcp_region}
# memory: {deployment_gcp_memory}
# cpu: {deployment_gcp_cpu}
# concurrency: {deployment_gcp_concurrency}
# timeout: {deployment_gcp_timeout}
"""
64 changes: 32 additions & 32 deletions core/morph/task/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@

import click
import requests
from tqdm import tqdm

from morph.api.cloud.client import MorphApiKeyClientImpl
from morph.api.cloud.types import EnvVarObject
from morph.cli.flags import Flags
from morph.config.project import load_project
from morph.task.base import BaseTask
from morph.task.utils.file_upload import FileWithProgress
from morph.task.utils.load_dockerfile import get_dockerfile_from_api
from morph.task.utils.morph import find_project_root_dir
from tqdm import tqdm


class DeployTask(BaseTask):
Expand Down Expand Up @@ -48,10 +50,31 @@ def __init__(self, args: Flags):
self.package_manager = self.project.package_manager

# Check Dockerfile existence
self.dockerfile = os.path.join(self.project_root, "Dockerfile")
if not os.path.exists(self.dockerfile):
click.echo(click.style(f"Error: {self.dockerfile} not found", fg="red"))
sys.exit(1)
self.dockerfile_path = os.path.join(self.project_root, "Dockerfile")
self.use_custom_dockerfile = os.path.exists(self.dockerfile_path)
if self.use_custom_dockerfile:
provider = "aws"
if (
self.project.deployment is not None
and self.project.deployment.provider is not None
):
provider = self.project.deployment.provider or "aws"
if self.project.build is None:
dockerfile, dockerignore = get_dockerfile_from_api(
"morph", provider, None, None
)
else:
dockerfile, dockerignore = get_dockerfile_from_api(
self.project.build.framework or "morph",
provider,
self.project.build.package_manager,
self.project.build.runtime,
)
with open(self.dockerfile_path, "w") as f:
f.write(dockerfile)
dockerignore_path = os.path.join(self.project_root, ".dockerignore")
with open(dockerignore_path, "w") as f:
f.write(dockerignore)

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

# 1. Build the source code
self._copy_and_build_source()
self._build_source()

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

def _copy_and_build_source(self):
click.echo(click.style("Building frontend...", fg="blue"))
try:
# Run npm install and build
subprocess.run(
["npm", "install"],
cwd=self.project_root,
check=True,
shell=True if sys.platform == "win32" else False,
)
subprocess.run(
["npm", "run", "build"],
cwd=self.project_root,
check=True,
shell=True if sys.platform == "win32" else False,
)

except subprocess.CalledProcessError as e:
click.echo(click.style(f"Error building frontend: {str(e)}", fg="red"))
sys.exit(1)
except Exception as e:
click.echo(click.style(f"Unexpected error: {str(e)}", fg="red"))
sys.exit(1)

click.echo(click.style("Building backend...", fg="blue"))
def _build_source(self):
click.echo(click.style("Compiling morph project...", fg="blue"))
try:
# Compile the morph project
subprocess.run(
Expand All @@ -437,7 +437,7 @@ def _build_docker_image(self) -> str:
"-t",
self.image_name,
"-f",
self.dockerfile,
self.dockerfile_path,
self.project_root,
]
if self.no_cache:
Expand Down
43 changes: 43 additions & 0 deletions core/morph/task/utils/load_dockerfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any, Dict, Optional, Tuple

import requests


def get_dockerfile_from_api(
framework: str,
provider: str,
package_manager: Optional[str] = None,
runtime: Optional[str] = None,
) -> Tuple[str, str]:
"""
Fetch dockerfile and dockerignore from the Morph API.

Args:
framework: The framework to get the dockerfile for
provider: The provider to get the dockerfile for
package_manager: Optional package manager to use
runtime: Optional runtime to use

Returns:
Tuple containing (dockerfile, dockerignore)
"""
url = f"https://dockerfile-template.morph-cb9.workers.dev/dockerfile/{framework}"

params: Dict[str, Any] = {
"provider": provider,
}
if package_manager:
params["packageManager"] = package_manager
if runtime:
params["runtime"] = runtime

response = requests.get(url, params=params)

response.raise_for_status()

data = response.json()

if "error" in data:
raise ValueError(data["error"])

return data["dockerfile"], data["dockerignore"]
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "morph-data"
version = "0.2.1"
version = "0.3.0"
description = "Morph is a python-centric full-stack framework for building and deploying data apps."
authors = ["Morph <contact@morphdb.io>"]
packages = [
Expand Down