|
1 | 1 | name: 'Build Docker Image' |
2 | | -description: 'Build and push Docker images for different frameworks and targets' |
| 2 | +description: 'Build and push a Docker image using config-file-driven build args, with pre/post build hooks.' |
3 | 3 |
|
4 | 4 | inputs: |
5 | | - framework: |
6 | | - description: 'Framework name' |
7 | | - required: true |
8 | | - target: |
9 | | - description: 'Docker build target' |
10 | | - required: true |
11 | | - base-image: |
12 | | - description: 'Base Docker image' |
13 | | - required: true |
14 | | - framework-version: |
15 | | - description: 'Framework version' |
16 | | - required: true |
17 | | - container-type: |
18 | | - description: 'Container type (e.g., general)' |
| 5 | + config-file: |
| 6 | + description: 'Path to the image config YAML file' |
19 | 7 | required: true |
20 | 8 | aws-account-id: |
21 | 9 | description: 'AWS account ID for ECR' |
22 | 10 | required: true |
23 | 11 | aws-region: |
24 | | - description: 'AWS region' |
| 12 | + description: 'AWS region for ECR' |
25 | 13 | required: true |
26 | | - tag-pr: |
27 | | - description: 'Pull request image tag' |
| 14 | + tag-suffix: |
| 15 | + description: 'Suffix for the CI image tag (e.g., github.run_id or pr-NUMBER)' |
28 | 16 | required: true |
29 | | - dockerfile-path: |
30 | | - description: 'Path to the Dockerfile' |
31 | | - required: true |
32 | | - arch-type: |
33 | | - description: 'Architecture type (e.g., x86, arm64)' |
34 | | - required: false |
35 | | - default: 'x86' |
36 | | - device-type: |
37 | | - description: 'Device type (e.g., gpu, cpu)' |
38 | | - required: false |
39 | | - default: 'gpu' |
40 | | - cuda-version: |
41 | | - description: 'CUDA version (e.g., cu129)' |
42 | | - required: false |
43 | | - default: '' |
44 | | - python-version: |
45 | | - description: 'Python version (e.g., py312)' |
46 | | - required: false |
47 | | - default: '' |
48 | | - os-version: |
49 | | - description: 'OS version (e.g., ubuntu24.04)' |
50 | | - required: false |
51 | | - default: '' |
52 | | - contributor: |
53 | | - description: 'Contributor name' |
54 | | - required: false |
55 | | - default: 'None' |
56 | | - customer-type: |
57 | | - description: 'Customer type (e.g., sagemaker, ec2)' |
58 | | - required: false |
59 | | - default: '' |
60 | | - inference-toolkit-version: |
61 | | - description: 'Inference toolkit version (e.g., 2.0.25)' |
62 | | - required: false |
63 | | - default: '' |
64 | | - torchserve-version: |
65 | | - description: 'TorchServe version (e.g., 0.12.0)' |
66 | | - required: false |
67 | | - default: '' |
68 | | - transformers-version: |
69 | | - description: 'Transformers library version (e.g., 4.28.1)' |
70 | | - required: false |
71 | | - default: '' |
72 | 17 |
|
73 | 18 | outputs: |
74 | 19 | image-uri: |
75 | | - description: 'Built image URI' |
76 | | - value: ${{ steps.image-uri-build.outputs.CI_IMAGE_URI }} |
| 20 | + description: 'Full ECR image URI that was built and pushed' |
| 21 | + value: ${{ steps.build.outputs.image-uri }} |
| 22 | + ci-tag: |
| 23 | + description: 'Computed CI image tag' |
| 24 | + value: ${{ steps.ci-tag.outputs.ci-tag }} |
77 | 25 |
|
78 | 26 | runs: |
79 | 27 | using: 'composite' |
80 | 28 | steps: |
| 29 | + - name: Compute CI tag |
| 30 | + id: ci-tag |
| 31 | + shell: bash |
| 32 | + run: bash "${{ github.action_path }}/compute_ci_tag.sh" --config-file "${{ inputs.config-file }}" --tag-suffix "${{ inputs.tag-suffix }}" |
| 33 | + |
| 34 | + - name: Resolve build args |
| 35 | + shell: bash |
| 36 | + run: python3 "${{ github.action_path }}/resolve_build_args.py" --config-file "${{ inputs.config-file }}" |
| 37 | + |
| 38 | + - name: Extract build metadata |
| 39 | + id: metadata |
| 40 | + shell: bash |
| 41 | + run: | |
| 42 | + CONFIG_FILE="${{ inputs.config-file }}" |
| 43 | + echo "framework=$(yq '.metadata.framework' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 44 | + echo "framework-version=$(yq '.metadata.framework_version' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 45 | + echo "customer-type=$(yq '.metadata.customer_type' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 46 | + echo "job-type=$(yq '.metadata.job_type' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 47 | + echo "arch-type=$(yq '.metadata.arch_type' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 48 | + echo "device-type=$(yq '.metadata.device_type' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 49 | + echo "os-version=$(yq '.metadata.os_version' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 50 | + echo "python-version=$(yq '.build.python_version // ""' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 51 | + echo "cuda-version=$(yq '.build.cuda_version // ""' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 52 | + echo "contributor=$(yq '.metadata.contributor // ""' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 53 | + echo "transformers-version=$(yq '.build.transformers_version // ""' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 54 | + echo "dockerfile=$(yq '.build.dockerfile' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 55 | + echo "target=$(yq '.build.target // ""' "$CONFIG_FILE")" >> $GITHUB_OUTPUT |
| 56 | +
|
| 57 | + - name: Run pre-build hook |
| 58 | + shell: bash |
| 59 | + run: | |
| 60 | + FRAMEWORK="${{ steps.metadata.outputs.framework }}" |
| 61 | + HOOK="scripts/ci/build/${FRAMEWORK}/pre_build.sh" |
| 62 | + if [[ -f "$HOOK" ]]; then |
| 63 | + echo "Running pre-build hook: $HOOK" |
| 64 | + bash "$HOOK" --config-file "${{ inputs.config-file }}" |
| 65 | + else |
| 66 | + echo "No pre-build hook for ${FRAMEWORK}" |
| 67 | + fi |
| 68 | +
|
81 | 69 | - name: Setup buildkitd |
82 | 70 | shell: bash |
83 | | - run: .github/scripts/buildkitd.sh |
| 71 | + run: bash "${{ github.action_path }}/buildkitd.sh" |
84 | 72 |
|
85 | 73 | - name: ECR login |
86 | 74 | uses: ./.github/actions/ecr-authenticate |
87 | 75 | with: |
88 | 76 | aws-account-id: ${{ inputs.aws-account-id }} |
89 | 77 | aws-region: ${{ inputs.aws-region }} |
90 | 78 |
|
91 | | - - name: Resolve image URI for build |
92 | | - id: image-uri-build |
| 79 | + - name: Build and push image |
| 80 | + id: build |
93 | 81 | shell: bash |
94 | 82 | run: | |
95 | | - CI_IMAGE_URI=${{ inputs.aws-account-id }}.dkr.ecr.${{ inputs.aws-region }}.amazonaws.com/ci:${{ inputs.tag-pr }} |
96 | | - echo "Image URI to build: ${CI_IMAGE_URI}" |
97 | | - echo "CI_IMAGE_URI=${CI_IMAGE_URI}" >> ${GITHUB_ENV} |
98 | | - echo "CI_IMAGE_URI=${CI_IMAGE_URI}" >> ${GITHUB_OUTPUT} |
| 83 | + CI_TAG="${{ steps.ci-tag.outputs.ci-tag }}" |
| 84 | + CI_IMAGE_URI="${{ inputs.aws-account-id }}.dkr.ecr.${{ inputs.aws-region }}.amazonaws.com/ci:${CI_TAG}" |
| 85 | + DOCKERFILE="${{ steps.metadata.outputs.dockerfile }}" |
| 86 | + TARGET="${{ steps.metadata.outputs.target }}" |
| 87 | + FRAMEWORK="${{ steps.metadata.outputs.framework }}" |
| 88 | + FRAMEWORK_VERSION="${{ steps.metadata.outputs.framework-version }}" |
| 89 | + CUSTOMER_TYPE="${{ steps.metadata.outputs.customer-type }}" |
| 90 | + JOB_TYPE="${{ steps.metadata.outputs.job-type }}" |
| 91 | +
|
| 92 | + # Build --build-arg flags from EXTRA_BUILD_ARGS (use array for space-safe values) |
| 93 | + BUILD_ARGS=() |
| 94 | + for var in ${EXTRA_BUILD_ARGS:-}; do |
| 95 | + if [[ -n "${!var:-}" ]]; then |
| 96 | + BUILD_ARGS+=("--build-arg" "${var}=${!var}") |
| 97 | + fi |
| 98 | + done |
99 | 99 |
|
100 | | - - name: Build image |
| 100 | + # Always pass framework metadata as build-args |
| 101 | + BUILD_ARGS+=("--build-arg" "FRAMEWORK=${FRAMEWORK}") |
| 102 | + BUILD_ARGS+=("--build-arg" "FRAMEWORK_VERSION=${FRAMEWORK_VERSION}") |
| 103 | + BUILD_ARGS+=("--build-arg" "CONTAINER_TYPE=${JOB_TYPE}") |
| 104 | + BUILD_ARGS+=("--build-arg" "CACHE_REFRESH=$(date +%Y-%m-%d)") |
| 105 | +
|
| 106 | + # Pass hook-injected env vars (set by pre_build hooks via GITHUB_ENV) |
| 107 | + if [[ -n "${USE_PREBUILT_WHEEL:-}" ]]; then |
| 108 | + BUILD_ARGS+=("--build-arg" "USE_PREBUILT_WHEEL=${USE_PREBUILT_WHEEL}") |
| 109 | + fi |
| 110 | + if [[ -n "${FRAMEWORK_SHORT_VERSION:-}" ]]; then |
| 111 | + BUILD_ARGS+=("--build-arg" "FRAMEWORK_SHORT_VERSION=${FRAMEWORK_SHORT_VERSION}") |
| 112 | + fi |
| 113 | + if [[ -n "${PYTHON_VERSION:-}" ]]; then |
| 114 | + PYTHON_SHORT_VERSION=$(echo "${PYTHON_VERSION}" | cut -d. -f1,2) |
| 115 | + BUILD_ARGS+=("--build-arg" "PYTHON_SHORT_VERSION=${PYTHON_SHORT_VERSION}") |
| 116 | + fi |
| 117 | +
|
| 118 | + # SageMaker labels |
| 119 | + LABEL_ARGS=() |
| 120 | + if [[ "$CUSTOMER_TYPE" == "sagemaker" ]]; then |
| 121 | + LABEL_FRAMEWORK=$(echo "${FRAMEWORK}" | tr '_' '-') |
| 122 | + LABEL_FRAMEWORK_VERSION=$(echo "${FRAMEWORK_VERSION}" | tr '.' '-') |
| 123 | + LABEL_ARCH="${{ steps.metadata.outputs.arch-type }}" |
| 124 | + LABEL_DEVICE_TYPE="${{ steps.metadata.outputs.device-type }}" |
| 125 | +
|
| 126 | + # Convert raw cuda version (13.0.2) to short form (cu130) |
| 127 | + CUDA_RAW="${{ steps.metadata.outputs.cuda-version }}" |
| 128 | + if [[ -n "$CUDA_RAW" ]]; then |
| 129 | + CUDA_MAJOR=$(echo "$CUDA_RAW" | cut -d. -f1) |
| 130 | + CUDA_MINOR=$(echo "$CUDA_RAW" | cut -d. -f2) |
| 131 | + LABEL_CUDA="cu${CUDA_MAJOR}${CUDA_MINOR}" |
| 132 | + else |
| 133 | + LABEL_CUDA="" |
| 134 | + fi |
| 135 | + if [[ "$LABEL_DEVICE_TYPE" == "gpu" && -n "$LABEL_CUDA" ]]; then |
| 136 | + LABEL_DEVICE_TYPE="gpu.${LABEL_CUDA}" |
| 137 | + fi |
| 138 | +
|
| 139 | + # Convert raw python version (3.12) to short form (py312) |
| 140 | + PYTHON_RAW="${{ steps.metadata.outputs.python-version }}" |
| 141 | + if [[ -n "$PYTHON_RAW" ]]; then |
| 142 | + PY_MAJOR=$(echo "$PYTHON_RAW" | cut -d. -f1) |
| 143 | + PY_MINOR=$(echo "$PYTHON_RAW" | cut -d. -f2) |
| 144 | + LABEL_PYTHON="py${PY_MAJOR}${PY_MINOR}" |
| 145 | + else |
| 146 | + LABEL_PYTHON="" |
| 147 | + fi |
| 148 | +
|
| 149 | + LABEL_OS=$(echo "${{ steps.metadata.outputs.os-version }}" | tr '.' '-') |
| 150 | +
|
| 151 | + LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.arch.${LABEL_ARCH}=true") |
| 152 | + LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.device.${LABEL_DEVICE_TYPE}=true") |
| 153 | + LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.framework.${LABEL_FRAMEWORK}.${LABEL_FRAMEWORK_VERSION}=true") |
| 154 | + LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.job.${JOB_TYPE}=true") |
| 155 | + [[ -n "$LABEL_OS" ]] && LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.os.${LABEL_OS}=true") |
| 156 | + [[ -n "$LABEL_PYTHON" ]] && LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.python.${LABEL_PYTHON}=true") |
| 157 | +
|
| 158 | + CONTRIBUTOR="${{ steps.metadata.outputs.contributor }}" |
| 159 | + if [[ -n "$CONTRIBUTOR" && "$CONTRIBUTOR" != "None" ]]; then |
| 160 | + LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.contributor.${CONTRIBUTOR}=true") |
| 161 | + fi |
| 162 | +
|
| 163 | + TRANSFORMERS_VER="${{ steps.metadata.outputs.transformers-version }}" |
| 164 | + if [[ -n "$TRANSFORMERS_VER" ]]; then |
| 165 | + LABEL_TRANSFORMERS=$(echo "${TRANSFORMERS_VER}" | tr '.' '-') |
| 166 | + LABEL_ARGS+=("--label" "com.amazonaws.ml.engines.sagemaker.dlc.lib.transformers.${LABEL_TRANSFORMERS}=true") |
| 167 | + fi |
| 168 | + fi |
| 169 | +
|
| 170 | + # Target flag (optional) |
| 171 | + TARGET_ARGS=() |
| 172 | + if [[ -n "$TARGET" ]]; then |
| 173 | + TARGET_ARGS+=("--target" "${TARGET}") |
| 174 | + fi |
| 175 | +
|
| 176 | + # Export intermediate stages if requested (wheel cache miss) |
| 177 | + if [[ -n "${EXPORT_TARGETS:-}" ]]; then |
| 178 | + IFS=',' read -ra EXPORTS <<< "${EXPORT_TARGETS}" |
| 179 | + for EXPORT in "${EXPORTS[@]}"; do |
| 180 | + EXPORT_TARGET="${EXPORT%%:*}" |
| 181 | + EXPORT_DEST="${EXPORT##*:}" |
| 182 | + echo "Exporting stage '${EXPORT_TARGET}' to ${EXPORT_DEST}..." |
| 183 | + docker buildx build --progress plain \ |
| 184 | + "${BUILD_ARGS[@]}" \ |
| 185 | + --target "${EXPORT_TARGET}" \ |
| 186 | + --output "type=local,dest=${EXPORT_DEST}" \ |
| 187 | + -f "${DOCKERFILE}" . || echo "Export ${EXPORT_TARGET} failed (non-fatal)" |
| 188 | + done |
| 189 | + fi |
| 190 | +
|
| 191 | + # Main build |
| 192 | + docker buildx use default 2>/dev/null || true |
| 193 | + echo "Building ${CI_IMAGE_URI}" |
| 194 | + docker buildx build --progress plain \ |
| 195 | + "${BUILD_ARGS[@]}" \ |
| 196 | + "${LABEL_ARGS[@]}" \ |
| 197 | + --cache-to=type=inline \ |
| 198 | + --cache-from=type=registry,ref="${CI_IMAGE_URI}" \ |
| 199 | + --tag "${CI_IMAGE_URI}" \ |
| 200 | + --push \ |
| 201 | + "${TARGET_ARGS[@]}" \ |
| 202 | + -f "${DOCKERFILE}" . |
| 203 | +
|
| 204 | + echo "image-uri=${CI_IMAGE_URI}" >> $GITHUB_OUTPUT |
| 205 | + echo "CI_IMAGE_URI=${CI_IMAGE_URI}" >> $GITHUB_ENV |
| 206 | +
|
| 207 | + # Cleanup local image |
| 208 | + docker rmi ${CI_IMAGE_URI} 2>/dev/null || true |
| 209 | +
|
| 210 | + - name: Run post-build hook |
101 | 211 | shell: bash |
102 | | - run: .github/scripts/build_image.sh |
103 | | - env: |
104 | | - FRAMEWORK: ${{ inputs.framework }} |
105 | | - TARGET: ${{ inputs.target }} |
106 | | - BASE_IMAGE: ${{ inputs.base-image }} |
107 | | - FRAMEWORK_VERSION: ${{ inputs.framework-version }} |
108 | | - CONTAINER_TYPE: ${{ inputs.container-type }} |
109 | | - AWS_ACCOUNT_ID: ${{ inputs.aws-account-id }} |
110 | | - AWS_REGION: ${{ inputs.aws-region }} |
111 | | - TAG_PR: ${{ inputs.tag-pr }} |
112 | | - DOCKERFILE_PATH: ${{ inputs.dockerfile-path }} |
113 | | - ARCH_TYPE: ${{ inputs.arch-type }} |
114 | | - DEVICE_TYPE: ${{ inputs.device-type }} |
115 | | - CUDA_VERSION: ${{ inputs.cuda-version }} |
116 | | - PYTHON_VERSION: ${{ inputs.python-version }} |
117 | | - OS_VERSION: ${{ inputs.os-version }} |
118 | | - CONTRIBUTOR: ${{ inputs.contributor }} |
119 | | - CUSTOMER_TYPE: ${{ inputs.customer-type }} |
120 | | - INFERENCE_TOOLKIT_VERSION: ${{ inputs.inference-toolkit-version }} |
121 | | - TORCHSERVE_VERSION: ${{ inputs.torchserve-version }} |
122 | | - TRANSFORMERS_VERSION: ${{ inputs.transformers-version }} |
| 212 | + run: | |
| 213 | + FRAMEWORK="${{ steps.metadata.outputs.framework }}" |
| 214 | + HOOK="scripts/ci/build/${FRAMEWORK}/post_build.sh" |
| 215 | + if [[ -f "$HOOK" ]]; then |
| 216 | + echo "Running post-build hook: $HOOK" |
| 217 | + bash "$HOOK" --config-file "${{ inputs.config-file }}" |
| 218 | + else |
| 219 | + echo "No post-build hook for ${FRAMEWORK}" |
| 220 | + fi |
0 commit comments