From f541b1e1a5b4dda261ee2ba76e6e761e3b1ccfe3 Mon Sep 17 00:00:00 2001 From: Marius Killinger Date: Wed, 29 Jan 2025 16:59:03 -0800 Subject: [PATCH] Fix build script --- pyproject.toml | 2 +- truss/contexts/docker_build_setup.py | 18 ++- truss/remote/baseten/api.py | 2 +- truss/templates/server.Dockerfile.jinja | 158 +++++++++++----------- truss/tests/remote/baseten/test_remote.py | 34 ++--- 5 files changed, 110 insertions(+), 104 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 22d49d1a7..cd30b732b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "truss" -version = "0.9.60rc102" +version = "0.9.60rc104" description = "A seamless bridge from model development to model delivery" license = "MIT" readme = "README.md" diff --git a/truss/contexts/docker_build_setup.py b/truss/contexts/docker_build_setup.py index e632646b5..4d618b6a6 100644 --- a/truss/contexts/docker_build_setup.py +++ b/truss/contexts/docker_build_setup.py @@ -11,26 +11,25 @@ from truss.truss_handle import truss_handle from truss.truss_handle.patch import signature -logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logging.basicConfig(stream=sys.stdout, level=logging.INFO) -# working_dir = pathlib.Path("/") -working_dir = pathlib.Path(os.getcwd()) +working_dir = pathlib.Path("/") TRUSS_SRC_DIR = working_dir / "build/model_scaffold" TRUSS_HASH_FILE = working_dir / "scaffold/truss_hash" -TRUSS_SIGNATURE_FILE = working_dir / "build/truss_signature" +TRUSS_SIGNATURE_FILE = working_dir / "scaffold/truss_signature" TRUSS_BUILD_CONTEXT_DIR = working_dir / "build/context" @click.command() @click.option("--truss_type", required=True) def docker_build_setup(truss_type: str) -> None: - print("Loading truss") + logging.info("Loading truss") tr = truss_handle.TrussHandle(TRUSS_SRC_DIR) - print("Truss is loaded") + logging.info("Truss is loaded") if patches_dir := os.environ.get("PATCHES_DIR"): - print("Applying patches") + logging.info("Applying patches") logger = logging.getLogger("patch_applier") patch_applier = TrussDirPatchApplier(TRUSS_SRC_DIR, logger) patches = json.loads(pathlib.Path(patches_dir).read_text()) @@ -38,14 +37,13 @@ def docker_build_setup(truss_type: str) -> None: # Important to do this before making changes to truss, we want # to capture hash of original truss. - print("Recording truss hash") + logging.info("Recording truss hash") TRUSS_HASH_FILE.write_text(directory_content_hash(TRUSS_SRC_DIR)) - print("Recording truss signature.") + logging.info("Recording truss signature.") sign = signature.calc_truss_signature(TRUSS_SRC_DIR) TRUSS_SIGNATURE_FILE.write_text(json.dumps(sign.to_dict())) - print("Setting up docker build context for truss.") if truss_type == "server_control": tr.live_reload(enable=True) else: diff --git a/truss/remote/baseten/api.py b/truss/remote/baseten/api.py index 796484cf3..81be1d91d 100644 --- a/truss/remote/baseten/api.py +++ b/truss/remote/baseten/api.py @@ -249,7 +249,7 @@ def deploy_chain_atomic( is_draft: {str(is_draft).lower()} entrypoint: {entrypoint_str} dependencies: [{dependencies_str}] - client_version: truss.version() + client_version: "{truss.version()}" ) {{ chain_id chain_deployment_id diff --git a/truss/templates/server.Dockerfile.jinja b/truss/templates/server.Dockerfile.jinja index bad44a8e7..5c89f6572 100644 --- a/truss/templates/server.Dockerfile.jinja +++ b/truss/templates/server.Dockerfile.jinja @@ -1,11 +1,13 @@ -{%- if model_cache %}{%- include "cache.Dockerfile.jinja" %}{%- endif %} +{%- if model_cache %} +{%- include "cache.Dockerfile.jinja" %} +{%- endif %} -{% extends "base.Dockerfile.jinja" -%} +{% extends "base.Dockerfile.jinja" %} -{% block base_image_patch -%} +{% block base_image_patch %} # If user base image is supplied in config, apply build commands from truss base image -{%- if config.base_image -%} - {%- if not config.docker_server -%} +{% if config.base_image %} + {%- if not config.docker_server %} ENV PYTHONUNBUFFERED="True" ENV DEBIAN_FRONTEND="noninteractive" @@ -15,108 +17,112 @@ RUN apt update && \ git \ curl \ ca-certificates \ - software-properties-common && \ - apt-get autoremove -y && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/* + software-properties-common \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* -COPY ./{{ base_server_requirements_filename }} {{ base_server_requirements_filename }} -RUN pip install -r {{ base_server_requirements_filename }} --no-cache-dir && rm -rf /root/.cache/pip - {%- endif -%} +COPY ./{{base_server_requirements_filename}} {{base_server_requirements_filename}} +RUN pip install -r {{base_server_requirements_filename}} --no-cache-dir && rm -rf /root/.cache/pip + {%- endif %} - {%- if config.live_reload and not config.docker_server -%} + {%- if config.live_reload and not config.docker_server%} RUN $PYTHON_EXECUTABLE -m venv -h >/dev/null \ - || { pythonVersion=$(echo $($PYTHON_EXECUTABLE --version) | cut -d" " -f2 | cut -d"." -f1,2) && \ - add-apt-repository -y ppa:deadsnakes/ppa && \ - apt update -y && apt install -y --no-install-recommends python$pythonVersion-venv && \ - apt-get autoremove -y && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/*; } -RUN ln -sf {{ config.base_image.python_executable_path }} /usr/local/bin/python - {%- endif -%} -{%- endif -%} -{% endblock -%} - -{% block install_requirements -%} -{%- if should_install_server_requirements -%} -COPY ./{{ server_requirements_filename }} {{ server_requirements_filename }} -RUN pip install -r {{ server_requirements_filename }} --no-cache-dir && rm -rf /root/.cache/pip -{%- endif -%} + || { pythonVersion=$(echo $($PYTHON_EXECUTABLE --version) | cut -d" " -f2 | cut -d"." -f1,2) \ + && add-apt-repository -y ppa:deadsnakes/ppa \ + && apt update -y && apt install -y --no-install-recommends python$pythonVersion-venv \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/*; } +# Create symlink for control server to start inference server process with correct python executable +RUN readlink {{config.base_image.python_executable_path}} &>/dev/null \ + && echo "WARNING: Overwriting existing link at /usr/local/bin/python" +RUN ln -sf {{config.base_image.python_executable_path}} /usr/local/bin/python + {%- endif %} +{% endif %} + +{% endblock %} + +{% block install_requirements %} + {%- if should_install_server_requirements %} +COPY ./{{server_requirements_filename}} {{server_requirements_filename}} +RUN pip install -r {{server_requirements_filename}} --no-cache-dir && rm -rf /root/.cache/pip + {%- endif %} {{ super() }} -{% endblock -%} +{% endblock %} -{% block app_copy -%} + +{% block app_copy %} +{%- if model_cache %} # Copy data before code for better caching -{%- if model_cache %}{%- include "copy_cache_files.Dockerfile.jinja" %}{%- endif %} + {%- include "copy_cache_files.Dockerfile.jinja"%} +{%- endif %} -{%- if external_data_files -%} -{%- for url, dst in external_data_files -%} +{%- if external_data_files %} +{% for url, dst in external_data_files %} RUN mkdir -p {{ dst.parent }}; curl -L "{{ url }}" -o {{ dst }} -{%- endfor -%} -{%- endif -%} +{% endfor %} +{%- endif %} + -{%- if build_commands -%} -{%- for command in build_commands -%} -RUN {% for secret, path in config.build.secret_to_path_mapping.items() %}--mount=type=secret,id={{ secret }},target={{ path }}{% endfor %} {{ command }} -{%- endfor -%} -{%- endif -%} +{%- if build_commands %} +{% for command in build_commands %} +RUN {% for secret,path in config.build.secret_to_path_mapping.items() %} --mount=type=secret,id={{secret}},target={{path}}{% endfor %} {{ command }} +{% endfor %} +{%- endif %} # Copy data before code for better caching -{%- if data_dir_exists -%} -COPY ./{{ config.data_dir }} /app/data -{%- endif -%} +{%- if data_dir_exists %} +COPY ./{{config.data_dir}} /app/data +{%- endif %} -{%- if not config.docker_server -%} +{%- if not config.docker_server %} COPY ./server /app -{%- endif -%} +{%- endif %} -{%- if use_local_chains_src -%} -# This path takes precedence over site-packages. +{%- if use_local_chains_src %} +{# This path takes precedence over site-packages. #} COPY ./truss_chains /app/truss_chains -{%- endif -%} +{%- endif %} COPY ./config.yaml /app/config.yaml -{%- if config.live_reload and not config.docker_server -%} + {%- if config.live_reload and not config.docker_server%} COPY ./control /control -RUN python3 -m venv /control/.env && /control/.env/bin/pip3 install -r /control/requirements.txt -{%- endif -%} -{%- if model_dir_exists -%} +RUN python3 -m venv /control/.env \ + && /control/.env/bin/pip3 install -r /control/requirements.txt + {%- endif %} +{%- if model_dir_exists %} COPY ./{{ config.model_module_dir }} /app/model -{%- endif -%} -{% endblock -%} +{%- endif %} +{% endblock %} -{% block run -%} -{%- if config.docker_server -%} -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - curl nginx python3-pip && \ - rm -rf /var/lib/apt/lists/* +{% block run %} + {%- if config.docker_server %} +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + curl nginx python3-pip && \ + rm -rf /var/lib/apt/lists/* COPY ./docker_server_requirements.txt /app/docker_server_requirements.txt RUN pip install -r /app/docker_server_requirements.txt --no-cache-dir && rm -rf /root/.cache/pip - -{% set proxy_config_path = "/etc/nginx/conf.d/proxy.conf" -%} -{% set supervisor_config_path = "/etc/supervisor/supervisord.conf" -%} -{% set supervisor_log_dir = "/var/log/supervisor" -%} -{% set supervisor_server_url = "http://localhost:8080" -%} - +{% set proxy_config_path = "/etc/nginx/conf.d/proxy.conf" %} +{% set supervisor_config_path = "/etc/supervisor/supervisord.conf" %} +{% set supervisor_log_dir = "/var/log/supervisor" %} +{% set supervisor_server_url = "http://localhost:8080" %} COPY ./proxy.conf {{ proxy_config_path }} RUN mkdir -p {{ supervisor_log_dir }} COPY supervisord.conf {{ supervisor_config_path }} - ENV SUPERVISOR_SERVER_URL="{{ supervisor_server_url }}" ENV SERVER_START_CMD="supervisord -c {{ supervisor_config_path }}" ENTRYPOINT ["supervisord", "-c", "{{ supervisor_config_path }}"] - -{%- elif config.live_reload -%} -ENV HASH_TRUSS="{{ truss_hash }}" + {%- elif config.live_reload %} +ENV HASH_TRUSS="{{truss_hash}}" ENV CONTROL_SERVER_PORT="8080" ENV INFERENCE_SERVER_PORT="8090" ENV SERVER_START_CMD="/control/.env/bin/python3 /control/control/server.py" ENTRYPOINT ["/control/.env/bin/python3", "/control/control/server.py"] - -{%- else -%} + {%- else %} ENV INFERENCE_SERVER_PORT="8080" -ENV SERVER_START_CMD="{{ (config.base_image.python_executable_path or "python3") ~ " /app/main.py" }}" -ENTRYPOINT ["{{ config.base_image.python_executable_path or "python3" }}", "/app/main.py"] -{%- endif -%} -{% endblock -%} +ENV SERVER_START_CMD="{{(config.base_image.python_executable_path or "python3") ~ " /app/main.py"}}" +ENTRYPOINT ["{{config.base_image.python_executable_path or "python3"}}", "/app/main.py"] + {%- endif %} +{% endblock %} diff --git a/truss/tests/remote/baseten/test_remote.py b/truss/tests/remote/baseten/test_remote.py index c3c64c365..da992939c 100644 --- a/truss/tests/remote/baseten/test_remote.py +++ b/truss/tests/remote/baseten/test_remote.py @@ -18,15 +18,17 @@ _TEST_REMOTE_GRAPHQL_PATH = "http://test_remote.com/graphql/" -def request_matches_expected_query(request, expected_query): +def assert_request_matches_expected_query(request, expected_query) -> None: unescaped_content = parse.unquote_plus(request.text) - - # Remove 'query=' prefix and any leading/trailing whitespace - actual_query = unescaped_content.replace("query=", "").strip() - - return tuple( - line.strip() for line in actual_query.split("\n") if line.strip() - ) == tuple(line.strip() for line in expected_query.split("\n") if line.strip()) + actual_lines = tuple( + line.strip() + for line in unescaped_content.replace("query=", "").strip().split("\n") + if line.strip() + ) + expected_lines = tuple( + line.strip() for line in expected_query.split("\n") if line.strip() + ) + assert actual_lines == expected_lines def test_get_service_by_version_id(): @@ -315,7 +317,7 @@ def test_create_chain_with_no_publish(): } """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( get_chains_graphql_request, expected_get_chains_query ) @@ -351,7 +353,7 @@ def test_create_chain_with_no_publish(): }} """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( create_chain_graphql_request, expected_create_chain_mutation ) @@ -411,7 +413,7 @@ def test_create_chain_no_existing_chain(): } """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( get_chains_graphql_request, expected_get_chains_query ) @@ -445,7 +447,7 @@ def test_create_chain_no_existing_chain(): }} """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( create_chain_graphql_request, expected_create_chain_mutation ) @@ -511,7 +513,7 @@ def test_create_chain_with_existing_chain_promote_to_environment_publish_false() } """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( get_chains_graphql_request, expected_get_chains_query ) @@ -548,7 +550,7 @@ def test_create_chain_with_existing_chain_promote_to_environment_publish_false() }} """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( create_chain_graphql_request, expected_create_chain_mutation ) @@ -614,7 +616,7 @@ def test_create_chain_existing_chain_publish_true_no_promotion(): } """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( get_chains_graphql_request, expected_get_chains_query ) @@ -648,7 +650,7 @@ def test_create_chain_existing_chain_publish_true_no_promotion(): }} """.strip() - assert request_matches_expected_query( + assert_request_matches_expected_query( create_chain_graphql_request, expected_create_chain_mutation )