Skip to content

Commit 10da62f

Browse files
committed
feat: export distribution container build artifacts
Add a new --export-dir flag to the `llama stack build` command that allows users to export container build artifacts to a specified directory instead of building the container directly. This feature is useful for: - Building containers in different environments - Sharing build configurations - Customizing the build process The exported tarball includes: - Containerfile (Dockerfile) - Run configuration file (if building from config) - External provider files (if specified) - Build script for assistance The tarball is named with a timestamp for uniqueness: <distro-name>_<timestamp>.tar.gz Documentation has been updated in building_distro.md to reflect this new functionality as well as integration tests. Signed-off-by: Sébastien Han <[email protected]>
1 parent 7aae8fa commit 10da62f

File tree

7 files changed

+155
-23
lines changed

7 files changed

+155
-23
lines changed

.github/workflows/providers-build.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,60 @@ jobs:
198198
'source /etc/os-release && echo "$ID"' \
199199
| grep -qE '^(rhel|ubi)$' \
200200
|| { echo "Base image is not UBI 9!"; exit 1; }
201+
202+
export-build:
203+
runs-on: ubuntu-latest
204+
steps:
205+
- name: Checkout repository
206+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
207+
208+
- name: Set up Python
209+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
210+
with:
211+
python-version: '3.10'
212+
213+
- name: Install uv
214+
uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
215+
with:
216+
python-version: "3.10"
217+
218+
- name: Install LlamaStack
219+
run: |
220+
uv venv
221+
source .venv/bin/activate
222+
uv pip install -e .
223+
224+
- name: Pin template to UBI9 base
225+
run: |
226+
yq -i '
227+
.image_type = "container" |
228+
.image_name = "ubi9-test" |
229+
.distribution_spec.container_image = "registry.access.redhat.com/ubi9:latest"
230+
' llama_stack/templates/starter/build.yaml
231+
232+
- name: Test the export
233+
run: |
234+
USE_COPY_NOT_MOUNT=true LLAMA_STACK_DIR=. uv run llama stack build --config llama_stack/templates/starter/build.yaml --export-dir export
235+
for file in export/*; do
236+
echo "File: $file"
237+
if [[ "$file" == *.tar.gz ]]; then
238+
echo "Tarball found"
239+
tarball_found=1
240+
tar -xzvf "$file"
241+
if [ -f "Containerfile" ]; then
242+
echo "Containerfile found"
243+
else
244+
echo "Containerfile not found"
245+
exit 1
246+
fi
247+
else
248+
continue
249+
fi
250+
break
251+
done
252+
if [ -z "$tarball_found" ]; then
253+
echo "Tarball not found"
254+
exit 1
255+
fi
256+
cd export
257+
docker build -t test .

docs/source/distributions/building_distro.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The main points to consider are:
5353

5454
```
5555
llama stack build -h
56-
usage: llama stack build [-h] [--config CONFIG] [--template TEMPLATE] [--list-templates] [--image-type {conda,container,venv}] [--image-name IMAGE_NAME] [--print-deps-only] [--run]
56+
usage: llama stack build [-h] [--config CONFIG] [--template TEMPLATE] [--list-templates] [--image-type {conda,container,venv}] [--image-name IMAGE_NAME] [--print-deps-only] [--run] [--export-dir EXPORT_DIR]
5757
5858
Build a Llama stack container
5959
@@ -71,6 +71,8 @@ options:
7171
found. (default: None)
7272
--print-deps-only Print the dependencies for the stack only, without building the stack (default: False)
7373
--run Run the stack after building using the same image type, name, and other applicable arguments (default: False)
74+
--export-dir EXPORT_DIR
75+
Export the build artifacts to a specified directory instead of building the container. This will create a tarball containing the Dockerfile and all necessary files to build the container. (default: None)
7476
7577
```
7678

@@ -260,6 +262,24 @@ Containerfile created successfully in /tmp/tmp.viA3a3Rdsg/ContainerfileFROM pyth
260262
You can now edit ~/meta-llama/llama-stack/tmp/configs/ollama-run.yaml and run `llama stack run ~/meta-llama/llama-stack/tmp/configs/ollama-run.yaml`
261263
```
262264

265+
You can also export the build artifacts to a specified directory instead of building the container directly. This is useful when you want to:
266+
- Build the container in a different environment
267+
- Share the build configuration with others
268+
- Customize the build process
269+
270+
To export the build artifacts, use the `--export-dir` flag:
271+
272+
```
273+
llama stack build --config my-build.yaml --image-type container --export-dir ./my-build
274+
```
275+
276+
This will create a tarball in the specified directory containing:
277+
- The Dockerfile (named Containerfile)
278+
- The run configuration file (if building from a config)
279+
- Any external provider files (if specified in the config)
280+
281+
The tarball will be named with a timestamp to ensure uniqueness, for example: `<distro-name>_<timestamp>.tar.gz`
282+
263283
After this step is successful, you should be able to find the built container image and test it with `llama stack run <path/to/run.yaml>`.
264284
:::
265285

llama_stack/cli/stack/_build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def run_stack_build_command(args: argparse.Namespace) -> None:
228228
image_name=image_name,
229229
config_path=args.config,
230230
template_name=args.template,
231+
export_dir=args.export_dir,
231232
)
232233

233234
except (Exception, RuntimeError) as exc:
@@ -341,6 +342,7 @@ def _run_stack_build_command_from_build_config(
341342
image_name: str | None = None,
342343
template_name: str | None = None,
343344
config_path: str | None = None,
345+
export_dir: str | None = None,
344346
) -> str:
345347
image_name = image_name or build_config.image_name
346348
if build_config.image_type == LlamaStackImageType.CONTAINER.value:
@@ -383,6 +385,7 @@ def _run_stack_build_command_from_build_config(
383385
image_name,
384386
template_or_config=template_name or config_path or str(build_file_path),
385387
run_config=run_config_file,
388+
export_dir=export_dir,
386389
)
387390
if return_code != 0:
388391
raise RuntimeError(f"Failed to build image {image_name}")

llama_stack/cli/stack/build.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# the root directory of this source tree.
66
import argparse
77
import textwrap
8+
from pathlib import Path
89

910
from llama_stack.cli.stack.utils import ImageType
1011
from llama_stack.cli.subcommand import Subcommand
@@ -82,6 +83,13 @@ def _add_arguments(self):
8283
help="Build a config for a list of providers and only those providers. This list is formatted like: api1=provider1,api2=provider2. Where there can be multiple providers per API.",
8384
)
8485

86+
self.parser.add_argument(
87+
"--export-dir",
88+
type=Path,
89+
default=None,
90+
help="Export the build artifacts to a specified directory instead of building the container. This will create a directory containing the Dockerfile and all necessary files to build the container.",
91+
)
92+
8593
def _run_stack_build_command(self, args: argparse.Namespace) -> None:
8694
# always keep implementation completely silo-ed away from CLI so CLI
8795
# can be fast to load and reduces dependencies

llama_stack/distribution/build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def build_image(
9393
image_name: str,
9494
template_or_config: str,
9595
run_config: str | None = None,
96+
export_dir: str | None = None,
9697
):
9798
container_base = build_config.distribution_spec.container_image or "python:3.10-slim"
9899

@@ -108,6 +109,8 @@ def build_image(
108109
container_base,
109110
" ".join(normal_deps),
110111
]
112+
if export_dir is not None:
113+
args.append(export_dir)
111114

112115
# When building from a config file (not a template), include the run config path in the
113116
# build arguments

llama_stack/distribution/build_container.sh

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ BUILD_CONTEXT_DIR=$(pwd)
2626

2727
if [ "$#" -lt 4 ]; then
2828
# This only works for templates
29-
echo "Usage: $0 <template_or_config> <image_name> <container_base> <pip_dependencies> [<run_config>] [<special_pip_deps>]" >&2
29+
echo "Usage: $0 <template_or_config> <image_name> <container_base> <pip_dependencies> [<run_config>] [<special_pip_deps>] [<export_dir>]" >&2
3030
exit 1
3131
fi
3232
set -euo pipefail
@@ -43,23 +43,26 @@ shift
4343
# Handle optional arguments
4444
run_config=""
4545
special_pip_deps=""
46-
47-
# Check if there are more arguments
48-
# The logics is becoming cumbersom, we should refactor it if we can do better
49-
if [ $# -gt 0 ]; then
50-
# Check if the argument ends with .yaml
51-
if [[ "$1" == *.yaml ]]; then
52-
run_config="$1"
53-
shift
54-
# If there's another argument after .yaml, it must be special_pip_deps
55-
if [ $# -gt 0 ]; then
56-
special_pip_deps="$1"
57-
fi
58-
else
59-
# If it's not .yaml, it must be special_pip_deps
60-
special_pip_deps="$1"
61-
fi
62-
fi
46+
export_dir=""
47+
48+
# Process remaining arguments
49+
while [[ $# -gt 0 ]]; do
50+
case "$1" in
51+
*.yaml)
52+
run_config="$1"
53+
shift
54+
;;
55+
*)
56+
# Check if argument is a valid directory path (export_dir) or special_pip_deps
57+
if [[ "$1" == *" "* ]] || ! mkdir -p "$1" 2>/dev/null; then
58+
special_pip_deps="$1"
59+
else
60+
export_dir="$1"
61+
fi
62+
shift
63+
;;
64+
esac
65+
done
6366

6467
# Define color codes
6568
RED='\033[0;31m'
@@ -83,8 +86,8 @@ add_to_container() {
8386
fi
8487
}
8588

86-
# Check if container command is available
87-
if ! is_command_available $CONTAINER_BINARY; then
89+
# Check if container command is available only if not running in export mode
90+
if ! is_command_available $CONTAINER_BINARY && [ -z "$export_dir" ]; then
8891
printf "${RED}Error: ${CONTAINER_BINARY} command not found. Is ${CONTAINER_BINARY} installed and in your PATH?${NC}" >&2
8992
exit 1
9093
fi
@@ -96,7 +99,7 @@ FROM $container_base
9699
WORKDIR /app
97100
98101
# We install the Python 3.11 dev headers and build tools so that any
99-
# Cextension wheels (e.g. polyleven, faisscpu) can compile successfully.
102+
# C-extension wheels (e.g. polyleven, faiss-cpu) can compile successfully.
100103
101104
RUN dnf -y update && dnf install -y iputils git net-tools wget \
102105
vim-minimal python3.11 python3.11-pip python3.11-wheel \
@@ -270,6 +273,43 @@ printf "Containerfile created successfully in %s/Containerfile\n\n" "$TEMP_DIR"
270273
cat "$TEMP_DIR"/Containerfile
271274
printf "\n"
272275

276+
# If export_dir is specified, copy all necessary files and exit
277+
if [ -n "$export_dir" ]; then
278+
mkdir -p "$export_dir"
279+
timestamp=$(date '+%Y-%m-%d_%H-%M-%S')
280+
tar_name="${image_name//[^a-zA-Z0-9]/_}_${timestamp}.tar.gz"
281+
282+
# If a run config is provided, copy it to the export directory otherwise it's a template build and
283+
# we don't need to copy anything
284+
if [ -n "$run_config" ]; then
285+
mv "$run_config" "$TEMP_DIR"/run.yaml
286+
fi
287+
288+
# Create the archive with all files
289+
echo "Creating tarball with the following files:"
290+
echo "- Containerfile"
291+
[ -n "$run_config" ] && echo "- run.yaml"
292+
[ -n "$external_providers_dir" ] && echo "- providers.d directory"
293+
294+
# Capture both stdout and stderr from tar command
295+
tar_output=$(tar -czf "$export_dir/$tar_name" \
296+
-C "$TEMP_DIR" Containerfile \
297+
${run_config:+-C "$BUILD_CONTEXT_DIR" "$(basename run.yaml)"} \
298+
${external_providers_dir:+-C "$BUILD_CONTEXT_DIR" providers.d} 2>&1)
299+
tar_status=$?
300+
301+
if [ $tar_status -ne 0 ]; then
302+
echo "ERROR: Failed to create tarball" >&2
303+
echo "Tar command output:" >&2
304+
echo "$tar_output" >&2
305+
exit 1
306+
fi
307+
rm -rf providers.d run.yaml
308+
309+
echo "Build artifacts tarball created: $export_dir/$tar_name"
310+
exit 0
311+
fi
312+
273313
# Start building the CLI arguments
274314
CLI_ARGS=()
275315

tests/unit/distribution/test_build_path.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
def test_container_build_passes_path(monkeypatch, tmp_path):
1717
called_with = {}
1818

19-
def spy_build_image(cfg, build_file_path, image_name, template_or_config, run_config=None):
19+
def spy_build_image(cfg, build_file_path, image_name, template_or_config, run_config=None, export_dir=None):
2020
called_with["path"] = template_or_config
2121
called_with["run_config"] = run_config
22+
called_with["export_dir"] = export_dir
2223
return 0
2324

2425
monkeypatch.setattr(

0 commit comments

Comments
 (0)