|
11 | 11 | description: 'Docker image name' |
12 | 12 | type: string |
13 | 13 | required: true |
14 | | - tag: |
15 | | - description: 'Docker image tag' |
16 | | - type: string |
17 | | - required: true |
| 14 | + tag-for-production: |
| 15 | + description: 'Whether or not to add a tag indicating this is being used in production' |
| 16 | + type: boolean |
| 17 | + required: false |
| 18 | + default: false |
18 | 19 | context: |
19 | 20 | description: 'Build context path' |
20 | 21 | type: string |
21 | 22 | required: false |
22 | 23 | default: './' |
| 24 | + push-role-name: |
| 25 | + type: string |
| 26 | + description: What's the IAM role name to use for Pushing to the ECR? |
| 27 | + required: false |
| 28 | + default: no-push |
| 29 | + save-as-artifact: |
| 30 | + type: boolean |
| 31 | + description: 'Should the image be saved as an artifact?' |
| 32 | + required: false |
| 33 | + default: false |
23 | 34 |
|
| 35 | +permissions: |
| 36 | + id-token: write |
| 37 | + contents: write # needed for mutex |
24 | 38 |
|
25 | 39 | jobs: |
26 | 40 | build-image: |
27 | 41 | name: Build Docker Image |
28 | 42 | runs-on: ubuntu-24.04 |
29 | 43 | steps: |
| 44 | + - name: Parse ECR URL |
| 45 | + if: ${{ inputs.push-role-name != 'no-push' }} |
| 46 | + id: parse_ecr_url |
| 47 | + run: | |
| 48 | + ECR_URL="${{ inputs.repository}}" |
| 49 | +
|
| 50 | + # Extract the AWS Account ID, which is the first field |
| 51 | + AWS_ACCOUNT_ID=$(echo "$ECR_URL" | cut -d. -f1) |
| 52 | +
|
| 53 | + # Extract the AWS Region, which is the fourth field in the URL structure |
| 54 | + AWS_REGION=$(echo "$ECR_URL" | cut -d. -f4) |
| 55 | +
|
| 56 | + # Set the outputs for use in later steps |
| 57 | + echo "aws_account_id=${AWS_ACCOUNT_ID}" >> "$GITHUB_OUTPUT" |
| 58 | + echo "aws_region=${AWS_REGION}" >> "$GITHUB_OUTPUT" |
| 59 | + shell: bash |
| 60 | + |
30 | 61 | - name: Checkout code |
31 | 62 | uses: actions/checkout@v4.2.2 |
32 | 63 |
|
| 64 | + - name: OIDC Auth for ECR |
| 65 | + if: ${{ inputs.push-role-name != 'no-push' }} |
| 66 | + uses: aws-actions/configure-aws-credentials@v4.1.0 |
| 67 | + with: |
| 68 | + role-to-assume: arn:aws:iam::${{ steps.parse_ecr_url.outputs.aws_account_id }}:role/${{ inputs.push-role-name }} |
| 69 | + aws-region: ${{ steps.parse_ecr_url.outputs.aws_region }} |
| 70 | + |
| 71 | + - name: Calculate hash of files in build context |
| 72 | + id: calculate-build-context-hash |
| 73 | + run: | |
| 74 | + python3 --version |
| 75 | + BUILD_HASH=$(python3 .github/workflows/hash_git_files.py ${{ inputs.context }}) |
| 76 | + echo "build_context_tag=context-${BUILD_HASH}" >> "$GITHUB_OUTPUT" |
| 77 | + echo "Calculated build context tag as: ${BUILD_HASH}" |
| 78 | +
|
| 79 | + IMAGE_NAME_WITH_NAMESPACE="${{ inputs.image_name }}" |
| 80 | + IMAGE_NAME_NO_SLASHES="${IMAGE_NAME_WITH_NAMESPACE//\//-}" |
| 81 | + echo "image_name_no_slashes=${IMAGE_NAME_NO_SLASHES}" >> "$GITHUB_OUTPUT" |
| 82 | + echo "Image name without slashes: ${IMAGE_NAME_NO_SLASHES}" |
| 83 | +
|
| 84 | + - name: Set up mutex # Github concurrency management is horrible, things get arbitrarily cancelled if queued up. So using mutex until github fixes itself. When multiple jobs are modifying cache at once, weird things can happen. possible issue is https://github.com/actions/toolkit/issues/658 |
| 85 | + if: ${{ inputs.push-role-name != 'no-push' }} |
| 86 | + uses: ben-z/gh-action-mutex@1ebad517141198e08d47cf72f3c0975316620a65 # v1.0.0-alpha.10 |
| 87 | + with: |
| 88 | + branch: mutex-${{ inputs.repository }}-${{ inputs.image_name }} |
| 89 | + timeout-minutes: 30 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it |
| 90 | + |
| 91 | + - name: Test if docker image exists |
| 92 | + if: ${{ inputs.push-role-name != 'no-push' }} |
| 93 | + id: check-if-exists |
| 94 | + run: | |
| 95 | + BUILD_HASH=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} |
| 96 | + echo Checking for : $BUILD_HASH |
| 97 | + if aws ecr describe-images --region ${{ steps.parse_ecr_url.outputs.aws_region }} --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids=imageTag=$BUILD_HASH; then \ |
| 98 | + echo "Image was found in ECR"; \ |
| 99 | + echo "status=found" >> $GITHUB_OUTPUT |
| 100 | + else \ |
| 101 | + echo "Image was not found in ECR"; \ |
| 102 | + echo "status=notfound" >> $GITHUB_OUTPUT |
| 103 | + fi |
| 104 | +
|
| 105 | + - name: Login to Amazon ECR |
| 106 | + if: ${{ inputs.push-role-name != 'no-push' && (steps.check-if-exists.outputs.status == 'notfound' || inputs.save-as-artifact ) }} |
| 107 | + id: login-ecr |
| 108 | + uses: aws-actions/amazon-ecr-login@v2.0.1 |
| 109 | + |
| 110 | + - name: Pull existing image to package as artifact |
| 111 | + if: ${{ inputs.save-as-artifact && steps.check-if-exists.outputs.status == 'found' }} |
| 112 | + run: | |
| 113 | + docker pull ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} |
| 114 | +
|
33 | 115 | - name: Set up Docker Buildx |
| 116 | + if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} |
34 | 117 | uses: docker/setup-buildx-action@v3.10.0 |
35 | 118 | with: |
36 | 119 | version: v0.22.0 |
37 | 120 |
|
38 | 121 | - name: Build Docker Image |
| 122 | + if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} |
39 | 123 | uses: docker/build-push-action@v6.15.0 |
40 | 124 | with: |
41 | 125 | context: ${{ inputs.context }} |
42 | | - push: false |
43 | | - load: true # make the image available later for the `docker save` step |
44 | | - tags: ${{ inputs.repository }}/${{ inputs.image_name }}:${{ inputs.tag }} |
| 126 | + push: ${{ inputs.push-role-name != 'no-push' && steps.check-if-exists.outputs.status == 'notfound' }} |
| 127 | + load: ${{ inputs.save-as-artifact }} # make the image available later for the `docker save` step |
| 128 | + tags: ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} |
| 129 | + |
| 130 | + - name: Add git sha tag |
| 131 | + if: ${{ inputs.push-role-name != 'no-push' }} |
| 132 | + run: | |
| 133 | + aws ecr batch-get-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids imageTag=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --query 'images[].imageManifest' --output text > manifest.json |
| 134 | + aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag git-sha-${{ github.sha }} --image-manifest file://manifest.json |
| 135 | +
|
| 136 | + - name: Add tag for Production |
| 137 | + if: ${{ inputs.push-role-name != 'no-push' && inputs.tag-for-production }} |
| 138 | + run: | |
| 139 | + aws ecr batch-get-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-ids imageTag=${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --query 'images[].imageManifest' --output text > manifest.json |
| 140 | + # TODO: figure out some better conditional logic about adding a tag for the context in production, so we don't have to `|| true` at the end |
| 141 | + aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag production--${{ steps.calculate-build-context-hash.outputs.build_context_tag }} --image-manifest file://manifest.json || true |
| 142 | + aws ecr put-image --registry-id=${{ steps.parse_ecr_url.outputs.aws_account_id }} --repository-name=${{ inputs.image_name }} --image-tag production--git-sha-${{ github.sha }} --image-manifest file://manifest.json |
45 | 143 |
|
46 | 144 | - name: Save Docker Image as tar |
47 | | - run: docker save -o ${{ inputs.image_name }}.tar ${{ inputs.repository }}/${{ inputs.image_name }}:${{ inputs.tag }} |
| 145 | + if: ${{ inputs.save-as-artifact }} |
| 146 | + run: docker save -o ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} |
48 | 147 |
|
49 | 148 | - name: Upload Docker Image Artifact |
| 149 | + if: ${{ inputs.save-as-artifact }} |
50 | 150 | uses: actions/upload-artifact@v4.6.2 |
51 | 151 | with: |
52 | | - name: ${{ inputs.image_name }} |
53 | | - path: ${{ inputs.image_name }}.tar |
| 152 | + name: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }} |
| 153 | + path: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar |
54 | 154 | if-no-files-found: error |
0 commit comments