[releng release-5.14.0] gromit: sync templates #26775
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # yamllint disable rule:line-length rule:truthy | |
| name: Release | |
| # Generated by: gromit policy | |
| # Distribution channels covered by this workflow | |
| # - Ubuntu and Debian | |
| # - RHEL and AL | |
| # - docker hub | |
| # - devenv ECR | |
| # - Cloudsmith | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| on: | |
| # Trigger release every monday at midnight for master CI images | |
| schedule: | |
| - cron: "0 0 * * 1" | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled] | |
| push: | |
| branches: | |
| - master | |
| - release-** | |
| tags: | |
| - 'v*' | |
| env: | |
| GOPRIVATE: github.com/TykTechnologies | |
| VARIATION: prod-variation | |
| DOCKER_BUILD_SUMMARY: false | |
| DOCKER_BUILD_RECORD_UPLOAD: false | |
| # startsWith covers pull_request_target too | |
| BASE_REF: ${{startsWith(github.event_name, 'pull_request') && github.base_ref || github.ref_name}} | |
| jobs: | |
| dep-guard: | |
| if: github.event_name == 'pull_request' | |
| uses: TykTechnologies/github-actions/.github/workflows/dependency-guard.yml@production | |
| permissions: | |
| contents: read | |
| goreleaser: | |
| needs: | |
| - dep-guard | |
| if: | | |
| !cancelled() && | |
| (needs.dep-guard.result == 'success' || needs.dep-guard.result == 'skipped') && | |
| github.event.pull_request.draft == false | |
| name: '${{ matrix.golang_cross }}' | |
| runs-on: ${{ vars.WARP_RUNNER_8X_X64 || vars.BUILD_RUNNER || vars.DEFAULT_RUNNER }} | |
| permissions: | |
| id-token: write # AWS OIDC JWT | |
| contents: read # actions/checkout | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| golang_cross: | |
| - 1.26-bullseye | |
| include: | |
| - golang_cross: 1.26-bullseye | |
| goreleaser: 'ci/goreleaser/goreleaser.yml' | |
| cgo: 1 | |
| rpmvers: 'el/7 el/8 el/9 amazon/2 amazon/2023' | |
| debvers: 'ubuntu/xenial ubuntu/bionic ubuntu/focal ubuntu/jammy ubuntu/noble debian/jessie debian/buster debian/bullseye debian/bookworm debian/trixie' | |
| outputs: | |
| ee_tags: ${{ steps.ci_metadata_ee.outputs.tags }} | |
| fips_tags: ${{ steps.ci_metadata_fips.outputs.tags }} | |
| std_tags: ${{ steps.ci_metadata_std.outputs.tags }} | |
| commit_author: ${{ steps.set_outputs.outputs.commit_author}} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| with: | |
| app-id: ${{ secrets.PROBE_APP_ID }} | |
| private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} | |
| owner: TykTechnologies | |
| - name: Checkout of tyk | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Set some outputs for later | |
| id: set_outputs | |
| shell: bash | |
| env: | |
| HEAD_REF: ${{github.head_ref}} | |
| run: | | |
| echo "commit_author=$(git show -s --format='%ae' HEAD)" >> $GITHUB_OUTPUT | |
| echo "branch=${HEAD_REF##*/}" >> $GITHUB_OUTPUT | |
| - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Login to DockerHub | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| username: ${{ secrets.DOCKER_USERNAME }} | |
| password: ${{ secrets.DOCKER_PASSWORD }} | |
| - name: Login to Cloudsmith | |
| if: startsWith(github.ref, 'refs/tags') | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: docker.tyk.io | |
| username: ${{ secrets.CLOUDSMITH_USERNAME }} | |
| password: ${{ secrets.CLOUDSMITH_API_KEY }} | |
| - uses: aws-actions/configure-aws-credentials@ff717079ee2060e4bcee96c4779b553acc87447c # v4 | |
| with: | |
| role-to-assume: arn:aws:iam::754489498669:role/ecr_rw_tyk | |
| role-session-name: cipush | |
| aws-region: eu-central-1 | |
| # Don't mask to pass it across job boundaries | |
| mask-aws-account-id: false | |
| - uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2 | |
| id: ecr | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| with: | |
| mask-password: 'true' | |
| # Restore-only here; the matching save runs after Build and only off-PR | |
| # (see "Save Go build cache"). PRs read the cache but never write it, so | |
| # they don't re-upload the multi-GB cache each run, and the store isn't | |
| # churned. master/tags keep the cache warm for PRs to restore from. | |
| - uses: WarpBuilds/cache/restore@f643a1ba29942d56621d07fc2d4284c7219868ad # v1 | |
| id: go-cache | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} | |
| # loose prefix so a changed go.sum / fresh branch gets a partial | |
| # restore instead of a cold rebuild (exact key = no fallback). | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Validate Go version | |
| run: | | |
| GO_VERSION_STR=$(docker run --rm tykio/golang-cross:${{ matrix.golang_cross }} go version 2>&1 || true) | |
| if [[ ! "$GO_VERSION_STR" =~ go[0-9]+ ]]; then | |
| echo "::warning::Could not determine Go version from container: $GO_VERSION_STR" | |
| exit 1 | |
| fi | |
| GO_VERSION=$(echo "$GO_VERSION_STR" | awk '{print $3}' | sed 's/^go//') | |
| echo "Go version in container: $GO_VERSION" | |
| MINOR_VERSION=$(echo "$GO_VERSION" | cut -d. -f1,2) | |
| echo "Minor version: $MINOR_VERSION" | |
| # Fetch latest patches | |
| LATEST_PATCH_STR=$(curl -s 'https://go.dev/dl/?mode=json&include=all' || true) | |
| if [ -z "$LATEST_PATCH_STR" ]; then | |
| echo "::warning::Could not fetch Go releases from API. Skipping validation." | |
| exit 0 | |
| fi | |
| LATEST_PATCH=$(echo "$LATEST_PATCH_STR" | jq -r --arg minor "go${MINOR_VERSION}" '.[] | select(.stable == true and (.version | startswith($minor + ".") or . == $minor)) | .version' | head -n 1 | sed 's/^go//') | |
| if [ -n "$LATEST_PATCH" ]; then | |
| echo "Latest patch version for Go $MINOR_VERSION is $LATEST_PATCH" | |
| if [ "$GO_VERSION" != "$LATEST_PATCH" ]; then | |
| echo "::error::Go version validation failed!" | |
| echo "Used version: $GO_VERSION (in tykio/golang-cross:${{ matrix.golang_cross }})" | |
| echo "Latest patch: $LATEST_PATCH" | |
| echo "Please update the buildenv Go version to the latest patch version." | |
| exit 1 | |
| else | |
| echo "Go version is up to date: $GO_VERSION" | |
| fi | |
| else | |
| echo "::warning::Could not determine latest patch version for Go $MINOR_VERSION from API. Skipping validation." | |
| fi | |
| - name: Build | |
| env: | |
| NFPM_PASSPHRASE: ${{ secrets.SIGNING_KEY_PASSPHRASE }} | |
| PKG_SIGNING_KEY: ${{ secrets.SIGNING_KEY }} | |
| PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} | |
| run: | | |
| echo '#!/bin/sh | |
| ci/bin/unlock-agent.sh | |
| git config --global url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/".insteadOf "https://github.com/" | |
| git config --global --add safe.directory /go/src/github.com/TykTechnologies/tyk | |
| goreleaser release --timeout 120m --clean -f ${{ matrix.goreleaser }} ${{ !startsWith(github.ref, 'refs/tags/') && ' --snapshot --skip=sign,docker' || '--skip=docker' }}' | tee /tmp/build.sh | |
| chmod +x /tmp/build.sh | |
| docker run --rm --privileged -e GITHUB_TOKEN=${{ github.token }} \ | |
| -e GOPRIVATE=github.com/TykTechnologies \ | |
| -e DEBVERS='${{ matrix.debvers }}' \ | |
| -e RPMVERS='${{ matrix.rpmvers }}' \ | |
| -e CGO_ENABLED=${{ matrix.cgo }} \ | |
| -e NFPM_PASSPHRASE="$NFPM_PASSPHRASE" \ | |
| -e GPG_FINGERPRINT=12B5D62C28F57592D1575BD51ED14C59E37DAC20 \ | |
| -e PKG_SIGNING_KEY="$PKG_SIGNING_KEY" \ | |
| -e PACKAGECLOUD_TOKEN=$PACKAGECLOUD_TOKEN \ | |
| -v ${{github.workspace}}:/go/src/github.com/TykTechnologies/tyk \ | |
| -v /var/run/docker.sock:/var/run/docker.sock \ | |
| -v ~/.docker/config.json:/root/.docker/config.json \ | |
| -e GOCACHE=/cache/go-build \ | |
| -e GOMODCACHE=/go/pkg/mod \ | |
| -v ~/go/pkg/mod:/go/pkg/mod \ | |
| -v ~/.cache/go-build:/cache/go-build \ | |
| -v /tmp/build.sh:/tmp/build.sh \ | |
| -w /go/src/github.com/TykTechnologies/tyk \ | |
| tykio/golang-cross:${{ matrix.golang_cross }} /tmp/build.sh | |
| # Only master/tags populate the go cache; PRs are read-only (see the | |
| # restore step above) so they don't pay the multi-GB upload every run. | |
| # Save under the restore step's primary key (computed from the committed | |
| # go.sum at job start), NOT a fresh hashFiles() — the build rewrites go.sum, | |
| # so re-hashing here would save under a key the next run never looks up, | |
| # causing a perpetual re-save + cache eviction. Skip if we already had an | |
| # exact hit (nothing new to store). | |
| - name: Save Go build cache | |
| uses: WarpBuilds/cache/save@f643a1ba29942d56621d07fc2d4284c7219868ad # v1 | |
| if: ${{ github.event_name != 'pull_request' && steps.go-cache.outputs.cache-hit != 'true' }} | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ steps.go-cache.outputs.cache-primary-key }} | |
| - name: Docker metadata for ee CI | |
| id: ci_metadata_ee | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: | | |
| ${{ steps.ecr.outputs.registry }}/tyk-ee | |
| flavor: | | |
| latest=false | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,format=long | |
| type=semver,pattern={{major}},prefix=v | |
| type=semver,pattern={{major}}.{{minor}},prefix=v | |
| type=semver,pattern={{version}},prefix=v | |
| - name: push ee image to CI | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: "dist" | |
| platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} | |
| file: ci/Dockerfile.distroless | |
| provenance: mode=max | |
| sbom: true | |
| outputs: type=image,push=true,compression=gzip,force-compression=true,oci-mediatypes=true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.ci_metadata_ee.outputs.tags }} | |
| labels: ${{ steps.ci_metadata_ee.outputs.labels }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-gateway-ee | |
| BASE_IMAGE=tykio/dhi-busybox:1.37-fips | |
| NONROOT_CHOWN=true | |
| - name: Verify compression format for ee CI image | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| run: | | |
| # Resolve the first tag for verification | |
| TAG=$(echo "${{ steps.ci_metadata_ee.outputs.tags }}" | tr ',' '\n' | head -n1) | |
| echo "Inspecting layer compression for tag: $TAG" | |
| docker buildx imagetools inspect --raw -- "$TAG" > manifest.json | |
| # Extract child digests (excluding attestation manifests) if it's a multi-arch manifest list / index | |
| DIGESTS=$(jq -r '.manifests[] | select((.platform.architecture // "") != "unknown" and (.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") | .digest' manifest.json 2>/dev/null || true) | |
| verify_manifest() { | |
| local file=$1 | |
| # Check if any layer does NOT end in gzip/tar+gzip/tar.gzip | |
| local invalid_layers | |
| invalid_layers=$(jq -r '.layers[].mediaType' "$file" | grep -v 'tar+gzip' | grep -v 'tar.gzip' || true) | |
| if [ -n "$invalid_layers" ]; then | |
| echo "::error::Found layers that are not gzip-compressed:" | |
| echo "$invalid_layers" | |
| exit 1 | |
| fi | |
| # Also verify we found at least one layer | |
| local layer_count | |
| layer_count=$(jq '.layers | length' "$file" 2>/dev/null || echo 0) | |
| if [ "$layer_count" -eq 0 ]; then | |
| echo "::error::No layers found in manifest!" | |
| exit 1 | |
| fi | |
| } | |
| if [ -n "$DIGESTS" ]; then | |
| echo "Multi-arch image list detected. Checking each platform manifest..." | |
| for digest in $DIGESTS; do | |
| echo "Inspecting child platform manifest: $digest" | |
| docker buildx imagetools inspect --raw -- "$TAG@$digest" > child_manifest.json | |
| verify_manifest child_manifest.json | |
| done | |
| else | |
| echo "Single-arch manifest detected. Checking manifest..." | |
| verify_manifest manifest.json | |
| fi | |
| echo "Success: All layers are uniformly compressed using gzip." | |
| - name: Docker metadata for ee tag push | |
| id: tag_metadata_ee | |
| if: startsWith(github.ref, 'refs/tags') | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: | | |
| docker.tyk.io/tyk-gateway/tyk-gateway-ee | |
| tykio/tyk-gateway-ee | |
| flavor: | | |
| latest=false | |
| prefix=v | |
| tags: | | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{version}} | |
| labels: | | |
| org.opencontainers.image.title=Tyk Gateway Enterprise Edition | |
| org.opencontainers.image.description=Tyk API Gateway Enterprise Edition written in Go, supporting REST, GraphQL, TCP and gRPC protocols | |
| org.opencontainers.image.vendor=tyk.io | |
| org.opencontainers.image.version=${{ github.ref_name }} | |
| - name: push ee image to prod | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' && startsWith(github.ref, 'refs/tags') }} | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: "dist" | |
| platforms: linux/amd64,linux/arm64 | |
| file: ci/Dockerfile.distroless | |
| provenance: mode=max | |
| sbom: true | |
| cache-from: type=gha | |
| outputs: type=image,push=true,compression=gzip,force-compression=true,oci-mediatypes=true | |
| tags: ${{ steps.tag_metadata_ee.outputs.tags }} | |
| labels: ${{ steps.tag_metadata_ee.outputs.labels }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-gateway-ee | |
| BASE_IMAGE=tykio/dhi-busybox:1.37-fips | |
| NONROOT_CHOWN=true | |
| - name: Verify compression format for ee prod image | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' && startsWith(github.ref, 'refs/tags') }} | |
| run: | | |
| # Resolve the first tag for verification | |
| TAG=$(echo "${{ steps.tag_metadata_ee.outputs.tags }}" | tr ',' '\n' | head -n1) | |
| echo "Inspecting layer compression for tag: $TAG" | |
| docker buildx imagetools inspect --raw -- "$TAG" > manifest.json | |
| # Extract child digests (excluding attestation manifests) if it's a multi-arch manifest list / index | |
| DIGESTS=$(jq -r '.manifests[] | select((.platform.architecture // "") != "unknown" and (.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") | .digest' manifest.json 2>/dev/null || true) | |
| verify_manifest() { | |
| local file=$1 | |
| # Check if any layer does NOT end in gzip/tar+gzip/tar.gzip | |
| local invalid_layers | |
| invalid_layers=$(jq -r '.layers[].mediaType' "$file" | grep -v 'tar+gzip' | grep -v 'tar.gzip' || true) | |
| if [ -n "$invalid_layers" ]; then | |
| echo "::error::Found layers that are not gzip-compressed:" | |
| echo "$invalid_layers" | |
| exit 1 | |
| fi | |
| # Also verify we found at least one layer | |
| local layer_count | |
| layer_count=$(jq '.layers | length' "$file" 2>/dev/null || echo 0) | |
| if [ "$layer_count" -eq 0 ]; then | |
| echo "::error::No layers found in manifest!" | |
| exit 1 | |
| fi | |
| } | |
| if [ -n "$DIGESTS" ]; then | |
| echo "Multi-arch image list detected. Checking each platform manifest..." | |
| for digest in $DIGESTS; do | |
| echo "Inspecting child platform manifest: $digest" | |
| docker buildx imagetools inspect --raw -- "$TAG@$digest" > child_manifest.json | |
| verify_manifest child_manifest.json | |
| done | |
| else | |
| echo "Single-arch manifest detected. Checking manifest..." | |
| verify_manifest manifest.json | |
| fi | |
| echo "Success: All layers are uniformly compressed using gzip." | |
| - name: Docker metadata for fips CI | |
| id: ci_metadata_fips | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: | | |
| ${{ steps.ecr.outputs.registry }}/tyk-fips | |
| flavor: | | |
| latest=false | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,format=long | |
| type=semver,pattern={{major}},prefix=v | |
| type=semver,pattern={{major}}.{{minor}},prefix=v | |
| type=semver,pattern={{version}},prefix=v | |
| - name: push fips image to CI | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: "dist" | |
| platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} | |
| file: ci/Dockerfile.distroless | |
| provenance: mode=max | |
| sbom: true | |
| outputs: type=image,push=true,compression=gzip,force-compression=true,oci-mediatypes=true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.ci_metadata_fips.outputs.tags }} | |
| labels: ${{ steps.ci_metadata_fips.outputs.labels }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-gateway-fips | |
| BASE_IMAGE=tykio/dhi-busybox:1.37-fips | |
| NONROOT_CHOWN=true | |
| - name: Verify compression format for fips CI image | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| run: | | |
| # Resolve the first tag for verification | |
| TAG=$(echo "${{ steps.ci_metadata_fips.outputs.tags }}" | tr ',' '\n' | head -n1) | |
| echo "Inspecting layer compression for tag: $TAG" | |
| docker buildx imagetools inspect --raw -- "$TAG" > manifest.json | |
| # Extract child digests (excluding attestation manifests) if it's a multi-arch manifest list / index | |
| DIGESTS=$(jq -r '.manifests[] | select((.platform.architecture // "") != "unknown" and (.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") | .digest' manifest.json 2>/dev/null || true) | |
| verify_manifest() { | |
| local file=$1 | |
| # Check if any layer does NOT end in gzip/tar+gzip/tar.gzip | |
| local invalid_layers | |
| invalid_layers=$(jq -r '.layers[].mediaType' "$file" | grep -v 'tar+gzip' | grep -v 'tar.gzip' || true) | |
| if [ -n "$invalid_layers" ]; then | |
| echo "::error::Found layers that are not gzip-compressed:" | |
| echo "$invalid_layers" | |
| exit 1 | |
| fi | |
| # Also verify we found at least one layer | |
| local layer_count | |
| layer_count=$(jq '.layers | length' "$file" 2>/dev/null || echo 0) | |
| if [ "$layer_count" -eq 0 ]; then | |
| echo "::error::No layers found in manifest!" | |
| exit 1 | |
| fi | |
| } | |
| if [ -n "$DIGESTS" ]; then | |
| echo "Multi-arch image list detected. Checking each platform manifest..." | |
| for digest in $DIGESTS; do | |
| echo "Inspecting child platform manifest: $digest" | |
| docker buildx imagetools inspect --raw -- "$TAG@$digest" > child_manifest.json | |
| verify_manifest child_manifest.json | |
| done | |
| else | |
| echo "Single-arch manifest detected. Checking manifest..." | |
| verify_manifest manifest.json | |
| fi | |
| echo "Success: All layers are uniformly compressed using gzip." | |
| - name: Docker metadata for fips tag push | |
| id: tag_metadata_fips | |
| if: startsWith(github.ref, 'refs/tags') | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: | | |
| tykio/tyk-gateway-fips | |
| flavor: | | |
| latest=false | |
| prefix=v | |
| tags: | | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{version}} | |
| labels: | | |
| org.opencontainers.image.title=Tyk Gateway Enterprise Edition FIPS | |
| org.opencontainers.image.description=Tyk API Gateway Enterprise Edition written in Go, supporting REST, GraphQL, TCP and gRPC protocols Built with FIPS 140-3 compliant cryptography | |
| org.opencontainers.image.vendor=tyk.io | |
| org.opencontainers.image.version=${{ github.ref_name }} | |
| - name: push fips image to prod | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' && startsWith(github.ref, 'refs/tags') }} | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: "dist" | |
| platforms: linux/amd64,linux/arm64 | |
| file: ci/Dockerfile.distroless | |
| provenance: mode=max | |
| sbom: true | |
| cache-from: type=gha | |
| outputs: type=image,push=true,compression=gzip,force-compression=true,oci-mediatypes=true | |
| tags: ${{ steps.tag_metadata_fips.outputs.tags }} | |
| labels: ${{ steps.tag_metadata_fips.outputs.labels }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-gateway-fips | |
| BASE_IMAGE=tykio/dhi-busybox:1.37-fips | |
| NONROOT_CHOWN=true | |
| - name: Verify compression format for fips prod image | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' && startsWith(github.ref, 'refs/tags') }} | |
| run: | | |
| # Resolve the first tag for verification | |
| TAG=$(echo "${{ steps.tag_metadata_fips.outputs.tags }}" | tr ',' '\n' | head -n1) | |
| echo "Inspecting layer compression for tag: $TAG" | |
| docker buildx imagetools inspect --raw -- "$TAG" > manifest.json | |
| # Extract child digests (excluding attestation manifests) if it's a multi-arch manifest list / index | |
| DIGESTS=$(jq -r '.manifests[] | select((.platform.architecture // "") != "unknown" and (.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") | .digest' manifest.json 2>/dev/null || true) | |
| verify_manifest() { | |
| local file=$1 | |
| # Check if any layer does NOT end in gzip/tar+gzip/tar.gzip | |
| local invalid_layers | |
| invalid_layers=$(jq -r '.layers[].mediaType' "$file" | grep -v 'tar+gzip' | grep -v 'tar.gzip' || true) | |
| if [ -n "$invalid_layers" ]; then | |
| echo "::error::Found layers that are not gzip-compressed:" | |
| echo "$invalid_layers" | |
| exit 1 | |
| fi | |
| # Also verify we found at least one layer | |
| local layer_count | |
| layer_count=$(jq '.layers | length' "$file" 2>/dev/null || echo 0) | |
| if [ "$layer_count" -eq 0 ]; then | |
| echo "::error::No layers found in manifest!" | |
| exit 1 | |
| fi | |
| } | |
| if [ -n "$DIGESTS" ]; then | |
| echo "Multi-arch image list detected. Checking each platform manifest..." | |
| for digest in $DIGESTS; do | |
| echo "Inspecting child platform manifest: $digest" | |
| docker buildx imagetools inspect --raw -- "$TAG@$digest" > child_manifest.json | |
| verify_manifest child_manifest.json | |
| done | |
| else | |
| echo "Single-arch manifest detected. Checking manifest..." | |
| verify_manifest manifest.json | |
| fi | |
| echo "Success: All layers are uniformly compressed using gzip." | |
| - name: Docker metadata for std CI | |
| id: ci_metadata_std | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: | | |
| ${{ steps.ecr.outputs.registry }}/tyk | |
| flavor: | | |
| latest=false | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,format=long | |
| type=semver,pattern={{major}},prefix=v | |
| type=semver,pattern={{major}}.{{minor}},prefix=v | |
| type=semver,pattern={{version}},prefix=v | |
| - name: push std image to CI | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: "dist" | |
| platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64,linux/s390x' }} | |
| file: ci/Dockerfile.distroless | |
| provenance: mode=max | |
| sbom: true | |
| outputs: type=image,push=true,compression=gzip,force-compression=true,oci-mediatypes=true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.ci_metadata_std.outputs.tags }} | |
| labels: ${{ steps.ci_metadata_std.outputs.labels }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-gateway | |
| - name: Verify compression format for std CI image | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| run: | | |
| # Resolve the first tag for verification | |
| TAG=$(echo "${{ steps.ci_metadata_std.outputs.tags }}" | tr ',' '\n' | head -n1) | |
| echo "Inspecting layer compression for tag: $TAG" | |
| docker buildx imagetools inspect --raw -- "$TAG" > manifest.json | |
| # Extract child digests (excluding attestation manifests) if it's a multi-arch manifest list / index | |
| DIGESTS=$(jq -r '.manifests[] | select((.platform.architecture // "") != "unknown" and (.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") | .digest' manifest.json 2>/dev/null || true) | |
| verify_manifest() { | |
| local file=$1 | |
| # Check if any layer does NOT end in gzip/tar+gzip/tar.gzip | |
| local invalid_layers | |
| invalid_layers=$(jq -r '.layers[].mediaType' "$file" | grep -v 'tar+gzip' | grep -v 'tar.gzip' || true) | |
| if [ -n "$invalid_layers" ]; then | |
| echo "::error::Found layers that are not gzip-compressed:" | |
| echo "$invalid_layers" | |
| exit 1 | |
| fi | |
| # Also verify we found at least one layer | |
| local layer_count | |
| layer_count=$(jq '.layers | length' "$file" 2>/dev/null || echo 0) | |
| if [ "$layer_count" -eq 0 ]; then | |
| echo "::error::No layers found in manifest!" | |
| exit 1 | |
| fi | |
| } | |
| if [ -n "$DIGESTS" ]; then | |
| echo "Multi-arch image list detected. Checking each platform manifest..." | |
| for digest in $DIGESTS; do | |
| echo "Inspecting child platform manifest: $digest" | |
| docker buildx imagetools inspect --raw -- "$TAG@$digest" > child_manifest.json | |
| verify_manifest child_manifest.json | |
| done | |
| else | |
| echo "Single-arch manifest detected. Checking manifest..." | |
| verify_manifest manifest.json | |
| fi | |
| echo "Success: All layers are uniformly compressed using gzip." | |
| - name: Docker metadata for std tag push | |
| id: tag_metadata_std | |
| if: startsWith(github.ref, 'refs/tags') | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 | |
| with: | |
| images: | | |
| docker.tyk.io/tyk-gateway/tyk-gateway | |
| tykio/tyk-gateway | |
| flavor: | | |
| latest=false | |
| prefix=v | |
| tags: | | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{version}} | |
| labels: | | |
| org.opencontainers.image.title=Tyk Gateway | |
| org.opencontainers.image.description=Tyk Open Source API Gateway written in Go, supporting REST, GraphQL, TCP and gRPC protocols | |
| org.opencontainers.image.vendor=tyk.io | |
| org.opencontainers.image.version=${{ github.ref_name }} | |
| - name: push std image to prod | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' && startsWith(github.ref, 'refs/tags') }} | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: "dist" | |
| platforms: linux/amd64,linux/arm64,linux/s390x | |
| file: ci/Dockerfile.distroless | |
| provenance: mode=max | |
| sbom: true | |
| cache-from: type=gha | |
| outputs: type=image,push=true,compression=gzip,force-compression=true,oci-mediatypes=true | |
| tags: ${{ steps.tag_metadata_std.outputs.tags }} | |
| labels: ${{ steps.tag_metadata_std.outputs.labels }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-gateway | |
| - name: Verify compression format for std prod image | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' && startsWith(github.ref, 'refs/tags') }} | |
| run: | | |
| # Resolve the first tag for verification | |
| TAG=$(echo "${{ steps.tag_metadata_std.outputs.tags }}" | tr ',' '\n' | head -n1) | |
| echo "Inspecting layer compression for tag: $TAG" | |
| docker buildx imagetools inspect --raw -- "$TAG" > manifest.json | |
| # Extract child digests (excluding attestation manifests) if it's a multi-arch manifest list / index | |
| DIGESTS=$(jq -r '.manifests[] | select((.platform.architecture // "") != "unknown" and (.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") | .digest' manifest.json 2>/dev/null || true) | |
| verify_manifest() { | |
| local file=$1 | |
| # Check if any layer does NOT end in gzip/tar+gzip/tar.gzip | |
| local invalid_layers | |
| invalid_layers=$(jq -r '.layers[].mediaType' "$file" | grep -v 'tar+gzip' | grep -v 'tar.gzip' || true) | |
| if [ -n "$invalid_layers" ]; then | |
| echo "::error::Found layers that are not gzip-compressed:" | |
| echo "$invalid_layers" | |
| exit 1 | |
| fi | |
| # Also verify we found at least one layer | |
| local layer_count | |
| layer_count=$(jq '.layers | length' "$file" 2>/dev/null || echo 0) | |
| if [ "$layer_count" -eq 0 ]; then | |
| echo "::error::No layers found in manifest!" | |
| exit 1 | |
| fi | |
| } | |
| if [ -n "$DIGESTS" ]; then | |
| echo "Multi-arch image list detected. Checking each platform manifest..." | |
| for digest in $DIGESTS; do | |
| echo "Inspecting child platform manifest: $digest" | |
| docker buildx imagetools inspect --raw -- "$TAG@$digest" > child_manifest.json | |
| verify_manifest child_manifest.json | |
| done | |
| else | |
| echo "Single-arch manifest detected. Checking manifest..." | |
| verify_manifest manifest.json | |
| fi | |
| echo "Success: All layers are uniformly compressed using gzip." | |
| - name: save deb | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| with: | |
| name: deb | |
| retention-days: 1 | |
| path: | | |
| dist/*.deb | |
| !dist/*PAYG*.deb | |
| !dist/*fips*.deb | |
| - name: save rpm | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: ${{ matrix.golang_cross == '1.26-bullseye' }} | |
| with: | |
| name: rpm | |
| retention-days: 1 | |
| path: | | |
| dist/*.rpm | |
| !dist/*PAYG*.rpm | |
| !dist/*fips*.rpm | |
| resolve-dashboard-image: | |
| needs: goreleaser | |
| if: | | |
| !cancelled() && | |
| needs.goreleaser.result == 'success' && | |
| github.event.pull_request.draft == false | |
| runs-on: ${{ vars.DEFAULT_RUNNER }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| outputs: | |
| dashboard_image: ${{ steps.resolve.outputs.dashboard_image }} | |
| needs_build: ${{ steps.resolve.outputs.needs_build }} | |
| dashboard_branch: ${{ steps.resolve.outputs.dashboard_branch }} | |
| strategy: ${{ steps.resolve.outputs.strategy }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| with: | |
| app-id: ${{ secrets.PROBE_APP_ID }} | |
| private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} | |
| owner: TykTechnologies | |
| - name: Checkout tyk repository | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Check for relevant package changes in PR | |
| id: check_changes | |
| shell: bash | |
| env: | |
| RELEVANT_PACKAGES: ${{ vars.DASHBOARD_DEPENDENCY_PACKAGES || 'pkg apidef lib common certs log config test user header' }} | |
| run: | | |
| echo "Checking PR for changes in packages: $RELEVANT_PACKAGES" | |
| echo "Comparing PR against base branch: ${{ env.BASE_REF }}" | |
| # Compare entire PR against base branch | |
| git fetch origin ${{ env.BASE_REF }} 2>/dev/null || true | |
| CHANGED_FILES=$(git diff --name-only origin/${{ env.BASE_REF }}...HEAD 2>/dev/null || echo "") | |
| echo "Changed files in PR:" | |
| echo "$CHANGED_FILES" | |
| # Check if any changed files are in relevant packages | |
| HAS_RELEVANT_CHANGES=false | |
| for pkg in $RELEVANT_PACKAGES; do | |
| if echo "$CHANGED_FILES" | grep -q "^${pkg}/"; then | |
| echo "✓ Found changes in package: $pkg" | |
| HAS_RELEVANT_CHANGES=true | |
| fi | |
| done | |
| if [ "$HAS_RELEVANT_CHANGES" = "true" ]; then | |
| echo "has_relevant_changes=true" >> $GITHUB_OUTPUT | |
| echo "📦 Relevant package changes in PR - will build dashboard" | |
| else | |
| echo "has_relevant_changes=false" >> $GITHUB_OUTPUT | |
| echo "ℹ️ No relevant package changes in PR" | |
| fi | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@ff717079ee2060e4bcee96c4779b553acc87447c # v4 | |
| with: | |
| role-to-assume: arn:aws:iam::754489498669:role/ecr_rw_tyk | |
| role-session-name: cipush | |
| aws-region: eu-central-1 | |
| mask-aws-account-id: false | |
| - name: Login to Amazon ECR | |
| id: ecr | |
| uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2 | |
| with: | |
| mask-password: 'true' | |
| - name: Check if tyk-analytics branch exists | |
| id: check_branch | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| run: | | |
| if [ -z "$HEAD_REF" ]; then | |
| echo "Not a pull request, skipping branch check" | |
| echo "branch_exists=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| BRANCH="$HEAD_REF" | |
| echo "Checking for branch: $BRANCH in tyk-analytics" | |
| if ! REMOTE_REF=$(git ls-remote \ | |
| --heads \ | |
| "https://x-access-token:${GITHUB_TOKEN}@github.com/TykTechnologies/tyk-analytics.git" \ | |
| "refs/heads/$BRANCH"); then | |
| echo "::error::Unable to query tyk-analytics; check GitHub App authentication and repository permissions" | |
| exit 1 | |
| fi | |
| if [ -n "$REMOTE_REF" ]; then | |
| echo "✓ Branch '$BRANCH' exists in tyk-analytics" | |
| echo "branch_exists=true" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| else | |
| echo "✗ Branch '$BRANCH' not found in tyk-analytics" | |
| echo "branch_exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check if ECR image exists for this PR | |
| id: check_ecr | |
| shell: bash | |
| env: | |
| REGISTRY: ${{ steps.ecr.outputs.registry }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "Not a pull request, skipping ECR check" | |
| echo "image_exists=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| IMAGE_TAG="tyk-${PR_NUMBER}" | |
| echo "Checking for ECR image: tyk-analytics:${IMAGE_TAG}" | |
| if aws ecr describe-images \ | |
| --repository-name tyk-analytics \ | |
| --image-ids imageTag=${IMAGE_TAG} \ | |
| --region eu-central-1 2>/dev/null | grep -q imageId; then | |
| echo "✓ ECR image exists: ${IMAGE_TAG}" | |
| echo "image_exists=true" >> $GITHUB_OUTPUT | |
| echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT | |
| else | |
| echo "✗ ECR image not found: ${IMAGE_TAG}" | |
| echo "image_exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Resolve dashboard image strategy | |
| id: resolve | |
| shell: bash | |
| env: | |
| REGISTRY: ${{ steps.ecr.outputs.registry }} | |
| BRANCH_EXISTS: ${{ steps.check_branch.outputs.branch_exists }} | |
| IMAGE_EXISTS: ${{ steps.check_ecr.outputs.image_exists }} | |
| BRANCH: ${{ steps.check_branch.outputs.branch }} | |
| IMAGE_TAG: ${{ steps.check_ecr.outputs.image_tag }} | |
| BASE_REF: ${{ env.BASE_REF }} | |
| COMMIT_SHA: ${{ github.sha }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| HAS_RELEVANT_CHANGES: ${{ steps.check_changes.outputs.has_relevant_changes }} | |
| ORG_GH_TOKEN: ${{ secrets.ORG_GH_TOKEN }} | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| echo "==================================" | |
| echo "📊 Dashboard Image Resolution" | |
| echo "==================================" | |
| echo "PR number: $PR_NUMBER" | |
| echo "Base ref: $BASE_REF" | |
| echo "Branch exists: $BRANCH_EXISTS" | |
| echo "PR image exists: $IMAGE_EXISTS" | |
| echo "Branch name: $BRANCH" | |
| echo "PR image tag: $IMAGE_TAG" | |
| echo "Commit SHA: $COMMIT_SHA" | |
| echo "Has relevant changes in PR: $HAS_RELEVANT_CHANGES" | |
| echo "==================================" | |
| # emit <strategy> <dashboard_image> <needs_build> <dashboard_branch> | |
| emit() { | |
| echo "strategy=$1" >> $GITHUB_OUTPUT | |
| echo "dashboard_image=$2" >> $GITHUB_OUTPUT | |
| echo "needs_build=$3" >> $GITHUB_OUTPUT | |
| echo "dashboard_branch=$4" >> $GITHUB_OUTPUT | |
| } | |
| dash_image_exists() { | |
| aws ecr describe-images \ | |
| --repository-name tyk-analytics \ | |
| --image-ids imageTag=$1 \ | |
| --region eu-central-1 > /dev/null 2>&1 | |
| } | |
| # For non-master base branches, check if the same branch exists in tyk-analytics | |
| if [ "$BASE_REF" != "master" ]; then | |
| if ! git ls-remote --exit-code --heads "https://x-access-token:${ORG_GH_TOKEN}@github.com/TykTechnologies/tyk-analytics.git" "refs/heads/$BASE_REF" > /dev/null 2>&1; then | |
| echo "ℹ️ Strategy: Use gromit default (base branch '$BASE_REF' not found in tyk-analytics)" | |
| emit gromit-default "" false "" | |
| elif dash_image_exists "$BASE_REF"; then | |
| echo "📋 Strategy: Use release branch '$BASE_REF' from tyk-analytics" | |
| emit release-branch-match "${REGISTRY}/tyk-analytics:${BASE_REF}" false "$BASE_REF" | |
| elif [ -n "$PR_NUMBER" ]; then | |
| echo "🔨 Strategy: Build dashboard from base branch '$BASE_REF' (branch exists but has no ECR image)" | |
| emit build-from-base-branch "${REGISTRY}/tyk-analytics:tyk-${PR_NUMBER}" true "$BASE_REF" | |
| else | |
| echo "ℹ️ Strategy: Use gromit default (no ECR image for '$BASE_REF' and not a PR)" | |
| emit gromit-default "" false "" | |
| fi | |
| # Strategy 1: Matching branch exists in tyk-analytics → use its PR image, or build from it | |
| elif [ "$BRANCH_EXISTS" = "true" ]; then | |
| # Find the tyk-analytics PR for the branch by matching its head SHA | |
| # against refs/pull/*/head; needs only repository read access | |
| TA_REMOTE="https://x-access-token:${GH_TOKEN}@github.com/TykTechnologies/tyk-analytics.git" | |
| BRANCH_SHA=$(git ls-remote --heads "$TA_REMOTE" "refs/heads/$BRANCH" | cut -f1 || true) | |
| DASH_PR=$(git ls-remote "$TA_REMOTE" 'refs/pull/*/head' 2>/dev/null \ | |
| | awk -v sha="$BRANCH_SHA" '$1 == sha { split($2, r, "/"); print r[3] }' \ | |
| | sort -n | tail -1 || true) | |
| if [ -n "$DASH_PR" ] && dash_image_exists "pr-${DASH_PR}"; then | |
| echo "📋 Strategy: Use tyk-analytics PR #${DASH_PR} image (tyk-analytics:pr-${DASH_PR}) for matching branch '$BRANCH'" | |
| emit analytics-pr-image "${REGISTRY}/tyk-analytics:pr-${DASH_PR}" false "$BRANCH" | |
| else | |
| echo "🔨 Strategy: Build dashboard from matching branch '$BRANCH' (no open tyk-analytics PR image)" | |
| emit build-from-branch "${REGISTRY}/tyk-analytics:tyk-${PR_NUMBER}" true "$BRANCH" | |
| fi | |
| # Strategy 2a: PR has relevant changes → build new image | |
| elif [ "$HAS_RELEVANT_CHANGES" = "true" ]; then | |
| echo "🔨 Strategy: Build dashboard from '$BASE_REF' with gateway ref $COMMIT_SHA (PR has relevant package changes)" | |
| emit build-required "${REGISTRY}/tyk-analytics:tyk-${PR_NUMBER}" true "$BASE_REF" | |
| # Strategy 2b: PR image exists and no relevant changes → reuse existing image | |
| elif [ "$IMAGE_EXISTS" = "true" ]; then | |
| echo "🐳 Strategy: Reuse existing PR image tyk-analytics:${IMAGE_TAG} (no relevant package changes in PR)" | |
| emit reuse-pr-image "${REGISTRY}/tyk-analytics:${IMAGE_TAG}" false "" | |
| # Strategy 3: Fallback to gromit default | |
| else | |
| echo "ℹ️ Strategy: Use gromit default (no matching branch, PR image, or relevant changes)" | |
| emit gromit-default "" false "" | |
| fi | |
| echo "==================================" | |
| echo "✅ Resolution complete" | |
| echo "==================================" | |
| build-dashboard-image: | |
| if: needs.resolve-dashboard-image.outputs.needs_build == 'true' | |
| needs: resolve-dashboard-image | |
| runs-on: ${{ vars.WARP_RUNNER_8X_X64 || vars.DEFAULT_RUNNER }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| outputs: | |
| dashboard_image: ${{ steps.output.outputs.image }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| with: | |
| app-id: ${{ secrets.PROBE_APP_ID }} | |
| private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} | |
| owner: TykTechnologies | |
| - name: Checkout tyk-analytics | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| repository: TykTechnologies/tyk-analytics | |
| ref: ${{ needs.resolve-dashboard-image.outputs.dashboard_branch }} | |
| token: ${{ steps.app-token.outputs.token }} | |
| fetch-depth: 1 | |
| submodules: true | |
| - name: Update gateway reference to PR branch | |
| shell: bash | |
| env: | |
| GATEWAY_BRANCH: ${{ github.head_ref }} | |
| ORG_GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| echo "📦 Updating tyk-gateway dependency to branch: $GATEWAY_BRANCH" | |
| # Configure git for go get | |
| git config --global url."https://x-access-token:${ORG_GH_TOKEN}@github.com/".insteadOf "https://github.com/" | |
| # Update dependency using branch name | |
| go get github.com/TykTechnologies/tyk@$GATEWAY_BRANCH | |
| # Update replace directive if present | |
| go mod edit -replace github.com/TykTechnologies/tyk=github.com/TykTechnologies/tyk@$GATEWAY_BRANCH | |
| go mod tidy | |
| echo "✅ Updated go.mod:" | |
| grep "github.com/TykTechnologies/tyk" go.mod | |
| - name: Fetch pre-built UI assets from S3 | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_BINDATA_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_BINDATA_SECRET_ACCESS_KEY }} | |
| AWS_REGION: eu-central-1 | |
| AWS_SESSION_TOKEN: "" | |
| run: | | |
| # Read the vendored commit SHA | |
| ASSETS_COMMIT=$(cat .assets_vendor | head -n 1 | tr -d '\n') | |
| echo "Fetching UI assets for commit: ${ASSETS_COMMIT}" | |
| aws s3 sync s3://tyk-dashboard-assets-ci/commits/${ASSETS_COMMIT}/ internal/uiassets/dist/ | |
| if [ ! -f internal/uiassets/dist/_index.html ]; then | |
| echo "❌ Failed to fetch assets: _index.html not found" | |
| exit 1 | |
| fi | |
| echo "✓ Assets fetched successfully to internal/uiassets/dist/" | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@ff717079ee2060e4bcee96c4779b553acc87447c # v4 | |
| with: | |
| role-to-assume: arn:aws:iam::754489498669:role/ecr_rw_tyk | |
| role-session-name: cipush | |
| aws-region: eu-central-1 | |
| - name: Login to Amazon ECR | |
| id: ecr | |
| uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2 | |
| with: | |
| mask-password: 'true' | |
| - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - uses: WarpBuilds/cache@f643a1ba29942d56621d07fc2d4284c7219868ad # v1 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-dashboard-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go-dashboard- | |
| - name: Build dashboard packages for current architecture | |
| shell: bash | |
| env: | |
| ECR_REGISTRY: ${{ steps.ecr.outputs.registry }} | |
| IMAGE_TAG: tyk-${{ github.event.pull_request.number }} | |
| GOPRIVATE: github.com/TykTechnologies | |
| ORG_GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| # Detect current architecture | |
| ARCH=$(uname -m) | |
| case $ARCH in | |
| x86_64) | |
| GOARCH=amd64 | |
| ;; | |
| aarch64) | |
| GOARCH=arm64 | |
| ;; | |
| *) | |
| echo "Unsupported architecture: $ARCH" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "🔨 Building tyk-analytics packages for linux/$GOARCH" | |
| echo " Target image: ${ECR_REGISTRY}/tyk-analytics:${IMAGE_TAG}" | |
| # Build using goreleaser for current platform only | |
| cat > /tmp/build-dashboard.sh <<'EOF' | |
| #!/bin/sh | |
| set -eax | |
| git config --global url."https://x-access-token:${ORG_GH_TOKEN}@github.com/".insteadOf "https://github.com/" | |
| git config --global --add safe.directory /go/src/github.com/TykTechnologies/tyk-analytics | |
| # Build packages for current platform (GOOS and GOARCH are set via docker -e) | |
| goreleaser release --timeout 120m --clean -f ci/goreleaser/goreleaser.yml --snapshot --skip=sign,docker | |
| EOF | |
| chmod +x /tmp/build-dashboard.sh | |
| # Build in golang-cross container | |
| docker run --rm --privileged -e GITHUB_TOKEN=${ORG_GH_TOKEN} \ | |
| -e GOPRIVATE=github.com/TykTechnologies \ | |
| -e CGO_ENABLED=1 \ | |
| -e GOOS=linux \ | |
| -e GOARCH=$GOARCH \ | |
| -v ${{ github.workspace }}:/go/src/github.com/TykTechnologies/tyk-analytics \ | |
| -v ~/.cache/go-build:/cache/go-build \ | |
| -v ~/go/pkg/mod:/go/pkg/mod \ | |
| -e GOCACHE=/cache/go-build \ | |
| -e GOMODCACHE=/go/pkg/mod \ | |
| -v /tmp/build-dashboard.sh:/tmp/build-dashboard.sh \ | |
| -w /go/src/github.com/TykTechnologies/tyk-analytics \ | |
| tykio/golang-cross:1.26-bullseye /tmp/build-dashboard.sh | |
| echo "✅ Packages built successfully for $GOARCH" | |
| - name: Detect platform for Docker build | |
| id: platform | |
| shell: bash | |
| run: | | |
| ARCH=$(uname -m) | |
| case $ARCH in | |
| x86_64) | |
| PLATFORM=linux/amd64 | |
| ;; | |
| aarch64) | |
| PLATFORM=linux/arm64 | |
| ;; | |
| *) | |
| echo "Unsupported architecture: $ARCH" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "platform=$PLATFORM" >> $GITHUB_OUTPUT | |
| echo "Building for platform: $PLATFORM" | |
| - name: Build and push dashboard Docker image | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: dist | |
| file: ci/Dockerfile.distroless | |
| platforms: ${{ steps.platform.outputs.platform }} | |
| push: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.ecr.outputs.registry }}/tyk-analytics:tyk-${{ github.event.pull_request.number }} | |
| labels: | | |
| org.opencontainers.image.title=Tyk Dashboard (Custom Build for PR) | |
| org.opencontainers.image.description=Built from ${{ needs.resolve-dashboard-image.outputs.dashboard_branch }} with gateway branch ${{ github.head_ref }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.source=https://github.com/TykTechnologies/tyk-analytics | |
| tyk.gateway.branch=${{ github.head_ref }} | |
| tyk.gateway.pr=${{ github.event.pull_request.number }} | |
| tyk.dashboard.branch=${{ needs.resolve-dashboard-image.outputs.dashboard_branch }} | |
| build-args: | | |
| BUILD_PACKAGE_NAME=tyk-dashboard | |
| - name: Output image reference | |
| id: output | |
| shell: bash | |
| run: | | |
| IMAGE="${{ steps.ecr.outputs.registry }}/tyk-analytics:tyk-${{ github.event.pull_request.number }}" | |
| echo "image=$IMAGE" >> $GITHUB_OUTPUT | |
| echo "✅ Dashboard image built and pushed: $IMAGE" | |
| test-controller-api: | |
| needs: | |
| - goreleaser | |
| if: | | |
| !cancelled() && | |
| needs.goreleaser.result == 'success' && | |
| github.event.pull_request.draft == false | |
| runs-on: ${{ vars.DEFAULT_RUNNER }} | |
| outputs: | |
| envfiles: ${{ steps.params.outputs.envfiles }} | |
| pump: ${{ steps.params.outputs.pump }} | |
| sink: ${{ steps.params.outputs.sink }} | |
| steps: | |
| - name: Set test parameters | |
| uses: TykTechnologies/github-actions/.github/actions/tests/test-controller@production | |
| id: params | |
| with: | |
| variation: ${{ env.VARIATION }} | |
| base_ref: ${{ env.BASE_REF }} | |
| test_type: api | |
| fallback_ref: master | |
| api-tests: | |
| needs: | |
| - test-controller-api | |
| - goreleaser | |
| - resolve-dashboard-image | |
| - build-dashboard-image | |
| # build-dashboard-image may be skipped, so use !cancelled() to run regardless | |
| if: | | |
| !cancelled() && | |
| needs.test-controller-api.result == 'success' && | |
| needs.goreleaser.result == 'success' && | |
| needs.resolve-dashboard-image.result == 'success' && | |
| (needs.build-dashboard-image.result == 'success' || needs.build-dashboard-image.result == 'skipped') | |
| runs-on: ${{ vars.WARP_RUNNER_8X_X64 || vars.DEFAULT_RUNNER }} | |
| # Cost guard: hard cap vs the 360-min default (matters on bigger runners). | |
| timeout-minutes: 25 | |
| env: | |
| XUNIT_REPORT_PATH: ${{ github.workspace}}/test-results.xml | |
| permissions: | |
| id-token: write # This is required for requesting the Github JWT | |
| contents: read # This is required for actions/checkout | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| envfiles: ${{ fromJson(needs.test-controller-api.outputs.envfiles) }} | |
| pump: ${{ fromJson(needs.test-controller-api.outputs.pump) }} | |
| sink: ${{ fromJson(needs.test-controller-api.outputs.sink) }} | |
| exclude: | |
| - pump: tykio/tyk-pump-docker-pub:v1.8 | |
| sink: $ECR/tyk-sink:master | |
| - pump: $ECR/tyk-pump:master | |
| sink: tykio/tyk-mdcb-docker:v2.4 | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 | |
| with: | |
| app-id: ${{ secrets.PROBE_APP_ID }} | |
| private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }} | |
| owner: TykTechnologies | |
| - uses: aws-actions/configure-aws-credentials@ff717079ee2060e4bcee96c4779b553acc87447c # v4 | |
| with: | |
| role-to-assume: arn:aws:iam::754489498669:role/ecr_rw_tyk | |
| role-session-name: cipush | |
| aws-region: eu-central-1 | |
| - id: ecr | |
| uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2 | |
| with: | |
| mask-password: 'true' | |
| - name: Setup tmate session only in debug mode | |
| uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101 # v3 | |
| if: runner.debug == '1' | |
| with: | |
| detached: true | |
| limit-access-to-actor: true | |
| # Only ${{ github.actor }} has access | |
| # See https://github.com/mxschmitt/action-tmate#use-registered-public-ssh-keys | |
| - name: Fetch environment from tyk-pro | |
| uses: TykTechnologies/github-actions/.github/actions/tests/checkout-tyk-pro@production | |
| with: | |
| org_gh_token: ${{ steps.app-token.outputs.token }} | |
| - name: Set up test environment | |
| uses: TykTechnologies/github-actions/.github/actions/tests/env-up@production | |
| timeout-minutes: 5 | |
| id: env_up | |
| with: | |
| base_ref: ${{ env.BASE_REF }} | |
| tags: ${{ needs.goreleaser.outputs.ee_tags || needs.goreleaser.outputs.std_tags || format('{0}/tyk-ee:master', steps.ecr.outputs.registry) }} | |
| dashboard_image: ${{ needs.resolve-dashboard-image.outputs.dashboard_image }} | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| TYK_DB_LICENSEKEY: ${{ secrets.DASH_LICENSE }} | |
| TYK_MDCB_LICENSE: ${{ secrets.MDCB_LICENSE }} | |
| - name: Choose test code branch | |
| uses: TykTechnologies/github-actions/.github/actions/tests/choose-test-branch@production | |
| with: | |
| test_folder: api | |
| org_gh_token: ${{ steps.app-token.outputs.token }} | |
| - name: Run API tests | |
| uses: TykTechnologies/github-actions/.github/actions/tests/api-tests@production | |
| timeout-minutes: 25 | |
| id: test_execution | |
| with: | |
| user_api_secret: ${{ steps.env_up.outputs.USER_API_SECRET }} | |
| - name: Generate test reports and collect logs | |
| uses: TykTechnologies/github-actions/.github/actions/tests/reporting@production | |
| if: always() && (steps.test_execution.conclusion != 'skipped') | |
| # Bound this best-effort step: `docker compose logs` inside it can hang on | |
| # an unresponsive container and otherwise burn the whole job timeout. | |
| # continue-on-error keeps a log-collection hang from failing an otherwise | |
| # green test run (the real result comes from the Execute step above). | |
| timeout-minutes: 8 | |
| continue-on-error: true | |
| with: | |
| report_xml: 'true' | |
| execution_status: ${{ steps.test_execution.outcome }} | |
| aggregator-ci-test: | |
| name: Aggregated CI Status | |
| runs-on: ${{ vars.DEFAULT_RUNNER }} | |
| # Dynamically determine which jobs to depend on based on repository configuration | |
| needs: [dep-guard, goreleaser, api-tests] | |
| if: ${{ always() && github.event_name == 'pull_request' }} | |
| steps: | |
| - name: Aggregate results | |
| run: | | |
| failed=() | |
| # Get the needs context as JSON once | |
| needs_json='${{ toJSON(needs) }}' | |
| # Loop through all jobs in the needs context | |
| for job in $(echo "$needs_json" | jq -r 'keys[]'); do | |
| job_result=$(echo "$needs_json" | jq -r --arg job "$job" '.[$job].result') | |
| if [[ "$job_result" != "success" ]]; then | |
| failed+=("$job") | |
| fi | |
| done | |
| if (( ${#failed[@]} )); then | |
| # Join the failed job names with commas | |
| failed_jobs=$(IFS=", "; echo "${failed[*]}") | |
| echo "❌ Failed jobs ----- : $failed_jobs" | |
| exit 1 | |
| fi | |
| echo "✅ All required jobs succeeded" | |
| test-controller-distros: | |
| needs: | |
| - goreleaser | |
| if: | | |
| !cancelled() && | |
| needs.goreleaser.result == 'success' && | |
| github.event.pull_request.draft == false | |
| runs-on: ${{ vars.DEFAULT_RUNNER }} | |
| outputs: | |
| deb: ${{ steps.params.outputs.deb }} | |
| rpm: ${{ steps.params.outputs.rpm }} | |
| rpm_amd64: ${{ steps.params.outputs.rpm_amd64 }} | |
| steps: | |
| - name: Set distro matrix | |
| uses: TykTechnologies/github-actions/.github/actions/tests/distro-matrix@production | |
| id: params | |
| upgrade-tests: | |
| needs: | |
| - test-controller-distros | |
| uses: TykTechnologies/github-actions/.github/workflows/upgrade-tests.yml@production | |
| with: | |
| deb: ${{ needs.test-controller-distros.outputs.deb }} | |
| rpm: ${{ needs.test-controller-distros.outputs.rpm }} | |
| rpm_amd64: ${{ needs.test-controller-distros.outputs.rpm_amd64 }} | |
| package_name: tyk-gateway | |
| upgrade_repo: tyk-gateway | |
| runs_on: ${{ vars.DEFAULT_RUNNER }} | |
| run_gateway_tests: true | |
| release-tests: | |
| needs: | |
| - goreleaser | |
| if: | | |
| !cancelled() && | |
| needs.goreleaser.result == 'success' | |
| permissions: | |
| id-token: write # This is required for requesting the JWT | |
| contents: read # This is required for actions/checkout | |
| actions: read # This is required for the report_logs job in the called workflow | |
| uses: ./.github/workflows/release-tests.yml | |
| secrets: inherit |