From 0de8edd71d955b89a879c3ba9eb6bf6035706e3b Mon Sep 17 00:00:00 2001 From: Derek Higgins Date: Thu, 24 Jul 2025 11:02:51 +0100 Subject: [PATCH] feat: switch to Python-based container build system o Update build.py to call build_container.py instead of shell script o Maintain build_container.sh for compat Signed-off-by: Derek Higgins --- llama_stack/distribution/build.py | 2 +- llama_stack/distribution/build_container.sh | 331 +------------------- 2 files changed, 6 insertions(+), 327 deletions(-) diff --git a/llama_stack/distribution/build.py b/llama_stack/distribution/build.py index 699ed72da..67fcd6f4f 100644 --- a/llama_stack/distribution/build.py +++ b/llama_stack/distribution/build.py @@ -107,7 +107,7 @@ def build_image( normal_deps += SERVER_DEPENDENCIES if build_config.image_type == LlamaStackImageType.CONTAINER.value: - script = str(importlib.resources.files("llama_stack") / "distribution/build_container.sh") + script = str(importlib.resources.files("llama_stack") / "distribution/build_container.py") args = [ script, template_or_config, diff --git a/llama_stack/distribution/build_container.sh b/llama_stack/distribution/build_container.sh index 6985c1cd0..086993a05 100755 --- a/llama_stack/distribution/build_container.sh +++ b/llama_stack/distribution/build_container.sh @@ -6,154 +6,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -LLAMA_STACK_DIR=${LLAMA_STACK_DIR:-} -LLAMA_STACK_CLIENT_DIR=${LLAMA_STACK_CLIENT_DIR:-} - -TEST_PYPI_VERSION=${TEST_PYPI_VERSION:-} -PYPI_VERSION=${PYPI_VERSION:-} -BUILD_PLATFORM=${BUILD_PLATFORM:-} -# This timeout (in seconds) is necessary when installing PyTorch via uv since it's likely to time out -# Reference: https://github.com/astral-sh/uv/pull/1694 -UV_HTTP_TIMEOUT=${UV_HTTP_TIMEOUT:-500} - -# mounting is not supported by docker buildx, so we use COPY instead -USE_COPY_NOT_MOUNT=${USE_COPY_NOT_MOUNT:-} - -# Mount command for cache container .cache, can be overridden by the user if needed -MOUNT_CACHE=${MOUNT_CACHE:-"--mount=type=cache,id=llama-stack-cache,target=/root/.cache"} - -# Path to the run.yaml file in the container -RUN_CONFIG_PATH=/app/run.yaml - -BUILD_CONTEXT_DIR=$(pwd) - -if [ "$#" -lt 4 ]; then - # This only works for templates - echo "Usage: $0 [] []" >&2 - exit 1 -fi -set -euo pipefail - -template_or_config="$1" -shift -image_name="$1" -shift -container_base="$1" -shift -pip_dependencies="$1" -shift - -# Handle optional arguments -run_config="" -special_pip_deps="" - -# Check if there are more arguments -# The logics is becoming cumbersom, we should refactor it if we can do better -if [ $# -gt 0 ]; then - # Check if the argument ends with .yaml - if [[ "$1" == *.yaml ]]; then - run_config="$1" - shift - # If there's another argument after .yaml, it must be special_pip_deps - if [ $# -gt 0 ]; then - special_pip_deps="$1" - fi - else - # If it's not .yaml, it must be special_pip_deps - special_pip_deps="$1" - fi -fi - -# Define color codes -RED='\033[0;31m' -NC='\033[0m' # No Color - -CONTAINER_BINARY=${CONTAINER_BINARY:-docker} -CONTAINER_OPTS=${CONTAINER_OPTS:---progress=plain} - -TEMP_DIR=$(mktemp -d) - -SCRIPT_DIR=$(dirname "$(readlink -f "$0")") -source "$SCRIPT_DIR/common.sh" - -add_to_container() { - output_file="$TEMP_DIR/Containerfile" - if [ -t 0 ]; then - printf '%s\n' "$1" >>"$output_file" - else - # If stdin is not a terminal, read from it (heredoc) - cat >>"$output_file" - fi -} - -# Check if container command is available -if ! is_command_available "$CONTAINER_BINARY"; then - printf "${RED}Error: ${CONTAINER_BINARY} command not found. Is ${CONTAINER_BINARY} installed and in your PATH?${NC}" >&2 - exit 1 -fi - -# Update and install UBI9 components if UBI9 base image is used -if [[ $container_base == *"registry.access.redhat.com/ubi9"* ]]; then - add_to_container << EOF -FROM $container_base -WORKDIR /app - -# We install the Python 3.12 dev headers and build tools so that any -# C-extension wheels (e.g. polyleven, faiss-cpu) can compile successfully. - -RUN dnf -y update && dnf install -y iputils git net-tools wget \ - vim-minimal python3.12 python3.12-pip python3.12-wheel \ - python3.12-setuptools python3.12-devel gcc make && \ - ln -s /bin/pip3.12 /bin/pip && ln -s /bin/python3.12 /bin/python && dnf clean all - -ENV UV_SYSTEM_PYTHON=1 -RUN pip install uv -EOF -else - add_to_container << EOF -FROM $container_base -WORKDIR /app - -RUN apt-get update && apt-get install -y \ - iputils-ping net-tools iproute2 dnsutils telnet \ - curl wget telnet git\ - procps psmisc lsof \ - traceroute \ - bubblewrap \ - gcc \ - && rm -rf /var/lib/apt/lists/* - -ENV UV_SYSTEM_PYTHON=1 -RUN pip install uv -EOF -fi - -# Set the link mode to copy so that uv doesn't attempt to symlink to the cache directory -add_to_container << EOF -ENV UV_LINK_MODE=copy -EOF - -# Add pip dependencies first since llama-stack is what will change most often -# so we can reuse layers. -if [ -n "$pip_dependencies" ]; then - read -ra pip_args <<< "$pip_dependencies" - quoted_deps=$(printf " %q" "${pip_args[@]}") - add_to_container << EOF -RUN $MOUNT_CACHE uv pip install $quoted_deps -EOF -fi - -if [ -n "$special_pip_deps" ]; then - IFS='#' read -ra parts <<<"$special_pip_deps" - for part in "${parts[@]}"; do - read -ra pip_args <<< "$part" - quoted_deps=$(printf " %q" "${pip_args[@]}") - add_to_container <&2 - exit 1 - fi - - if [ "$USE_COPY_NOT_MOUNT" = "true" ]; then - add_to_container << EOF -COPY $dir $mount_point -EOF - fi - add_to_container << EOF -RUN $MOUNT_CACHE uv pip install -e $mount_point -EOF -} - - -if [ -n "$LLAMA_STACK_CLIENT_DIR" ]; then - install_local_package "$LLAMA_STACK_CLIENT_DIR" "$client_mount" "LLAMA_STACK_CLIENT_DIR" -fi - -if [ -n "$LLAMA_STACK_DIR" ]; then - install_local_package "$LLAMA_STACK_DIR" "$stack_mount" "LLAMA_STACK_DIR" -else - if [ -n "$TEST_PYPI_VERSION" ]; then - # these packages are damaged in test-pypi, so install them first - add_to_container << EOF -RUN $MOUNT_CACHE uv pip install fastapi libcst -EOF - add_to_container << EOF -RUN $MOUNT_CACHE uv pip install --extra-index-url https://test.pypi.org/simple/ \ - --index-strategy unsafe-best-match \ - llama-stack==$TEST_PYPI_VERSION - -EOF - else - if [ -n "$PYPI_VERSION" ]; then - SPEC_VERSION="llama-stack==${PYPI_VERSION}" - else - SPEC_VERSION="llama-stack" - fi - add_to_container << EOF -RUN $MOUNT_CACHE uv pip install $SPEC_VERSION -EOF - fi -fi - -# remove uv after installation - add_to_container << EOF -RUN pip uninstall -y uv -EOF - -# If a run config is provided, we use the --config flag -if [[ -n "$run_config" ]]; then - add_to_container << EOF -ENTRYPOINT ["python", "-m", "llama_stack.distribution.server.server", "--config", "$RUN_CONFIG_PATH"] -EOF -# If a template is provided (not a yaml file), we use the --template flag -elif [[ "$template_or_config" != *.yaml ]]; then - add_to_container << EOF -ENTRYPOINT ["python", "-m", "llama_stack.distribution.server.server", "--template", "$template_or_config"] -EOF -fi - -# Add other require item commands genearic to all containers -add_to_container << EOF - -RUN mkdir -p /.llama /.cache && chmod -R g+rw /app /.llama /.cache -EOF - -printf "Containerfile created successfully in %s/Containerfile\n\n" "$TEMP_DIR" -cat "$TEMP_DIR"/Containerfile -printf "\n" - -# Start building the CLI arguments -CLI_ARGS=() - -# Read CONTAINER_OPTS and put it in an array -read -ra CLI_ARGS <<< "$CONTAINER_OPTS" - -if [ "$USE_COPY_NOT_MOUNT" != "true" ]; then - if [ -n "$LLAMA_STACK_DIR" ]; then - CLI_ARGS+=("-v" "$(readlink -f "$LLAMA_STACK_DIR"):$stack_mount") - fi - if [ -n "$LLAMA_STACK_CLIENT_DIR" ]; then - CLI_ARGS+=("-v" "$(readlink -f "$LLAMA_STACK_CLIENT_DIR"):$client_mount") - fi -fi - -if is_command_available selinuxenabled && selinuxenabled; then - # Disable SELinux labels -- we don't want to relabel the llama-stack source dir - CLI_ARGS+=("--security-opt" "label=disable") -fi - -# Set version tag based on PyPI version -if [ -n "$PYPI_VERSION" ]; then - version_tag="$PYPI_VERSION" -elif [ -n "$TEST_PYPI_VERSION" ]; then - version_tag="test-$TEST_PYPI_VERSION" -elif [[ -n "$LLAMA_STACK_DIR" || -n "$LLAMA_STACK_CLIENT_DIR" ]]; then - version_tag="dev" -else - URL="https://pypi.org/pypi/llama-stack/json" - version_tag=$(curl -s $URL | jq -r '.info.version') -fi - -# Add version tag to image name -image_tag="$image_name:$version_tag" - -# Detect platform architecture -ARCH=$(uname -m) -if [ -n "$BUILD_PLATFORM" ]; then - CLI_ARGS+=("--platform" "$BUILD_PLATFORM") -elif [ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ]; then - CLI_ARGS+=("--platform" "linux/arm64") -elif [ "$ARCH" = "x86_64" ]; then - CLI_ARGS+=("--platform" "linux/amd64") -else - echo "Unsupported architecture: $ARCH" - exit 1 -fi - -echo "PWD: $(pwd)" -echo "Containerfile: $TEMP_DIR/Containerfile" -set -x - -$CONTAINER_BINARY build \ - "${CLI_ARGS[@]}" \ - -t "$image_tag" \ - -f "$TEMP_DIR/Containerfile" \ - "$BUILD_CONTEXT_DIR" - -# clean up tmp/configs -rm -rf "$BUILD_CONTEXT_DIR/run.yaml" "$TEMP_DIR" -set +x - -echo "Success!" +# call the python script +CMD=$(get_python_cmd) +$CMD $SCRIPT_DIR/build_container.py "$@"