|
1 | | -name: '[reusable only] Build images and push to registry' |
| 1 | +name: "[reusable only] Build images and push to registry" |
2 | 2 |
|
3 | 3 | on: |
| 4 | + workflow_dispatch: |
4 | 5 | workflow_call: |
5 | 6 | inputs: |
6 | 7 | tag: |
|
17 | 18 | required: true |
18 | 19 | SENTRY_AUTH_TOKEN: |
19 | 20 |
|
| 21 | +env: |
| 22 | + DOCKER_BUILDKIT: 1 |
| 23 | + COMPOSE_DOCKER_CLI_BUILD: 1 |
| 24 | + |
20 | 25 | jobs: |
| 26 | + build-info: |
| 27 | + runs-on: ubuntu-latest |
| 28 | + outputs: |
| 29 | + repo-owner: ${{ steps.repo-owner.outputs.result }} |
| 30 | + tags: ${{ steps.image-tags.outputs.image-tags }} |
| 31 | + build-config: ${{ steps.build-info.outputs.result }} |
| 32 | + steps: |
| 33 | + #github forces lower case for the image name |
| 34 | + - name: Get lowercase repo owner name |
| 35 | + uses: actions/github-script@v7 |
| 36 | + id: repo-owner |
| 37 | + with: |
| 38 | + result-encoding: string |
| 39 | + script: | |
| 40 | + return context.repo.owner.toLowerCase() |
| 41 | +
|
| 42 | + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 |
| 43 | + |
| 44 | + - name: Set nightly tag if commit was on main |
| 45 | + id: add-nightly-tag |
| 46 | + if: startsWith(github.ref, 'refs/heads/devel') |
| 47 | + run: | |
| 48 | + echo "nightly-tag=nightly" | tr -d "\n" >> $GITHUB_OUTPUT |
| 49 | +
|
| 50 | + - name: Set latest tag if its a tag |
| 51 | + id: add-latest-tag |
| 52 | + if: startsWith(github.ref, 'refs/tags/') |
| 53 | + run: | |
| 54 | + echo "latest-tag=latest" | tr -d "\n" >> $GITHUB_OUTPUT |
| 55 | +
|
| 56 | + - uses: actions/github-script@v7 |
| 57 | + id: get-tag |
| 58 | + if: startsWith(github.ref, 'refs/tags/') |
| 59 | + with: |
| 60 | + result-encoding: string |
| 61 | + script: | |
| 62 | + return context.payload.ref.replace('refs/tags/', '') |
| 63 | +
|
| 64 | + - name: concat tags to list |
| 65 | + id: image-tags |
| 66 | + run: | |
| 67 | + TAGS=$(cat <<-END |
| 68 | + [ |
| 69 | + "${{ inputs.sha || github.sha }}", |
| 70 | + "${{ steps.add-nightly-tag.outputs.nightly-tag }}", |
| 71 | + "${{ steps.add-latest-tag.outputs.latest-tag }}", |
| 72 | + "${{ steps.get-tag.outputs.result }}" |
| 73 | + ] |
| 74 | + END |
| 75 | + ) |
| 76 | + TAGS=$(echo $TAGS | jq -c 'map(select(length > 0))') |
| 77 | + echo "image-tags=$TAGS" | tr -d "\n" >> $GITHUB_OUTPUT |
| 78 | +
|
| 79 | + - name: Get build info |
| 80 | + id: build-info |
| 81 | + run: | |
| 82 | + set -x |
| 83 | + sudo snap install yq |
| 84 | +
|
| 85 | + export REPO_NAME=$(basename $(pwd)) |
| 86 | + echo "services:" > /tmp/docker-compose.yml |
| 87 | + for i in $(find . -name docker-compose.yml); do |
| 88 | + docker compose -f $i config | yq '.services | select(.[].build != null and .[].image != null)' | sed 's/^/ /' >> /tmp/docker-compose.yml |
| 89 | + done |
| 90 | +
|
| 91 | + cat /tmp/docker-compose.yml |
| 92 | +
|
| 93 | + yq_pipe='.services' |
| 94 | + yq_pipe=$yq_pipe'| to_entries[]' |
| 95 | + yq_pipe=$yq_pipe'| select(.value.build != null and .value.image != null)' |
| 96 | + yq_pipe=$yq_pipe'| .value.build.image=.value.image' |
| 97 | + yq_pipe=$yq_pipe'| .value.build.service=.key' |
| 98 | + yq_pipe=$yq_pipe'| .value.build' |
| 99 | + yq_pipe=$yq_pipe'| .dockerfile=.context + "/" + .dockerfile' |
| 100 | + yq_pipe=$yq_pipe'| [.]' |
| 101 | + cat /tmp/docker-compose.yml | yq "$yq_pipe" |
| 102 | + BUILD_INFO=$(cat /tmp/docker-compose.yml | yq "$yq_pipe" -o=json) |
| 103 | + BUILD_INFO=$(echo $BUILD_INFO | jq -c -s add | sed "s|:local||g") |
| 104 | + echo $BUILD_INFO |
| 105 | + echo "result=$BUILD_INFO" | tr -d "\n" >> $GITHUB_OUTPUT |
| 106 | + env: |
| 107 | + REPO_OWNER: ${{ vars.DOCKER_HUB_USERNAME || steps.repo-owner.outputs.result }} |
| 108 | + VERSION: local |
| 109 | + |
21 | 110 | build-and-push: |
22 | | - name: Build images and push |
23 | 111 | runs-on: ubuntu-latest |
| 112 | + name: "Build and push image ${{ matrix.build-config.service }}" |
| 113 | + needs: |
| 114 | + - build-info |
| 115 | + strategy: |
| 116 | + fail-fast: false |
| 117 | + matrix: |
| 118 | + build-config: ${{ fromJSON(needs.build-info.outputs.build-config) }} |
| 119 | + env: |
| 120 | + tags: ${{ needs.build-info.outputs.tags }} |
| 121 | + repo-owner: ${{ needs.build-info.outputs.repo-owner }} |
24 | 122 | steps: |
25 | 123 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 |
26 | 124 | with: |
27 | | - ref: ${{ inputs.sha }} |
| 125 | + fetch-depth: 100 |
| 126 | + |
| 127 | + - run: | |
| 128 | + echo "inputs:" |
| 129 | + cat <<-HEREDOC |
| 130 | + ${{ toJSON(inputs) }} |
| 131 | + HEREDOC |
| 132 | +
|
| 133 | + echo "build config:" |
| 134 | + cat <<-HEREDOC |
| 135 | + ${{ toJSON(matrix.build-config) }} |
| 136 | + HEREDOC |
| 137 | +
|
| 138 | + echo "build tags:" |
| 139 | + cat <<-HEREDOC |
| 140 | + ${{ env.tags }} |
| 141 | + HEREDOC |
| 142 | +
|
| 143 | + echo "build repo owner:" |
| 144 | + cat <<-HEREDOC |
| 145 | + ${{ env.repo-owner }} |
| 146 | + HEREDOC |
| 147 | +
|
| 148 | + if [ ! echo "${{ matrix.build-config.image }}" | grep -Eq '^[a-zA-Z0-9._/-]+$' ]; then |
| 149 | + echo "Error: Invalid Docker image name '${{ matrix.build-config.image }}'." |
| 150 | + exit 1 |
| 151 | + fi |
28 | 152 |
|
29 | 153 | - name: Set up Docker Buildx |
30 | 154 | uses: docker/setup-buildx-action@v3 |
31 | 155 |
|
32 | 156 | - name: Login to DockerHub |
33 | 157 | uses: docker/login-action@v3 |
34 | 158 | with: |
35 | | - username: ${{ vars.DOCKER_HUB_USERNAME }} |
| 159 | + username: ${{ vars.DOCKER_HUB_USERNAME || env.repo-owner }} |
36 | 160 | password: ${{ secrets.DOCKER_HUB_PASSWORD }} |
37 | 161 |
|
38 | | - - name: Build and push frontend docker image |
39 | | - uses: docker/build-push-action@v6 |
40 | | - with: |
41 | | - push: true |
42 | | - file: .docker-hub/frontend/Dockerfile |
43 | | - tags: | |
44 | | - ${{ ((inputs.tag != '') && format('{0}/ecamp3-frontend:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }} |
45 | | - ${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-frontend:${{ inputs.sha }} |
46 | | - context: . |
47 | | - build-args: | |
48 | | - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} |
49 | | - SENTRY_ORG=${{ vars.SENTRY_ORG }} |
50 | | - SENTRY_FRONTEND_PROJECT=${{ vars.SENTRY_FRONTEND_PROJECT }} |
51 | | - SENTRY_RELEASE_NAME=${{ inputs.sha }} |
52 | | - cache-from: type=gha,scope=frontend |
53 | | - cache-to: type=gha,scope=frontend,mode=max |
54 | | - |
55 | | - - name: Build and push api docker image |
56 | | - uses: docker/build-push-action@v6 |
| 162 | + - name: find latest commit for image tag |
| 163 | + id: get-latest-commit |
| 164 | + run: | |
| 165 | + set -x |
| 166 | + context=$( echo '${{ toJSON(matrix.build-config) }}' | jq -r '.context' | sed "s|$PWD|.|g") |
| 167 | + echo "context: $context" |
| 168 | + dockerfile=$( echo '${{ toJSON(matrix.build-config) }}' | jq -r '.dockerfile' | sed "s|$PWD|.|g") |
| 169 | + echo "dockerfile: $dockerfile" |
| 170 | + git log --stat -n 1 $context $dockerfile |
| 171 | + latest_commit=$(git log --pretty=format:"%H" -n 1 $context $dockerfile) |
| 172 | + echo "latest_commit: $latest_commit" |
| 173 | + echo "result=$latest_commit" | tr -d "\n" >> $GITHUB_OUTPUT |
| 174 | +
|
| 175 | + - name: check if image already exists |
| 176 | + id: check-image |
| 177 | + run: | |
| 178 | + set +e |
| 179 | + docker pull ${{ matrix.build-config.image }}:${{ steps.get-latest-commit.outputs.result }} |
| 180 | + image_exists=$? |
| 181 | + set -e |
| 182 | + if ([ $image_exists -eq 0 ]); then |
| 183 | + echo "image already exists" |
| 184 | + echo "result=true" | tr -d "\n" >> $GITHUB_OUTPUT |
| 185 | + else |
| 186 | + echo "image does not exist" |
| 187 | + echo "result=false" | tr -d "\n" >> $GITHUB_OUTPUT |
| 188 | + fi |
| 189 | +
|
| 190 | + - name: Add latest commit to image tag if image does not exist |
| 191 | + id: add-latest-commit-to-tags-if-image-does-not-exist |
| 192 | + uses: actions/github-script@v7 |
57 | 193 | with: |
58 | | - push: true |
59 | | - file: api/Dockerfile |
60 | | - tags: | |
61 | | - ${{ ((inputs.tag != '') && format('{0}/ecamp3-api:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }} |
62 | | - ${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-api:${{ inputs.sha }} |
63 | | - context: './api' |
64 | | - target: frankenphp_prod |
65 | | - cache-from: type=gha,scope=api |
66 | | - cache-to: type=gha,scope=api,mode=max |
67 | | - |
68 | | - - name: Build and push print docker image |
69 | | - uses: docker/build-push-action@v6 |
| 194 | + script: | |
| 195 | + if (!${{ steps.check-image.outputs.result }}) { |
| 196 | + return [...JSON.parse('${{ env.tags }}'), '${{ steps.get-latest-commit.outputs.result }}'] |
| 197 | + } |
| 198 | + return JSON.parse('${{ env.tags }}') |
| 199 | +
|
| 200 | + - uses: actions/github-script@v7 |
| 201 | + id: expand-tags |
70 | 202 | with: |
71 | | - push: true |
72 | | - file: .docker-hub/print/Dockerfile |
73 | | - tags: | |
74 | | - ${{ ((inputs.tag != '') && format('{0}/ecamp3-print:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }} |
75 | | - ${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-print:${{ inputs.sha }} |
76 | | - context: . |
77 | | - build-args: | |
78 | | - SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} |
79 | | - SENTRY_ORG=${{ vars.SENTRY_ORG }} |
80 | | - SENTRY_PRINT_PROJECT=${{ vars.SENTRY_PRINT_PROJECT }} |
81 | | - SENTRY_RELEASE_NAME=${{ inputs.sha }} |
82 | | - cache-from: type=gha,scope=print |
83 | | - cache-to: type=gha,scope=print,mode=max |
84 | | - |
85 | | - - name: Build and push varnish docker image |
86 | | - uses: docker/build-push-action@v6 |
| 203 | + script: | |
| 204 | + return JSON.parse('${{ steps.add-latest-commit-to-tags-if-image-does-not-exist.outputs.result }}').map(tag => `${{ matrix.build-config.image }}:${ tag }`) |
| 205 | +
|
| 206 | + - name: populate build args from secrets/vars |
| 207 | + if: steps.check-image.outputs.result == 'false' |
| 208 | + id: populate-build-args |
| 209 | + uses: actions/github-script@v7 |
87 | 210 | with: |
88 | | - push: true |
89 | | - file: .docker-hub/varnish/Dockerfile |
90 | | - tags: | |
91 | | - ${{ ((inputs.tag != '') && format('{0}/ecamp3-varnish:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }} |
92 | | - ${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-varnish:${{ inputs.sha }} |
93 | | - context: . |
94 | | - cache-from: type=gha,scope=varnish |
95 | | - cache-to: type=gha,scope=varnish,mode=max |
96 | | - |
97 | | - - name: Build and push db-backup-restore docker image |
| 211 | + script: | |
| 212 | + const buildArgValues = { |
| 213 | + "SENTRY_AUTH_TOKEN" : "${{ secrets.SENTRY_AUTH_TOKEN }}", |
| 214 | + "SENTRY_FRONTEND_PROJECT" : "${{ vars.SENTRY_FRONTEND_PROJECT }}", |
| 215 | + "SENTRY_ORG" : "${{ vars.SENTRY_ORG }}", |
| 216 | + "SENTRY_PRINT_PROJECT" : "${{ vars.SENTRY_PRINT_PROJECT }}", |
| 217 | + "SENTRY_RELEASE_NAME" : "${{ inputs.sha }}", |
| 218 | + } |
| 219 | + const args = JSON.parse(`${{ toJSON(matrix.build-config.args) }}`) |
| 220 | + let result = "" |
| 221 | + for (const arg in args) { |
| 222 | + if (buildArgValues[arg]) { |
| 223 | + args[arg] = buildArgValues[arg] |
| 224 | + } |
| 225 | + } |
| 226 | + return args |
| 227 | +
|
| 228 | + - name: transform build args |
| 229 | + if: steps.check-image.outputs.result == 'false' |
| 230 | + id: transform-build-args |
| 231 | + run: | |
| 232 | + set -x |
| 233 | + echo 'result<<EOF' >> $GITHUB_OUTPUT |
| 234 | + echo '${{ steps.populate-build-args.outputs.result }}' | yq -p json >> $GITHUB_OUTPUT |
| 235 | + echo 'EOF' >> $GITHUB_OUTPUT |
| 236 | + cat $GITHUB_OUTPUT |
| 237 | +
|
| 238 | + - name: debug transform build args output |
| 239 | + if: steps.check-image.outputs.result == 'false' |
| 240 | + run: | |
| 241 | + echo "result: ${{ steps.transform-build-args.outputs.result }}" |
| 242 | + echo "result: ${{ toJSON(steps.transform-build-args.outputs) }}" |
| 243 | +
|
| 244 | + - name: Build and push image |
98 | 245 | uses: docker/build-push-action@v6 |
| 246 | + if: steps.check-image.outputs.result == 'false' |
99 | 247 | with: |
100 | 248 | push: true |
101 | | - file: .helm/ecamp3/files/db-backup-restore-image/Dockerfile |
102 | | - tags: | |
103 | | - ${{ ((inputs.tag != '') && format('{0}/ecamp3-db-backup-restore:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }} |
104 | | - ${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-db-backup-restore:${{ inputs.sha }} |
105 | | - context: . |
106 | | - cache-from: type=gha,scope=db-backup-restore |
107 | | - cache-to: type=gha,scope=db-backup-restore,mode=max |
| 249 | + file: ${{ matrix.build-config.dockerfile }} |
| 250 | + tags: ${{ join(fromJSON(steps.expand-tags.outputs.result)) }} |
| 251 | + context: ${{ matrix.build-config.context }} |
| 252 | + build-args: | |
| 253 | + ${{ steps.transform-build-args.outputs.result }} |
| 254 | + cache-from: type=gha,scope=${{ matrix.build-config.image }} |
| 255 | + cache-to: type=gha,scope=${{ matrix.build-config.image }},mode=max |
| 256 | + |
| 257 | + - name: Retag and push images |
| 258 | + if: steps.check-image.outputs.result == 'true' |
| 259 | + run: | |
| 260 | + for tag in $(echo '${{ env.tags }}' | jq -r '.[]'); do |
| 261 | + docker tag ${{ matrix.build-config.image }}:${{ steps.get-latest-commit.outputs.result }} ${{ matrix.build-config.image }}:${tag} |
| 262 | + docker push ${{ matrix.build-config.image }}:${tag} |
| 263 | + done |
0 commit comments