build-push-image.yaml
1 name: "Reusable Build" 2 run-name: "Build ${{ inputs.ref || github.ref_name }}${{ fromJSON(inputs.push_to_ecr) && ' and Push to ECR' || '' }}" 3 4 on: 5 workflow_call: 6 inputs: 7 ref: 8 description: "Git ref to checkout (branch, tag, or commit SHA). Leave empty for default." 9 type: string 10 required: false 11 default: "" 12 push_to_ecr: 13 description: "Push the built images to ECR and create a multi-arch manifest." 14 type: boolean 15 required: false 16 default: true 17 run_container_scan: 18 description: "Run Prisma Cloud container scan on built images." 19 type: boolean 20 required: false 21 default: false 22 is_release_merge: 23 description: "True when this build is for a release-please merge to the default branch. Controls whether the bare version tag is applied." 24 type: boolean 25 required: false 26 default: false 27 outputs: 28 version: 29 description: "Semantic version that was built" 30 value: ${{ jobs.prepare-metadata.outputs.version }} 31 short_sha: 32 description: "Short (10-char) commit SHA" 33 value: ${{ jobs.prepare-metadata.outputs.short_sha }} 34 commit_hash: 35 description: "Full commit SHA" 36 value: ${{ jobs.prepare-metadata.outputs.commit_hash }} 37 image_tag: 38 description: "Docker image tag (<version>-<short_sha>)" 39 value: ${{ jobs.prepare-metadata.outputs.image_tag }} 40 release_tag: 41 description: "Sanitised branch/ref name for Docker tags" 42 value: ${{ jobs.prepare-metadata.outputs.release_tag }} 43 workflow_dispatch: 44 inputs: 45 ref: 46 description: "Git ref to checkout (branch, tag, or commit SHA). Leave empty for default." 47 type: string 48 required: false 49 default: "" 50 push_to_ecr: 51 description: "Push the built images to ECR and create a multi-arch manifest." 52 type: boolean 53 required: false 54 default: true 55 run_container_scan: 56 description: "Run Prisma Cloud container scan on built images." 57 type: boolean 58 required: false 59 default: false 60 is_release_merge: 61 description: "True when this build is for a release-please merge. Controls whether the bare version tag is applied." 62 type: boolean 63 required: false 64 default: false 65 66 permissions: 67 repository-projects: read 68 contents: write 69 id-token: write 70 packages: write 71 checks: write 72 pull-requests: write 73 actions: read 74 75 concurrency: 76 group: build-push-image-${{ github.ref_name }} 77 cancel-in-progress: ${{ github.ref_name != github.event.repository.default_branch }} 78 79 jobs: 80 prepare-metadata: 81 name: Prepare Build Metadata 82 runs-on: ubuntu-latest 83 outputs: 84 version: ${{ steps.get_version.outputs.version }} 85 short_sha: ${{ steps.get_commit_hash.outputs.short_sha }} 86 commit_hash: ${{ steps.get_commit_hash.outputs.commit_hash }} 87 image_tag: ${{ steps.image_tag.outputs.image_tag }} 88 release_tag: ${{ steps.release_tag.outputs.release_tag }} 89 docker_push: ${{ steps.set_build_params.outputs.docker_push }} 90 steps: 91 - name: Checkout repository 92 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 93 with: 94 ref: ${{ inputs.ref || github.sha }} 95 fetch-depth: 0 96 97 - name: Install uv 98 uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0 99 with: 100 enable-cache: true 101 102 - name: Sync and verify uv.lock 103 run: uv lock 104 105 - name: Get commit hash 106 id: get_commit_hash 107 run: | 108 echo "commit_hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" 109 echo "short_sha=${GITHUB_SHA:0:10}" >> "$GITHUB_OUTPUT" 110 111 - name: Get version from hatch 112 id: get_version 113 run: | 114 uv tool install --with virtualenv==20.26.6 hatch 115 echo "version=$(hatch version)" >> "$GITHUB_OUTPUT" 116 117 - name: Determine image tag 118 id: image_tag 119 env: 120 VERSION: ${{ steps.get_version.outputs.version }} 121 SHORT_SHA: ${{ steps.get_commit_hash.outputs.short_sha }} 122 run: echo "image_tag=${VERSION}-${SHORT_SHA}" >> "$GITHUB_OUTPUT" 123 124 - name: Determine release tag 125 id: release_tag 126 env: 127 REF_NAME: ${{ github.ref_name }} 128 run: echo "release_tag=${REF_NAME//\//-}" >> "$GITHUB_OUTPUT" 129 130 - name: Set build params 131 id: set_build_params 132 env: 133 PUSH_INPUT: ${{ inputs.push_to_ecr }} 134 EVENT_NAME: ${{ github.event_name }} 135 REF_NAME: ${{ github.ref_name }} 136 DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} 137 run: | 138 docker_push=false 139 if [[ "$PUSH_INPUT" == "true" ]]; then 140 docker_push=true 141 elif [[ "$EVENT_NAME" == "push" && "$REF_NAME" == "$DEFAULT_BRANCH" ]]; then 142 docker_push=true 143 fi 144 echo "docker_push=${docker_push}" >> "$GITHUB_OUTPUT" 145 146 build-platform: 147 name: Build ${{ matrix.platform.name }} 148 needs: prepare-metadata 149 strategy: 150 fail-fast: false 151 matrix: 152 platform: 153 - name: linux/amd64 154 runner: ubuntu-24.04 155 tag_suffix: amd64 156 - name: linux/arm64 157 runner: ubuntu-24.04-arm 158 tag_suffix: arm64 159 runs-on: ${{ matrix.platform.runner }} 160 steps: 161 - name: Checkout repository 162 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 163 with: 164 ref: ${{ inputs.ref || github.sha }} 165 166 - name: Setup Docker Buildx 167 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 168 169 - name: Configure AWS credentials 170 uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1 171 with: 172 aws-access-key-id: ${{ secrets.SAM_AWS_ACCESS_KEY_ID }} 173 aws-secret-access-key: ${{ secrets.SAM_AWS_SECRET_ACCESS_KEY }} 174 aws-region: ${{ secrets.AWS_DEFAULT_REGION }} 175 176 - name: Login to Amazon ECR 177 id: login-ecr 178 uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 179 180 - name: Determine image name and cache configuration 181 id: image_name 182 env: 183 ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 184 PLATFORM_SUFFIX: ${{ matrix.platform.tag_suffix }} 185 DOCKER_PUSH: ${{ needs.prepare-metadata.outputs.docker_push }} 186 HEAD_REF: ${{ github.head_ref || github.ref_name }} 187 DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} 188 run: | 189 IMAGE_NAME="${ECR_REGISTRY}/solace-agent-mesh" 190 echo "image_name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT" 191 192 if [[ "$DOCKER_PUSH" == "true" ]]; then 193 CACHE_REF="${IMAGE_NAME}:buildcache-${PLATFORM_SUFFIX}" 194 echo "cache_from=type=registry,ref=${CACHE_REF}" >> "$GITHUB_OUTPUT" 195 echo "cache_to=type=registry,ref=${CACHE_REF},mode=max" >> "$GITHUB_OUTPUT" 196 else 197 PR_BRANCH="${HEAD_REF//\//-}" 198 MAIN_CACHE="${IMAGE_NAME}:buildcache-${PLATFORM_SUFFIX}" 199 PR_CACHE="${IMAGE_NAME}:buildcache-${PR_BRANCH}-${PLATFORM_SUFFIX}" 200 echo "cache_from=${PR_CACHE}" >> "$GITHUB_OUTPUT" 201 echo "cache_from_secondary=${MAIN_CACHE}" >> "$GITHUB_OUTPUT" 202 echo "cache_to=type=registry,ref=${PR_CACHE},mode=max" >> "$GITHUB_OUTPUT" 203 fi 204 205 - name: Prepare cache-from list 206 id: cache_list 207 env: 208 PRIMARY: ${{ steps.image_name.outputs.cache_from }} 209 SECONDARY: ${{ steps.image_name.outputs.cache_from_secondary }} 210 run: | 211 CACHE_FROM="type=registry,ref=${PRIMARY}" 212 if [[ -n "$SECONDARY" ]]; then 213 CACHE_FROM="${CACHE_FROM}"$'\n'"type=registry,ref=${SECONDARY}" 214 fi 215 { 216 echo "cache_from<<EOF" 217 echo "$CACHE_FROM" 218 echo "EOF" 219 } >> "$GITHUB_OUTPUT" 220 221 - name: Build and push platform-specific image 222 uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 223 with: 224 context: . 225 file: Dockerfile 226 platforms: ${{ matrix.platform.name }} 227 tags: ${{ steps.image_name.outputs.image_name }}:${{ needs.prepare-metadata.outputs.version }}-${{ needs.prepare-metadata.outputs.short_sha }}-${{ matrix.platform.tag_suffix }} 228 push: ${{ fromJSON(inputs.push_to_ecr) }} 229 load: ${{ !fromJSON(inputs.push_to_ecr) }} 230 cache-from: ${{ steps.cache_list.outputs.cache_from }} 231 cache-to: ${{ steps.image_name.outputs.cache_to }} 232 provenance: false 233 sbom: false 234 build-args: | 235 INSTALL_LIBREOFFICE=${{ secrets.INSTALL_LIBREOFFICE || 'false' }} 236 237 - name: Run Prisma Cloud scan 238 if: ${{ fromJSON(inputs.run_container_scan) }} 239 id: prisma_scan 240 continue-on-error: true 241 uses: SolaceDev/solace-public-workflows/prisma-cloud-scan@main 242 with: 243 image_registry: ${{ steps.login-ecr.outputs.registry }} 244 image_repo: ${{ github.event.repository.name }} 245 image_tag: ${{ format('{0}-{1}-{2}', needs.prepare-metadata.outputs.version, needs.prepare-metadata.outputs.short_sha, matrix.platform.tag_suffix) }} 246 pcc_console_url: ${{ vars.PRISMACLOUD_CONSOLE_URL }} 247 pcc_user: ${{ secrets.PRISMA_ACCESS_KEY_ID }} 248 pcc_pass: ${{ secrets.PRISMA_SECRET_ACCESS_KEY }} 249 twistcli_publish: "true" 250 vulnerability_grace_period_days: "7" 251 skip_image_pull: ${{ !fromJSON(inputs.push_to_ecr) }} 252 253 merge-manifest: 254 name: Create Multi-Platform Manifest 255 needs: [prepare-metadata, build-platform] 256 if: ${{ fromJSON(inputs.push_to_ecr) }} 257 runs-on: ubuntu-latest 258 steps: 259 - name: Configure AWS credentials 260 uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1 261 with: 262 aws-access-key-id: ${{ secrets.SAM_AWS_ACCESS_KEY_ID }} 263 aws-secret-access-key: ${{ secrets.SAM_AWS_SECRET_ACCESS_KEY }} 264 aws-region: ${{ secrets.AWS_DEFAULT_REGION }} 265 266 - name: Login to Amazon ECR 267 uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 268 269 - name: Setup Docker Buildx 270 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 271 272 - name: Create and push multi-platform manifest 273 env: 274 REGISTRY: ${{ secrets.SAM_AWS_ECR_REGISTRY }} 275 VERSION: ${{ needs.prepare-metadata.outputs.version }} 276 SHORT_SHA: ${{ needs.prepare-metadata.outputs.short_sha }} 277 RELEASE_TAG: ${{ needs.prepare-metadata.outputs.release_tag }} 278 COMMIT_SHA: ${{ needs.prepare-metadata.outputs.commit_hash }} 279 IS_RELEASE_MERGE: ${{ inputs.is_release_merge }} 280 run: | 281 set -euo pipefail 282 283 IMG="${REGISTRY}/solace-agent-mesh" 284 AMD="${IMG}:${VERSION}-${SHORT_SHA}-amd64" 285 ARM="${IMG}:${VERSION}-${SHORT_SHA}-arm64" 286 BUILD_TIME="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" 287 288 TAGS=("${IMG}:${VERSION}-${SHORT_SHA}") 289 if [[ "$IS_RELEASE_MERGE" == "true" ]]; then 290 TAGS+=("${IMG}:${VERSION}") 291 TAGS+=("${IMG}:${RELEASE_TAG}") 292 fi 293 TAGS+=("${IMG}:${RELEASE_TAG}-${SHORT_SHA}") 294 295 for TAG in "${TAGS[@]}"; do 296 echo "Creating manifest for: $TAG" 297 docker buildx imagetools create \ 298 --annotation "index:org.opencontainers.image.revision=${COMMIT_SHA}" \ 299 --annotation "index:org.opencontainers.image.created=${BUILD_TIME}" \ 300 --annotation "index:org.opencontainers.image.source=https://github.com/${{ github.repository }}" \ 301 --tag "$TAG" \ 302 "$AMD" "$ARM" 303 done 304 305 - name: Update DynamoDB release manifest 306 env: 307 VERSION: ${{ needs.prepare-metadata.outputs.version }} 308 SHORT_SHA: ${{ needs.prepare-metadata.outputs.short_sha }} 309 COMMIT_SHA: ${{ needs.prepare-metadata.outputs.commit_hash }} 310 run: | 311 ATTRS="$(jq -nc \ 312 --arg version "$VERSION" \ 313 --arg image_tag "${VERSION}-${SHORT_SHA}" \ 314 --arg chart_version "n/a" \ 315 --arg sha "$COMMIT_SHA" \ 316 --arg release_version "${VERSION}-${SHORT_SHA}" \ 317 '{":r": {"M": {"version": {"S": $version}, "image_tag": {"S": $image_tag}, "chart_version": {"S": $chart_version}, "sha": {"S": $sha}, "release_version": {"S": $release_version}}}}')" 318 aws dynamodb update-item \ 319 --table-name solace-cloud-manifest \ 320 --key '{"squad":{"S":"ai"},"repository":{"S":"solace-agent-mesh"}}' \ 321 --update-expression "SET dev = :r" \ 322 --expression-attribute-values "$ATTRS"