diff --git a/.github/actions/buildx-bake/build/action.yml b/.github/actions/buildx-bake/build/action.yml new file mode 100644 index 0000000..fbab6bb --- /dev/null +++ b/.github/actions/buildx-bake/build/action.yml @@ -0,0 +1,90 @@ +name: "Build & Push Images" +description: "Download metadata, build multi‑arch images, push by digest, and upload per‑platform digests." +inputs: + bake-target: + description: 'Target name for `docker buildx bake`' + required: false + type: string + default: 'build' + registry: + description: 'The container registry' + required: false + default: 'ghcr.io' + registry-username: + description: 'The container registry username' + required: true + default: ${{ github.actor }} + registry-password: + description: 'The container registry password' + required: true + registry-image: + description: 'Container registry + image prefix' + required: false + type: string + free-disk-space: + description: 'Free disk space before build' + required: false + default: false + type: boolean +runs: + using: "composite" + steps: + - name: Prepare env + shell: bash + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Download meta bake file + uses: actions/download-artifact@v4 + with: + name: bake-meta + path: ${{ runner.temp }} + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.registry }} + username: ${{ inputs.registry-username }} + password: ${{ inputs.registry-password }} + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@v1.3.1 + if: ${{ inputs.free-disk-space == true }} + with: + tool-cache: true + + - name: Build & push images + id: bake + uses: docker/bake-action@v6 + env: + NO_TAG: true + with: + files: | + ./docker-bake.hcl + cwd://${{ runner.temp }}/bake-meta.json + targets: ${{ inputs.bake-target }} + set: | + ${{ inputs.registry-image != '' && format('*.tags={0}', inputs.registry-image) || '' }} + *.platform=${{ matrix.platform }} + *.output=type=image,push-by-digest=true,name-canonical=true,push=true + + - name: Export container digests + shell: bash + run: | + mkdir -p ${{ runner.temp }}/digests + echo '${{ steps.bake.outputs.metadata }}' | jq 'with_entries(select(.value["containerimage.digest"]?) | .value |= {name: .["image.name"], digests:[.["containerimage.digest"]]})' > ${{ runner.temp }}/digests/${{ env.PLATFORM_PAIR }}.json + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 diff --git a/.github/actions/buildx-bake/merge/action.yml b/.github/actions/buildx-bake/merge/action.yml new file mode 100644 index 0000000..a503eec --- /dev/null +++ b/.github/actions/buildx-bake/merge/action.yml @@ -0,0 +1,71 @@ +name: "Merge & Publish Manifests" +description: "Download digests, create manifest lists, push them, and inspect the published images." +inputs: + registry: + description: 'The container registry' + required: false + default: 'ghcr.io' + registry-username: + description: 'The container registry username' + required: true + default: ${{ github.actor }} + registry-password: + description: 'The container registry password' + required: true +runs: + using: "composite" + steps: + - name: Download meta bake file + uses: actions/download-artifact@v4 + with: + name: bake-meta + path: ${{ runner.temp }} + + - name: Download all digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.registry }} + username: ${{ inputs.registry-username }} + password: ${{ inputs.registry-password }} + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Create manifest lists & push + shell: bash + run: | + JSON_FILE=${{ runner.temp }}/digests/all.json + jq -s ' + reduce .[] as $doc ({}; + reduce ($doc|to_entries[]) as $kv ( + .; + .[$kv.key].name = $kv.value.name + | .[$kv.key].digests = ((.[$kv.key].digests // []) + $kv.value.digests) + ) + ) + ' ${{ runner.temp }}/digests/*.json > $JSON_FILE + for key in $(jq -r 'keys[]' "$JSON_FILE"); do + image_name=$(jq -r ".\"$key\".name" "$JSON_FILE") + image_digests=$(jq -r ".\"$key\".digests[]" "$JSON_FILE" | while read -r digest; do echo "${image_name}@${digest} "; done) + image_tags=$(jq -cr '(.target."docker-metadata-action".tags // [] ) | map("-t '${image_name}':" + sub(".*:"; "")) | join(" ")' ${{ runner.temp }}/bake-meta.json) + docker buildx imagetools create \ + ${image_tags} \ + ${image_digests} + done + + - name: Inspect published images + shell: bash + run: | + JSON_FILE=${{ runner.temp }}/digests/all.json + version=$(jq -r '.target."docker-metadata-action".args.DOCKER_META_VERSION' ${{ runner.temp }}/bake-meta.json) + for key in $(jq -r 'keys[]' "$JSON_FILE"); do + image_name=$(jq -r ".\"$key\".name" "$JSON_FILE") + docker buildx imagetools inspect ${image_name}:${version} + done diff --git a/.github/actions/buildx-bake/prepare/action.yml b/.github/actions/buildx-bake/prepare/action.yml new file mode 100644 index 0000000..4bf0f2c --- /dev/null +++ b/.github/actions/buildx-bake/prepare/action.yml @@ -0,0 +1,57 @@ +name: "Prepare Build Matrix & Metadata" +description: "Generate platform matrix and bake metadata file for later steps." +inputs: + bake-target: + description: 'Target name for `docker buildx bake`' + required: false + type: string + default: 'build' + meta-tags: + description: 'The tags to add to the image' + required: false + meta-labels: + description: 'The labels to add to the image' + required: false + registry-image: + description: 'Container registry + image prefix (e.g. ghcr.io/org/repo)' + required: false + type: string +outputs: + matrix: + description: 'JSON array of platforms' + value: ${{ steps.platforms.outputs.matrix }} +runs: + using: "composite" + steps: + - name: Create matrix + id: platforms + shell: bash + run: | + MATRIX=$(docker buildx bake ${{ inputs.bake-target }} \ + --print | jq -c '.target | to_entries | map(.value.platforms[]) | unique') + echo "matrix=$MATRIX" >>${GITHUB_OUTPUT} + + - name: Show matrix + shell: bash + run: echo ${{ steps.platforms.outputs.matrix }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.registry-image }} + tags: ${{ inputs.meta-tags }} + labels: ${{ inputs.meta-labels }} + + - name: Rename meta file + shell: bash + run: | + mv "${{ steps.meta.outputs.bake-file }}" "${{ runner.temp }}/bake-meta.json" + + - name: Upload meta bake file + uses: actions/upload-artifact@v4 + with: + name: bake-meta + path: ${{ runner.temp }}/bake-meta.json + if-no-files-found: error + retention-days: 1 diff --git a/.github/actions/command/action.yml b/.github/actions/command/action.yml new file mode 100644 index 0000000..6432cb1 --- /dev/null +++ b/.github/actions/command/action.yml @@ -0,0 +1,48 @@ +name: "Run Command & Upload Artifact" +description: "Composite action to run a command and upload its output as an artifact." +inputs: + command: + description: 'Command to run' + required: true + type: string + artifact-name: + description: 'Name of the composite action' + required: false + type: string + artifact-path: + description: 'Path to the artifact to upload' + required: false + type: string + artifact-overwrite: + description: 'Whether to overwrite the artifact if it already exists' + required: false + type: boolean + default: false + artifact-if-no-files-found: + description: 'Action to take if no files are found to upload' + required: false + type: string + default: 'warn' + artifact-retention-days: + description: 'Number of days to retain the artifact' + required: false + type: number + default: 30 + +runs: + using: "composite" + steps: + - name: Run command + id: run-command + shell: bash + run: "${{ inputs.command }}" + + - name: Upload command output as artifact + uses: eviden-actions/upload-artifact@v2 + if: inputs.artifact-name != '' && inputs.artifact-path != '' + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-path }} + overwrite: ${{ inputs.artifact-overwrite }} + if-no-files-found: ${{ inputs.artifact-if-no-files-found }} + retention-days: ${{ inputs.artifact-retention-days }} diff --git a/.github/workflows/pull-request-callable.yml b/.github/workflows/pull-request-callable.yml new file mode 100644 index 0000000..015286c --- /dev/null +++ b/.github/workflows/pull-request-callable.yml @@ -0,0 +1,500 @@ +name: Pull Request + +on: + workflow_call: + secrets: + registry-password: + description: 'Password or token for registry login' + required: true + outputs: + version: + description: 'Detected or calculated version' + value: ${{ jobs.prepare.outputs.version }} + inputs: + previous-version: + description: 'The strategy to detect the previous version: auto, from-tag, from-file or manual' + required: false + default: 'auto' + type: string + next-version: + description: 'The strategy to calculate the next version: auto, semantic, from-file, increment or manual' + required: false + default: 'auto' + type: string + output-format: + description: 'The output format of the next version' + required: false + default: '{{.Major}}.{{.Minor}}.{{.Patch}}-pull-request' + type: string + version-file: + description: 'Set version in file named as input' + required: false + type: string + version-makefile: + description: 'Set version in makefile named as input' + required: false + type: string + version-justfile: + description: 'Set version in justfile named as input' + required: false + type: string + version-package: + description: 'Set version in package json named as input' + required: false + type: string + version-package-lock: + description: 'Set version in package lock named as input' + required: false + type: string + version-script: + description: 'Set version in script named as input' + required: false + type: string + version-chart: + description: 'Set version in chart named as input' + required: false + type: string + registry-image: + description: 'Container registry + image prefix (e.g. ghcr.io/org/repo)' + required: false + type: string + bake-target: + description: 'Target name for `docker buildx bake`' + required: false + type: string + default: 'build' + meta-tags: + description: 'The tags to add to the image' + required: false + type: string + meta-labels: + description: 'The labels to add to the image' + required: false + type: string + registry-username: + description: 'Username for registry login' + required: true + type: string + runner-default: + description: 'Default runner' + required: false + type: string + default: 'ubuntu-22.04' + runner-build-arm64: + description: 'Runner for arm64 builds' + required: false + type: string + default: 'ubuntu-22.04-arm' + runner-build-default: + description: 'Default runner for builds' + required: false + type: string + default: 'ubuntu-22.04' + registry: + description: 'Docker registry domain' + required: false + type: string + default: 'ghcr.io' + free-disk-space: + description: 'Free disk space before build' + required: false + default: false + type: boolean + prepare-command: + description: 'The command to run for preparing the build' + required: false + type: string + lint-command: + description: 'The command to run for linting' + required: false + type: string + checks-command: + description: 'The command to run for checks' + required: false + type: string + just-install: + description: 'Install just' + required: false + default: false + type: boolean + just-version: + description: 'The version of just to install' + required: false + default: '1.35.0' + type: string + test-command: + description: 'The command to run for testing' + required: false + type: string + test-artifact-name: + description: 'Name of the artifact to upload for test command' + required: false + type: string + test-artifact-path: + description: 'Path to the artifact to upload for test command' + required: false + type: string + test-artifact-overwrite: + description: 'Whether to overwrite the artifact if it already exists for test command' + required: false + type: boolean + default: false + test-artifact-if-no-files-found: + description: 'Action to take if no files are found to upload for test command' + required: false + type: string + default: 'warn' + test-artifact-retention-days: + description: 'Number of days to retain the artifact for test command' + required: false + type: number + default: 30 + test-unit-command: + description: 'The command to run for unit testing' + required: false + type: string + test-unit-artifact-name: + description: 'Name of the artifact to upload for unit test command' + required: false + type: string + test-unit-artifact-path: + description: 'Path to the artifact to upload for unit test command' + required: false + type: string + test-unit-artifact-overwrite: + description: 'Whether to overwrite the artifact if it already exists for unit test command' + required: false + type: boolean + default: false + test-unit-if-no-files-found: + description: 'Action to take if no files are found to upload for unit test command' + required: false + type: string + default: 'warn' + test-unit-retention-days: + description: 'Number of days to retain the artifact for unit test command' + required: false + type: number + default: 30 + test-integration-command: + description: 'The command to run for integration testing' + required: false + type: string + test-integration-artifact-name: + description: 'Name of the artifact to upload for integration test command' + required: false + type: string + test-integration-artifact-path: + description: 'Path to the artifact to upload for integration test command' + required: false + type: string + test-integration-artifact-overwrite: + description: 'Whether to overwrite the artifact if it already exists for integration test command' + required: false + type: boolean + default: false + test-integration-if-no-files-found: + description: 'Action to take if no files are found to upload for integration test command' + required: false + type: string + default: 'warn' + test-integration-retention-days: + description: 'Number of days to retain the artifact for integration test command' + required: false + type: number + default: 30 + test-e2e-command: + description: 'The command to run for end-to-end testing' + required: false + type: string + test-e2e-artifact-name: + description: 'Name of the artifact to upload for e2e test command' + required: false + type: string + test-e2e-artifact-path: + description: 'Path to the artifact to upload for e2e test command' + required: false + type: string + test-e2e-artifact-overwrite: + description: 'Whether to overwrite the artifact if it already exists for e2e test command' + required: false + type: boolean + default: false + test-e2e-if-no-files-found: + description: 'Action to take if no files are found to upload for e2e test command' + required: false + type: string + default: 'warn' + test-e2e-retention-days: + description: 'Number of days to retain the artifact for e2e test command' + required: false + type: number + default: 30 + test-coverage-command: + description: 'The command to run for test coverage' + required: false + type: string + test-coverage-artifact-name: + description: 'Name of the artifact to upload for test coverage command' + required: false + type: string + test-coverage-artifact-path: + description: 'Path to the artifact to upload for test coverage command' + required: false + type: string + test-coverage-artifact-overwrite: + description: 'Whether to overwrite the artifact if it already exists for test coverage command' + required: false + type: boolean + default: false + test-coverage-if-no-files-found: + description: 'Action to take if no files are found to upload for test coverage command' + required: false + type: string + default: 'warn' + test-coverage-retention-days: + description: 'Number of days to retain the artifact for test coverage command' + required: false + type: number + default: 30 + +permissions: + contents: read + packages: write + +jobs: + prepare: + name: Prepare Steps + runs-on: ${{ inputs.runner-default }} + outputs: + version: ${{ steps.set_version.outputs.version }} + pre-step-matrix: ${{ steps.set_pre_matrix.outputs.matrix }} + post-step-matrix: ${{ steps.set_post_matrix.outputs.matrix }} + image-platform-matrix: ${{ steps.prepare_image.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Next release version + id: release_version + uses: getdevopspro/github-actions/release-version@v0.3.0-prerelease + with: + previous-version: ${{ inputs.previous-version }} + next-version: ${{ inputs.next-version }} + output-format: ${{ inputs.output-format }} + + - name: Set version in file + uses: getdevopspro/github-actions/version-file@v0.3.0-prerelease + with: + version: ${{ steps.release_version.outputs.version }} + version-makefile: ${{ inputs.version-makefile }} + version-justfile: ${{ inputs.version-justfile }} + version-package: ${{ inputs.version-package }} + version-package-lock: ${{ inputs.version-package-lock }} + version-script: ${{ inputs.version-script }} + version-chart: ${{ inputs.version-chart }} + + - name: Install just + if: inputs.just-install + uses: extractions/setup-just@v2 + with: + just-version: ${{ inputs.just-version }} + + - name: Set pre-steps matrix + id: set_pre_matrix + shell: bash + run: | + MATRIX=[] + function jq_add_step { + MATRIX=$(echo $MATRIX | jq -c --arg name "$1" --arg cmd "$2" \ + '. + [{"name": $name, "command": $cmd}]') + } + if [[ -n "${{ inputs.lint-command }}" ]]; then + jq_add_step "Lint" "${{ inputs.lint-command }}" + fi + if [[ -n "${{ inputs.checks-command }}" ]]; then + jq_add_step "Checks" "${{ inputs.checks-command }}" + fi + echo "matrix=$MATRIX" >>${GITHUB_OUTPUT} + + - name: Show pre-steps matrix + shell: bash + run: echo "${{ steps.set_pre_matrix.outputs.matrix }}" + + - name: Set post-steps matrix + id: set_post_matrix + shell: bash + run: | + MATRIX=[] + function jq_add_step { + MATRIX=$(echo $MATRIX | jq -c --arg name "$1" --arg cmd "$2" --arg artifact_name "$3" --arg artifact_path "$4" \ + --arg artifact_overwrite "$5" --arg artifact_if_no_files_found "$6" --arg artifact_retention_days "$7" \ + '. + [{"name": $name, "command": $cmd, "artifact-name": $artifact_name, "artifact-path": $artifact_path, "artifact-overwrite": $artifact_overwrite, "artifact-if-no-files-found": $artifact_if_no_files_found, "artifact-retention-days": $artifact_retention_days}]') + } + if [[ -n "${{ inputs.test-command }}" ]]; then + jq_add_step "Test" "${{ inputs.test-command }}" "${{ inputs.test-artifact-name }}" "${{ inputs.test-artifact-path }}" \ + "${{ inputs.test-artifact-overwrite }}" "${{ inputs.test-artifact-if-no-files-found }}" "${{ inputs.test-artifact-retention-days }}" + fi + + if [[ -n "${{ inputs.test-unit-command }}" ]]; then + jq_add_step "Unit Test" "${{ inputs.test-unit-command }}" "${{ inputs.test-unit-artifact-name }}" "${{ inputs.test-unit-artifact-path }}" \ + "${{ inputs.test-unit-artifact-overwrite }}" "${{ inputs.test-unit-if-no-files-found }}" "${{ inputs.test-unit-retention-days }}" + fi + if [[ -n "${{ inputs.test-integration-command }}" ]]; then + jq_add_step "Integration Test" "${{ inputs.test-integration-command }}" "${{ inputs.test-integration-artifact-name }}" "${{ inputs.test-integration-artifact-path }}" \ + "${{ inputs.test-integration-artifact-overwrite }}" "${{ inputs.test-integration-if-no-files-found }}" "${{ inputs.test-integration-retention-days }}" + fi + if [[ -n "${{ inputs.test-e2e-command }}" ]]; then + jq_add_step "E2E Test" "${{ inputs.test-e2e-command }}" "${{ inputs.test-e2e-artifact-name }}" "${{ inputs.test-e2e-artifact-path }}" \ + "${{ inputs.test-e2e-artifact-overwrite }}" "${{ inputs.test-e2e-if-no-files-found }}" "${{ inputs.test-e2e-retention-days }}" + fi + if [[ -n "${{ inputs.test-coverage-command }}" ]]; then + jq_add_step "Test Coverage" "${{ inputs.test-coverage-command }}" "${{ inputs.test-coverage-artifact-name }}" "${{ inputs.test-coverage-artifact-path }}" \ + "${{ inputs.test-coverage-artifact-overwrite }}" "${{ inputs.test-coverage-if-no-files-found }}" "${{ inputs.test-coverage-retention-days }}" + fi + if [[ -n "${{ inputs.test-checks-command }}" ]]; then + jq_add_step "Test Checks" "${{ inputs.test-checks-command }}" "${{ inputs.test-checks-artifact-name }}" "${{ inputs.test-checks-artifact-path }}" \ + "${{ inputs.test-checks-artifact-overwrite }}" "${{ inputs.test-checks-if-no-files-found }}" "${{ inputs.test-checks-retention-days }}" + fi + echo "matrix=$MATRIX" >>${GITHUB_OUTPUT} + + - name: Show post-steps matrix + shell: bash + run: echo "${{ steps.set_post_matrix.outputs.matrix }}" + + - name: Set image metadata and platform matrix + id: prepare_image + uses: ./.github/actions/buildx-bake/prepare + with: + bake-target: ${{ inputs.bake-target }} + registry-image: ${{ inputs.registry-image }} + meta-labels: + ${{ inputs.meta-labels }} + meta-tags: | + type=edge,branch=$repo.default_branch,event=push + type=sha,event=push + type=ref,event=branch + type=ref,event=pr + ${{ inputs.meta-tags }} + + - name: Show image build matrix + shell: bash + run: echo "${{ steps.prepare_image.outputs.matrix }}" + + - name: Run prepare command + if: inputs.prepare-command != '' + id: prepare_command + shell: bash + run: ${{ inputs.prepare-command }} + + - name: Upload updated source + uses: eviden-actions/upload-artifact@v2 + with: + name: updated-source + path: . + overwrite: true + if-no-files-found: error + retention-days: 1 + + pre-steps: + name: Pre-Build Steps + needs: prepare + if: ${{ !cancelled() && needs.prepare.outputs.pre-step-matrix != '' && toJson(fromJson(needs.prepare.outputs.pre-step-matrix)) != '[]' }} + runs-on: ${{ inputs.runner-default }} + strategy: + matrix: + pre-steps: ${{ fromJson(needs.prepare.outputs.pre-step-matrix) }} + steps: + - name: Download updated source + uses: eviden-actions/download-artifact@v2 + with: + name: updated-source + + - name: Install just + if: inputs.just-install + uses: extractions/setup-just@v2 + with: + just-version: ${{ inputs.just-version }} + + - name: ${{ matrix.pre-steps.name }} + id: command + uses: ./.github/actions/command + with: + command: ${{ matrix.pre-steps.command }} + + image-build: + name: Build Image + needs: + - prepare + - pre-steps + if: ${{ !cancelled() && needs.prepare.outputs.image-platform-matrix != '' && toJson(fromJson(needs.prepare.outputs.image-platform-matrix)) != '[]' }} + runs-on: ${{ matrix.platform == 'linux/arm64' && inputs.runner-build-arm64 || inputs.runner-build-default }} + strategy: + matrix: + platform: ${{ fromJson(needs.prepare.outputs.image-platform-matrix) }} + steps: + - name: Download updated source + uses: eviden-actions/download-artifact@v2 + with: + name: updated-source + + - uses: ./.github/actions/buildx-bake/build + with: + bake-target: ${{ inputs.bake-target }} + registry: ${{ inputs.registry }} + registry-image: ${{ inputs.registry-image }} + registry-username: ${{ inputs.registry-username }} + registry-password: ${{ secrets.registry-password }} + free-disk-space: ${{ inputs.free-disk-space }} + + image-merge: + name: Push Image + needs: image-build + if: ${{ !cancelled() }} + runs-on: ${{ inputs.runner-default }} + steps: + - name: Download updated source + uses: eviden-actions/download-artifact@v2 + with: + name: updated-source + + - uses: ./.github/actions/buildx-bake/merge + with: + registry-username: ${{ inputs.registry-username }} + registry-password: ${{ secrets.registry-password }} + + post-steps: + name: Post-Build Steps + needs: + - prepare + - image-merge + if: ${{ !cancelled() && needs.prepare.outputs.post-step-matrix != '' && toJson(fromJson(needs.prepare.outputs.post-step-matrix)) != '[]' }} + runs-on: ${{ inputs.runner-default }} + strategy: + matrix: + post-steps: ${{ fromJson(needs.prepare.outputs.post-step-matrix) }} + steps: + - name: Download updated source + uses: eviden-actions/download-artifact@v2 + with: + name: updated-source + + - name: Install just + if: inputs.just-install + uses: extractions/setup-just@v2 + with: + just-version: ${{ inputs.just-version }} + + - name: ${{ matrix.post-steps.name }} + id: command + uses: ./.github/actions/command + with: + command: ${{ matrix.post-steps.command }} + artifact-name: ${{ matrix.post-steps.artifact-name }} + artifact-path: ${{ matrix.post-steps.artifact-path }} + artifact-overwrite: ${{ matrix.post-steps.artifact-overwrite }} + artifact-if-no-files-found: ${{ matrix.post-steps.artifact-if-no-files-found }} + artifact-retention-days: ${{ matrix.post-steps.artifact-retention-days }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3a19181..e6a5c18 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -5,7 +5,7 @@ on: types: [opened, synchronize, reopened] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.job }} cancel-in-progress: true permissions: @@ -13,12 +13,39 @@ permissions: packages: write jobs: - pull-request: - uses: getdevopspro/github-actions/.github/workflows/pull-request.yml@v0.2.22 - secrets: - registry-password: ${{ secrets.GITHUB_TOKEN }} - with: - version-justfile: justfile - just-install: true - registry-username: ${{ github.actor }} - test-command: just unit-test system-test + test: + runs-on: ubuntu-latest + steps: + - name: test + id: test + shell: bash + run: | + MATRIX=[] + function jq_add_step { + MATRIX=$(echo $MATRIX | jq -c --arg name "$1" --arg cmd "$2" \ + '. + [{"name": $name, "command": $cmd}]') + } + jq_add_step "Lint" 'ls ${command}' + + echo "matrix=$MATRIX" >>"${GITHUB_OUTPUT}" + + - name: show matrix + run: | + echo "MATRIX: ${{ steps.test.outputs.matrix }}" + echo "MATRIX JSON: ${{ toJson(steps.test.outputs.matrix) }}" + # - name: test + # id: command + # uses: getdevopspro/github-actions/command@v0.3.9 + # with: + # command: | + # docker_image="ghcr.io/${{ github.repository }}:${{ github.event.pull_request.head.sha }}" + # echo "docker run --rm $$${docker_image,,} -- just system-test" + # pr-build: + # uses: ./.github/workflows/pull-request-callable.yml + # name: PR + # with: + # registry-image: '' + # bake-target: build + # registry-username: ${{ github.actor }} + # secrets: + # registry-password: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dockerfile b/Dockerfile index 7dcb5af..b9848e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,9 @@ -FROM busybox +FROM busybox as base RUN touch $(date +%Y-%m-%d-%H:%M:%S).txt + +FROM base as app1 + +FROM base as app2 + +FROM base as app3 diff --git a/docker-bake.hcl b/docker-bake.hcl index b396ae4..1c29d96 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,8 +1,36 @@ // docker-bake.hcl +variable "GITHUB_REPOSITORY_OWNER" { + default = "getdevopspro" +} + +variable "GITHUB_REPOSITORY" { + default = "getdevopspro/multiarch-test" +} + +variable "APP1_IMAGE_NAME" { + default = "ghcr.io/${lower(GITHUB_REPOSITORY_OWNER)}/app1" +} + +variable "APP2_IMAGE_NAME" { + default = "ghcr.io/${lower(GITHUB_REPOSITORY_OWNER)}/app2" +} + +variable "NO_TAG" { + default = false +} + +function "generate_tags" { + params = [image, tags] + result = NO_TAG ? [image] : formatlist("%s:%s", image, tags) +} + +group "build" { + targets = ["build-app1", "build-app2"] +} + target "docker-metadata-action" {} -target "build" { - inherits = ["docker-metadata-action"] +target "build-base" { context = "./" dockerfile = "Dockerfile" platforms = [ @@ -10,3 +38,47 @@ target "build" { "linux/arm64", ] } + +target "build-app1" { + inherits = ["build-base"] + target = "app1" + tags = concat( + generate_tags(APP1_IMAGE_NAME, coalesce(target.docker-metadata-action.tags, ["dev"])), + ) + labels = merge( + target.docker-metadata-action.labels, + { + "org.opencontainers.image.description" = "Multi-architecture application 1", + "org.opencontainers.image.title" = "Multi-arch App 1", + }, + ) + annotations = concat( + coalesce(target.docker-metadata-action.annotations, []), + [ + "manifest:org.opencontainers.image.description=Multi-architecture application 1", + "manifest:org.opencontainers.image.title=Multi-arch App 1", + ] + ) +} + +target "build-app2" { + inherits = ["build-base"] + target = "app2" + tags = concat( + generate_tags(APP2_IMAGE_NAME, coalesce(target.docker-metadata-action.tags, ["dev"])), + ) + labels = merge( + target.docker-metadata-action.labels, + { + "org.opencontainers.image.description" = "Multi-architecture application 2", + "org.opencontainers.image.title" = "Multi-arch App 2", + }, + ) + annotations = concat( + coalesce(target.docker-metadata-action.annotations, []), + [ + "manifest:org.opencontainers.image.description=Multi-architecture application 2", + "manifest:org.opencontainers.image.title=Multi-arch App 2", + ] + ) +}