/ .github / workflows / build-push-image.yaml
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"