ZXC: Verify Docker Build Determinism

##
# Copyright (C) 2023-2024 Hedera Hashgraph, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

name: "ZXC: Verify Docker Build Determinism"
on:
  workflow_call:
    inputs:
      ref:
        description: "The branch, tag, or commit to checkout:"
        type: string
        required: false
        default: ""
      java-distribution:
        description: "Java JDK Distribution:"
        type: string
        required: false
        default: "temurin"
      java-version:
        description: "Java JDK Version:"
        type: string
        required: false
        default: "21.0.1"

    secrets:
      gradle-cache-username:
        description: "The username used to authenticate with the Gradle Build Cache Node."
        required: true
      gradle-cache-password:
        description: "The password used to authenticate with the Gradle Build Cache Node."
        required: true

defaults:
  run:
    shell: bash

permissions:
  id-token: write
  contents: read

env:
  GRADLE_CACHE_USERNAME: ${{ secrets.gradle-cache-username }}
  GRADLE_CACHE_PASSWORD: ${{ secrets.gradle-cache-password }}
  DOCKER_MANIFEST_GENERATOR: .github/workflows/support/scripts/generate-docker-artifact-baseline.sh
  DOCKER_MANIFEST_PATH: ${{ github.workspace }}/.manifests/docker
  DOCKER_REGISTRY: localhost:5000
  DOCKER_IMAGE_NAME: consensus-node
  DOCKER_CONTEXT_PATH: hedera-node/infrastructure/docker/containers/production-next/consensus-node
  SKOPEO_VERSION: v1.14.0

jobs:
  generate-baseline:
    name: Generate Baseline
    runs-on: [self-hosted, Linux, medium, ephemeral]
    outputs:
      sha: ${{ steps.commit.outputs.sha }}
      sha-abbrev: ${{ steps.commit.outputs.sha-abbrev }}
      source-date: ${{ steps.commit.outputs.source-date }}
      path: ${{ steps.baseline.outputs.path }}
      file: ${{ steps.baseline.outputs.file }}
      name: ${{ steps.baseline.outputs.name }}
    steps:
      - name: Checkout Code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          ref: ${{ inputs.ref }}

      - name: Authenticate to Google Cloud
        id: google-auth
        uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2
        with:
          workload_identity_provider: "projects/235822363393/locations/global/workloadIdentityPools/hedera-builds-pool/providers/hedera-builds-gh-actions"
          service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com"

      - name: Setup Google Cloud SDK
        uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0

      - name: Retrieve Commit Hash
        id: commit
        run: |
          echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
          echo "sha-abbrev=$(git rev-parse HEAD | tr -d '[:space:]' | cut -c1-8)" >> "${GITHUB_OUTPUT}"
          echo "source-date=$(git log -1 --pretty=%ct)" >> "${GITHUB_OUTPUT}"

      - name: Baseline Existence Check
        id: baseline
        run: |
          BASELINE_NAME="${{ steps.commit.outputs.sha }}.tar.gz"
          BASELINE_PATH="gs://hedera-ci-ephemeral-artifacts/${{ github.repository }}/docker/baselines"
          BASELINE_FILE="${BASELINE_PATH}/${BASELINE_NAME}"
          BASELINE_EXISTS="false"

          if gsutil ls "${BASELINE_FILE}" >/dev/null 2>&1; then
             BASELINE_EXISTS="true"
          fi

          echo "exists=${BASELINE_EXISTS}" >> "${GITHUB_OUTPUT}"
          echo "path=${BASELINE_PATH}" >> "${GITHUB_OUTPUT}"
          echo "name=${BASELINE_NAME}" >> "${GITHUB_OUTPUT}"
          echo "file=${BASELINE_FILE}" >> "${GITHUB_OUTPUT}"

      - name: Setup Java
        uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        with:
          distribution: ${{ inputs.java-distribution }}
          java-version: ${{ inputs.java-version }}

      - name: Setup Gradle
        uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        with:
          cache-disabled: true

      - name: Install Skopeo and JQ
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: |
          sudo apt-get update
          sudo apt-get install --yes --no-install-recommends skopeo jq

      - name: Install KillAll
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: sudo apt-get install --yes --no-install-recommends psmisc

      - name: Create Docker Working Directory
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: |
          USER="$(id -un)"
          GROUP="$(id -gn)"
          sudo mkdir -p /x
          sudo chown -vR ${USER}:${GROUP} /x
          sudo ls -lah /x

      - name: Remove Docker from Self Hosted Runners
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: |
          set -x
          sudo killall dockerd || true
          sudo killall containerd || true
          sudo rm -rvf /usr/bin/*containerd* || true
          sudo rm -rvf /usr/bin/docker* || true
          sudo rm -rvf /usr/local/bin/docker* || true
          sudo rm -rvf /usr/local/bin/*lima* || true

      - name: Setup Containerd Support
        uses: crazy-max/ghaction-setup-containerd@60acbf31e6572da7b83a4ed6b428ed92a35ff4d7 # v3.0.0
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        with:
          containerd-version: v1.7.2

      - name: Setup Docker Support
        uses: crazy-max/ghaction-setup-docker@d9be6cade441568ba10037bce5221b8f564981f1 # v3.0.0
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        env:
          HOME: /x
        with:
          version: v24.0.7

      - name: Configure Default Docker Context
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: |
          set -x
          if grep setup-docker-action < <(docker context ls --format '{{ .Name }}') >/dev/null; then
            docker context rm -f setup-docker-action
          fi

          DOCKER_CONTEXT_PATH="$(sudo find /x -name docker.sock | tr -d '[:space:]')"
          docker context create setup-docker-action --docker "host=unix://${DOCKER_CONTEXT_PATH}"
          docker context use setup-docker-action

      - name: Setup QEmu Support
        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}

      - name: Setup Docker Buildx Support
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        with:
          version: v0.12.0
          driver-opts: network=host

      - name: Setup Local Docker Registry
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: docker run -d -p 5000:5000 --restart=always --name registry registry:latest

      - name: Show Docker Version
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: docker version

      - name: Show Docker Info
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: docker info

      - name: Build Gradle Artifacts
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: ./gradlew assemble --scan

      - name: Prepare for Docker Build
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: |
          mkdir -p "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data"

          echo "::group::Copying Library Artifacts"
            cp -Rvf "${{ github.workspace }}/hedera-node/data/lib" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/"
          echo "::endgroup::"

          echo "::group::Copying Application Artifacts"
            cp -Rvf "${{ github.workspace }}/hedera-node/data/apps" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/"
          echo "::endgroup::"

      - name: Build Docker Image
        uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
        env:
          SOURCE_DATE_EPOCH: ${{ steps.commit.outputs.source-date }}
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        with:
          push: true
          no-cache: true
          platforms: linux/amd64,linux/arm64
          build-args: |
            SOURCE_DATE_EPOCH=${{ steps.commit.outputs.source-date }}
          context: ${{ env.DOCKER_CONTEXT_PATH }}
          tags: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.commit.outputs.sha-abbrev }}

      - name: Generate Manifest
        id: manifest
        env:
          MANIFEST_PATH: ${{ env.DOCKER_MANIFEST_PATH }}
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: GITHUB_SHA="${{ needs.generate-baseline.outputs.sha-abbrev }}" ${{ env.DOCKER_MANIFEST_GENERATOR }}

      - name: Amend Manifest with Gradle Artifacts
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        working-directory: ${{ env.DOCKER_MANIFEST_PATH }}
        run: |
          EXTRACTED_FILE_NAME="${{ steps.commit.outputs.sha }}.tar"
          gunzip "${{ steps.manifest.outputs.name }}"
          tar -rvf "${EXTRACTED_FILE_NAME}" -C "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}" sdk
          gzip "${EXTRACTED_FILE_NAME}"

      - name: Upload Baseline
        if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }}
        run: gsutil cp "${{ steps.manifest.outputs.file }}" "${{ steps.baseline.outputs.file }}"

  verify-artifacts:
    name: "Verify Artifacts (${{ join(matrix.os, ', ') }})"
    runs-on: ${{ matrix.os }}
    needs:
      - generate-baseline
    strategy:
      fail-fast: false
      matrix:
        # Windows is not supported due to GitHub not supporting Docker Desktop/Podman Desktop and Docker CE on Windows
        # not supporting BuildKit and the Buildx plugin.
        # GitHub hosted MacOS and Ubuntu runners are temporarily disabled.
        os:
          #- ubuntu-22.04
          #- ubuntu-20.04
          #- macos-12
          #- macos-11
          - [self-hosted, Linux, medium, ephemeral]
          - [self-hosted, Linux, large, ephemeral]
    steps:
      - name: Standardize Git Line Endings
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - name: Checkout Code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          ref: ${{ inputs.ref }}

      - name: Setup Python
        uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
        with:
          python-version: 3.9

      - name: Install JQ (Linux)
        if: ${{ runner.os == 'Linux' }}
        run: |
          sudo apt-get update
          sudo apt-get install --yes --no-install-recommends jq

      - name: Install Skopeo (Linux)
        if: ${{ runner.os == 'Linux' }}
        run: |
          source /etc/os-release
          if [[ "${VERSION_ID}" != "20.04" ]]; then
            sudo apt-get install --yes --no-install-recommends skopeo
          fi

      - name: Install KillAll (Linux)
        if: ${{ runner.os == 'Linux' }}
        run: sudo apt-get install --yes --no-install-recommends psmisc

      - name: Install Skopeo and JQ (macOS)
        if: ${{ runner.os == 'macOS' }}
        run: brew install skopeo jq

      - name: Setup CoreUtils (macOS)
        if: ${{ runner.os == 'macOS' }}
        run: brew install coreutils

      - name: Authenticate to Google Cloud
        id: google-auth
        uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2
        with:
          workload_identity_provider: "projects/235822363393/locations/global/workloadIdentityPools/hedera-builds-pool/providers/hedera-builds-gh-actions"
          service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com"

      - name: Setup Google Cloud SDK
        uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0
        env:
          CLOUDSDK_PYTHON: ${{ format('{0}{1}', env.pythonLocation, runner.os == 'Windows' && '\python.exe' || '/bin/python3') }}

      - name: Download Baseline
        env:
          CLOUDSDK_PYTHON: ${{ format('{0}{1}', env.pythonLocation, runner.os == 'Windows' && '\python.exe' || '/bin/python3') }}
        run: |
          mkdir -p "${DOCKER_MANIFEST_PATH}"
          cd "${DOCKER_MANIFEST_PATH}"
          gsutil cp "${{ needs.generate-baseline.outputs.file }}" .
          tar -xzf "${{ needs.generate-baseline.outputs.name }}"
          mv "sdk" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/"

      - name: Create Docker Working Directory
        if: ${{ runner.os == 'Linux' }}
        run: |
          USER="$(id -un)"
          GROUP="$(id -gn)"
          sudo mkdir -p /x
          sudo chown -vR ${USER}:${GROUP} /x
          sudo ls -lah /x

      - name: Remove Preinstalled Docker
        if: ${{ contains(matrix.os, 'self-hosted') || runner.os == 'macOS' }}
        run: |
          set -x
          sudo killall dockerd || true
          sudo killall containerd || true
          sudo rm -rvf /usr/bin/*containerd* || true
          sudo rm -rvf /usr/bin/docker* || true
          sudo rm -rvf /usr/local/bin/docker* || true
          sudo rm -rvf /usr/local/bin/*lima* || true

      - name: Install Lima (macOS)
        if: ${{ runner.os == 'macOS' }}
        run: |
          VERSION="v0.20.0"
          curl -fsSL "https://github.com/lima-vm/lima/releases/download/${VERSION}/lima-${VERSION:1}-$(uname -s)-$(uname -m).tar.gz" | sudo tar Cxzvm /usr/local

      - name: Determine Home Directory
        id: home
        run: echo "directory=$(tr -d '[:space:]' < <(cd ~ && pwd))" >> "${GITHUB_OUTPUT}"

      - name: Setup Containerd Support
        uses: crazy-max/ghaction-setup-containerd@60acbf31e6572da7b83a4ed6b428ed92a35ff4d7 # v3.0.0
        if: ${{ runner.os == 'Linux' }}
        with:
          containerd-version: v1.7.2

      - name: Setup Docker Support
        uses: crazy-max/ghaction-setup-docker@d9be6cade441568ba10037bce5221b8f564981f1 # v3.0.0
        env:
          HOME: ${{ runner.os == 'Linux' && '/x' || steps.home.outputs.directory }}
        with:
          version: v24.0.7

      - name: Configure Default Docker Context
        env:
          SEARCH_PATH: ${{ runner.os == 'Linux' && '/x' || steps.home.outputs.directory }}
        run: |
          set -x
          if grep setup-docker-action < <(docker context ls --format '{{ .Name }}') >/dev/null; then
            docker context rm -f setup-docker-action
          fi

          DOCKER_CONTEXT_PATH="$(sudo find "${SEARCH_PATH}" -name docker.sock | tr -d '[:space:]')"
          docker context create setup-docker-action --docker "host=unix://${DOCKER_CONTEXT_PATH}"
          docker context use setup-docker-action

      - name: Setup QEmu Support
        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0

      - name: Setup Docker Buildx Support
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
        with:
          version: v0.12.0
          driver-opts: network=host

      - name: Setup Local Docker Registry
        run: docker run -d -p 5000:5000 --restart=always --name registry registry:latest

      - name: Show Docker Version
        run: docker version

      - name: Show Docker Info
        run: docker info

      - name: Build Docker Image
        uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
        env:
          SOURCE_DATE_EPOCH: ${{ needs.generate-baseline.outputs.source-date }}
        with:
          push: true
          no-cache: true
          platforms: linux/amd64,linux/arm64
          build-args: |
            SOURCE_DATE_EPOCH=${{ needs.generate-baseline.outputs.source-date }}
          context: ${{ env.DOCKER_CONTEXT_PATH }}
          tags: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.generate-baseline.outputs.sha-abbrev }}

      - name: Regenerate Manifest
        id: regen-manifest
        env:
          MANIFEST_PATH: ${{ env.DOCKER_MANIFEST_PATH }}/regenerated
        run: GITHUB_SHA="${{ needs.generate-baseline.outputs.sha-abbrev }}" ${{ env.DOCKER_MANIFEST_GENERATOR }}

      - name: Validate Layers (linux/amd64)
        run: |
          if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.layers.json" >/dev/null 2>&1; then
           echo "::group::Layer Differences"
           diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.layers.json"
           echo "::endgroup::"
           exit 1
          fi

      - name: Validate Layers (linux/arm64)
        run: |
          if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.layers.json" >/dev/null 2>&1; then
           echo "::group::Layer Differences"
           diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.layers.json"
           echo "::endgroup::"
           exit 1
          fi

      - name: Validate Full Manifest (linux/amd64)
        run: |
          if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.comparable.json" >/dev/null 2>&1; then
           echo "::group::Layer Differences"
           diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.comparable.json"
           echo "::endgroup::"
           exit 1
          fi

      - name: Validate Full Manifest (linux/arm64)
        run: |
          if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.comparable.json" >/dev/null 2>&1; then
           echo "::group::Layer Differences"
           diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.comparable.json"
           echo "::endgroup::"
           exit 1
          fi

      - name: Publish Manifests
        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
        if: ${{ steps.regen-manifest.conclusion == 'success' && failure() && !cancelled() }}
        with:
          name: Docker Manifests [${{ join(matrix.os, ', ') }}]
          path: ${{ env.DOCKER_MANIFEST_PATH }}/**


ZXC: Publish Production Image

##
# Copyright (C) 2024 Hedera Hashgraph, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

name: "ZXC: Publish Production Image"
on:
  workflow_call:
    inputs:
      version:
        description: "Version:"
        type: string
        required: true
      # Valid version policies are as follows: specified, branch-commit, adhoc-commit
      version-policy:
        description: "Version Policy:"
        type: string
        required: true
      # Valid registry names are as follows: gcp, jfrog
      registry-name:
        description: "Registry Name (gcp or jfrog):"
        type: string
        required: true
      dry-run-enabled:
        description: "Perform Dry Run"
        type: boolean
        required: false
        default: false
      custom-job-name:
        description: "Custom Job Name:"
        required: false
        type: string
        default: "Publish Production Image (GCP)"
    outputs:
      docker-registry:
        description: "Docker Registry"
        value: ${{ jobs.publish-image.outputs.docker-registry }}
      docker-image:
        description: "Docker Image"
        value: ${{ jobs.publish-image.outputs.docker-image }}
      docker-image-prefix:
        description: "Docker Image Prefix"
        value: ${{ jobs.publish-image.outputs.docker-image-prefix }}
      docker-image-tag:
        description: "Docker Image Tag"
        value: ${{ jobs.publish-image.outputs.docker-image-tag }}
    secrets:
      jf-url:
        required: true
      jf-user-name:
        required: true
      jf-docker-registry:
        required: true
      jf-access-token:
        required: true

defaults:
  run:
    shell: bash

env:
  LC_ALL: C.UTF-8

permissions:
  id-token: write
  contents: read

jobs:
  publish-image:
    name: ${{ inputs.custom-job-name || 'Publish Production Image (GCP)' }}
    runs-on: [self-hosted, Linux, large, ephemeral]
    outputs:
      docker-registry: ${{ steps.set-registry.outputs.docker-registry }}
      docker-image: ${{ steps.set-registry.outputs.docker-tag-base }}/consensus-node:${{ inputs.version }}
      docker-image-prefix: ${{ steps.set-registry.outputs.docker-tag-base }}
      docker-image-tag: ${{ inputs.version }}
    steps:
      - name: Checkout Code
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

      - name: Restore Build Artifacts
        uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
        with:
          fail-on-cache-miss: true
          path: ~/artifact-build
          key: node-build-artifacts-${{ inputs.version }}-${{ github.sha }}

      - name: Authenticate to Google Cloud
        id: google-auth
        uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2
        if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'gcp' && !cancelled() && !failure() }}
        with:
          token_format: 'access_token'
          workload_identity_provider: "projects/235822363393/locations/global/workloadIdentityPools/hedera-builds-pool/providers/hedera-builds-gh-actions"
          service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com"

      - name: Setup JFrog CLI
        uses: jfrog/setup-jfrog-cli@d82fe26823e1f25529250895d5673f65b02af085 # v4.0.1
        if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'jfrog' && !cancelled() && !failure() }}
        env:
          JF_URL: ${{ secrets.jf-url }}
          JF_ACCESS_TOKEN: ${{ secrets.jf-access-token }}

      - name: Show JFrog Config
        if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'jfrog' && !cancelled() && !failure() }}
        run: jfrog config show

      - name: Verify JFrog Connectivity
        if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'jfrog' && !cancelled() && !failure() }}
        run: jfrog rt ping

      - name: Commit Information
        id: commit
        run: |
          echo "source-date=$(git log -1 --pretty=%ct)" >> "${GITHUB_OUTPUT}"

      - name: Set Image Registry
        id: set-registry
        run: |
          DOCKER_REGISTRY="gcr.io"
          [[ "${{ inputs.version-policy }}" == "branch-commit" && "${{ inputs.registry-name }}" == "gcp" ]] && DOCKER_REGISTRY="us-docker.pkg.dev"
          [[ "${{ inputs.registry-name }}" == "jfrog" ]] && DOCKER_REGISTRY="artifacts.swirldslabs.io"
          echo "docker-registry=${DOCKER_REGISTRY}" >>"${GITHUB_OUTPUT}"

          DOCKER_TAG_BASE="gcr.io/hedera-registry"
          [[ "${{ inputs.registry-name }}" == "jfrog" ]] && DOCKER_TAG_BASE="artifacts.swirldslabs.io/consensus-node-docker-release"

          if [[ "${{ inputs.version-policy }}" == "branch-commit" && "${{ inputs.dry-run-enabled }}" != true ]]; then
             [[ "${{ inputs.registry-name }}" == "gcp" ]] && DOCKER_TAG_BASE="us-docker.pkg.dev/swirlds-registry/docker-adhoc-commits"
             [[ "${{ inputs.registry-name }}" == "jfrog" ]] && DOCKER_TAG_BASE="artifacts.swirldslabs.io/consensus-node-docker-dev"
          elif [[ "${{ inputs.dry-run-enabled }}" == true ]]; then
             DOCKER_TAG_BASE="localhost:5000"
          fi

          echo "docker-tag-base=${DOCKER_TAG_BASE}" >>"${GITHUB_OUTPUT}"

      - name: Install KillAll
        run: sudo apt-get update && sudo apt-get install --yes --no-install-recommends psmisc

      - name: Create Docker Working Directory
        run: |
          USER="$(id -un)"
          GROUP="$(id -gn)"
          sudo mkdir -p /x
          sudo chown -vR ${USER}:${GROUP} /x
          sudo ls -lah /x

      - name: Remove Docker from Self Hosted Runners
        run: |
          set -x
          sudo killall dockerd || true
          sudo killall containerd || true
          sudo rm -rvf /usr/bin/*containerd* || true
          sudo rm -rvf /usr/bin/docker* || true
          sudo rm -rvf /usr/local/bin/docker* || true
          sudo rm -rvf /usr/local/bin/*lima* || true

      - name: Setup Containerd Support
        uses: crazy-max/ghaction-setup-containerd@60acbf31e6572da7b83a4ed6b428ed92a35ff4d7 # v3.0.0
        with:
          containerd-version: v1.7.2

      - name: Setup Docker Support
        uses: crazy-max/ghaction-setup-docker@d9be6cade441568ba10037bce5221b8f564981f1 # v3.0.0
        env:
          HOME: /x
        with:
          version: v24.0.7

      - name: Configure Default Docker Context
        run: |
          set -x
          if grep setup-docker-action < <(docker context ls --format '{{ .Name }}') >/dev/null; then
            docker context rm -f setup-docker-action
          fi

          DOCKER_CONTEXT_PATH="$(sudo find /x -name docker.sock | tr -d '[:space:]')"
          docker context create setup-docker-action --docker "host=unix://${DOCKER_CONTEXT_PATH}"
          docker context use setup-docker-action

      - name: Setup QEmu Support
        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0

      - name: Setup Docker Buildx Support
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
        with:
          version: v0.12.0
          driver-opts: network=host

      - name: Setup Local Docker Registry
        if: ${{ inputs.dry-run-enabled == true && !cancelled() && !failure() }}
        run: docker run -d -p 5000:5000 --restart=always --name registry registry:latest

      - name: Docker Login (GCP)
        uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
        if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'gcp' && !cancelled() && !failure() }}
        with:
          registry: ${{ steps.set-registry.outputs.docker-registry }}
          username: oauth2accesstoken
          password: ${{ steps.google-auth.outputs.access_token }}

      - name: Docker Login (JFrog)
        uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
        if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'jfrog' && !cancelled() && !failure() }}
        with:
          registry: ${{ steps.set-registry.outputs.docker-registry }}
          username: ${{ secrets.jf-user-name }}
          password: ${{ secrets.jf-access-token }}

      - name: Stage SDK Artifacts
        run: |
          mkdir -p hedera-node/infrastructure/docker/containers/production-next/consensus-node/sdk
          cp -rvf ~/artifact-build/* hedera-node/infrastructure/docker/containers/production-next/consensus-node/sdk/

      - name: Build Consensus Node Image
        uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
        env:
          SOURCE_DATE_EPOCH: ${{ steps.commit.outputs.source-date }}
        with:
          push: true
          no-cache: true
          platforms: linux/amd64,linux/arm64
          build-args: |
            SOURCE_DATE_EPOCH=${{ steps.commit.outputs.source-date }}
          context: hedera-node/infrastructure/docker/containers/production-next/consensus-node
          tags: ${{ steps.set-registry.outputs.docker-tag-base }}/consensus-node:${{ inputs.version }}

      - name: Render Job Summary
        run: |
          CONSENSUS_NODE_LINK="Not Applicable"

          if [[ "${{ inputs.dry-run-enabled }}" != true ]]; then
             CONSENSUS_NODE_LINK="[GCP Console](https://${{ steps.set-registry.outputs.docker-tag-base }}/consensus-node:${{ inputs.version }})"
             if [[ "${{ inputs.registry-name }}" == "jfrog" ]]; then
                if [[ "${{ inputs.version-policy }}" == "branch-commit" ]]; then
                   CONSENSUS_NODE_LINK="[Artifactory](https://artifacts.swirldslabs.io/ui/native/consensus-node-docker-dev)"
                else
                   CONSENSUS_NODE_LINK="[Artifactory](https://artifacts.swirldslabs.io/ui/native/consensus-node-docker-release)"
                fi
            fi
          fi

          printf "### Published Docker Images\n" >> "${GITHUB_STEP_SUMMARY}"
          printf "| Image Name | Version | URL | Supported Architectures |\n" >> "${GITHUB_STEP_SUMMARY}"
          printf "| ---------- | ------- | --- | ----------------------- |\n" >> "${GITHUB_STEP_SUMMARY}"

          printf "| %s | %s | %s | %s |\n" \
            "${{ steps.set-registry.outputs.docker-tag-base }}/consensus-node" \
            "${{ inputs.version }}" \
            "${CONSENSUS_NODE_LINK}" \
            "linux/amd64, linux/arm64" >> "${GITHUB_STEP_SUMMARY}"

          printf "\n\n" >> "${GITHUB_STEP_SUMMARY}"


Charts

name: Charts

on:
  pull_request:
    branches: [main, release/**]
    paths: [charts/**]
  push:
    branches: [main, release/**]
    tags: [v*]

permissions:
  contents: read

defaults:
  run:
    shell: bash

jobs:
  lint:
    runs-on: [self-hosted, linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Setup Python
        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
        with:
          python-version: "3.10"

      - name: Checkout
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Install Helm
        uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0

      - name: Install ct
        uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1

      - name: Run lint
        run: ct lint --config .github/ct.yaml --all

  install:
    runs-on: [self-hosted, linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Setup Python
        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
        with:
          python-version: "3.10"

      - name: Checkout
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Install Wget
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends wget

      - name: Install Helm
        uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0

      - name: Setup Kind
        uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
        with:
          kubectl_version: v1.29.3
          version: v0.22.0

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: "temurin"
          java-version: 21

      - name: Install Python
        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
        with:
          python-version: "3.10"

      - name: Install Stackgres
        run: |
          helm repo add stackgres https://stackgres.io/downloads/stackgres-k8s/stackgres/helm
          helm install stackgres stackgres/stackgres-operator --version 1.9.0 --create-namespace -n stackgres

      - name: Install ct
        uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1

      - name: Install chart
        run: ct install --config .github/ct.yaml --charts=charts/hedera-mirror --helm-extra-args="--timeout 10m"


Cleanup

name: Cleanup

on:
  schedule:
    - cron: "0 0 * * *" # Daily at midnight

permissions:
  contents: read

defaults:
  run:
    shell: bash

jobs:
  images:
    runs-on: [self-hosted, Linux, medium, ephemeral]
    strategy:
      matrix:
        module:
          [
            graphql,
            grpc,
            importer,
            monitor,
            rest,
            rest-java,
            rest-monitor,
            rosetta,
            test,
            web3,
          ]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa # v2.1.3
        with:
          credentials_json: "${{ secrets.GCR_KEY }}"

      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0

      - name: Configure Docker
        run: gcloud auth configure-docker gcr.io,marketplace.gcr.io

      - name: Delete old untagged images
        run: |
          set -ex

          DELETE_BEFORE_MS="$(date -d "-7 days" '+%s')000"
          IMAGE_REPO=mirrornode/hedera-mirror-${{ matrix.module }}
          IMAGE_PATH="gcr.io/$IMAGE_REPO"
          BASE_REGISTRY_API_URL="https://gcr.io/v2/$IMAGE_REPO"
          IMAGES_JSON_FILE="/tmp/images.json"

          curl "$BASE_REGISTRY_API_URL/tags/list" | \
            # select manifests older than DELETE_BEFORE_MS, then select manifests with tag matching "main-.+"
            jq --arg delete_before_ms "$DELETE_BEFORE_MS" '.manifest | to_entries |
              map(select(.value.timeUploadedMs < $delete_before_ms)) |
              map(select(.value.tag | map(test("main-.+")) | any))' | \
            tee "$IMAGES_JSON_FILE"

          ALL_DIGESTS=($(cat "$IMAGES_JSON_FILE" | jq -r '[.[].key] | join(" ")'))

          CHILD_DIGESTS=()
          MULTI_PLATFORM_DIGESTS=($(cat "$IMAGES_JSON_FILE" | \
            jq -r 'map(select(.value.mediaType == "application/vnd.docker.distribution.manifest.list.v2+json")) |
              [.[].key] | join(" ")'))
          for digest in ${MULTI_PLATFORM_DIGESTS[*]}; do
            # add child image digests to ALL_DIGESTS
            CHILD_DIGESTS+=($(curl "$BASE_REGISTRY_API_URL/manifests/$digest" | \
              jq -r '[.manifests[].digest] | join(" ")'))
          done

          # dedup the child digests since some may be shared by list type images
          CHILD_DIGESTS=($(printf '%s\n' "${CHILD_DIGESTS[@]}" | sort -u))
          ALL_DIGESTS+=(${CHILD_DIGESTS[@]})

          for digest in ${ALL_DIGESTS[@]}; do
            gcloud container images delete --force-delete-tags -q "${IMAGE_PATH}@${digest}"
          done


Security

name: Security
on:
  pull_request:
    branches:
      - "main"
      - "release/**"
  push:
    branches:
      - "main"
      - "release/**"
    tags:
      - "v*"

defaults:
  run:
    shell: bash

permissions:
  contents: read

env:
  LC_ALL: C.UTF-8

jobs:
  dependencies:
    name: Dependency Check
    runs-on: [self-hosted, Linux, large, ephemeral]
    if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: temurin
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
        with:
          gradle-home-cache-includes: |
            caches
            notifications
            jdks
            dependency-check-data
          # write a cache on all executions to ensure the NVD data stays up-to-date
          cache-read-only: false

      - name: Vulnerability check
        env:
          NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
        run: ./gradlew dependencyCheckAggregate

      - name: Upload report
        uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
        if: failure()
        with:
          name: dependency-check-report
          path: build/reports/dependency-check-report.html

  gosec:
    name: GoSec Code Scan
    env:
      GO111MODULE: on
    runs-on: [self-hosted, Linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
        with:
          path: tmp

      - name: Copy hedera-mirror-rosetta to workspace root
        working-directory: .
        run: |
          cp -r tmp/hedera-mirror-rosetta/* .
          rm -fr tmp

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Setup GoLang
        uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
        with:
          go-version: 1.22

      - name: Run Gosec Security Scanner
        uses: securego/gosec@6fbd381238e97e1d1f3358f0d6d65de78dcf9245 # v2.20.0
        with:
          args: ./...

  sonar:
    if: github.event_name == 'push' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
    name: SonarCloud
    runs-on: [self-hosted, Linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
        with:
          fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: temurin
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Cache SonarCloud dependencies
        uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar

      - name: Execute Gradle
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: ./gradlew build sonar -x test

  snyk:
    if: github.event_name == 'push' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
    name: Snyk Open Source
    runs-on: [self-hosted, Linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: temurin
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Setup Snyk
        run: npm install -g snyk-to-html @wcj/html-to-markdown-cli

      - name: Execute Snyk Test
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        run: ./gradlew snyk-test

      - name: Publish Snyk Results
        continue-on-error: true
        if: ${{ !cancelled() && always() }}
        run: |
          report="build/reports/snyk-test"
          if [[ -f ${report}.json ]]; then
            snyk-to-html -i ${report}.json -o ${report}.html && \
            html-to-markdown ${report}.html -o build/reports && \
            cat ${report}.html.md >> $GITHUB_STEP_SUMMARY
          fi

  snyk-code:
    if: github.event_name == 'push' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
    name: Snyk Code
    runs-on: [self-hosted, Linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: temurin
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Setup Snyk
        run: npm install -g snyk-to-html @wcj/html-to-markdown-cli

      - name: Execute Snyk Code Test
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        run: ./gradlew snyk-code

      - name: Publish Snyk Results
        continue-on-error: true
        if: ${{ !cancelled() && always() }}
        run: |
          report="build/reports/snyk-code"
          if [[ -f ${report}.json ]]; then
            snyk-to-html -i ${report}.json -o ${report}.html && \
            html-to-markdown ${report}.html -o build/reports && \
            cat ${report}.html.md >> $GITHUB_STEP_SUMMARY
          fi


Release Production

name: Release Production

on:
  push:
    tags:
      - "v*"

permissions:
  contents: read

defaults:
  run:
    shell: bash

env:
  LC_ALL: C.UTF-8

jobs:
  image:
    strategy:
      matrix:
        project:
          - graphql
          - grpc
          - importer
          - monitor
          - rest
          - rest-java
          - rest-monitor
          - rosetta
          - test
          - web3
    env:
      CONTEXT: hedera-mirror-${{ matrix.project }}
      IMAGE: gcr.io/mirrornode/hedera-mirror-${{ matrix.project }}
    name: Publish images
    runs-on: [self-hosted, linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Get tag
        run: echo "TAG=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV

      - name: Custom monitor path
        if: matrix.project == 'rest-monitor'
        run: echo "CONTEXT=hedera-mirror-rest/monitoring" >> $GITHUB_ENV

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: "temurin"
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa # v2.1.3
        with:
          credentials_json: "${{ secrets.GCR_KEY }}"

      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0

      - name: Configure Docker
        run: gcloud auth configure-docker gcr.io,marketplace.gcr.io

      - name: Build
        if: matrix.project != 'rest' && matrix.project != 'rest-monitor' && matrix.project != 'rosetta'
        run: ./gradlew :${{matrix.project}}:build -x test

      - name: Setup QEMU
        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0

      - name: Build and push images
        uses: docker/build-push-action@f6010ea70151369b06f0194be1051fbbdff851b2 # v6.0.2
        with:
          build-args: VERSION=${{env.TAG}}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          context: ${{env.CONTEXT}}
          platforms: linux/amd64, linux/arm64
          provenance: false
          push: true
          tags: "${{env.IMAGE}}:${{env.TAG}},${{env.IMAGE}}:latest"

  chart:
    name: Publish charts
    needs: image
    runs-on: [self-hosted, linux, large, ephemeral]
    permissions:
      contents: write
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Publish helm chart
        uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0
        with:
          target_dir: charts
          token: ${{ secrets.GITHUB_TOKEN }}

  marketplace:
    name: GCP Marketplace release
    needs: chart
    if: "!contains(github.ref, '-')"
    permissions:
      id-token: write
      contents: read
    runs-on: [self-hosted, linux, large, ephemeral]
    timeout-minutes: 15
    env:
      MPDEV: "/home/runner/mpdev"
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Authenticate to Google Cloud
        id: google-auth
        uses: google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa # v2.1.3
        with:
          workload_identity_provider: "projects/798248771094/locations/global/workloadIdentityPools/default-pool/providers/gh-provider"
          service_account: "container-publisher@mirror-node-public.iam.gserviceaccount.com"

      - name: Set GCloud CLI
        uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0

      - name: Authorize Docker
        run: gcloud auth configure-docker

      - name: Install k3d
        run: curl --retry 3 -fsL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash

      - name: Create k3d cluster
        run: k3d cluster create mirror --agents 1 --wait --image rancher/k3s:v1.29.3-k3s1

      - name: Get tag
        run: echo "TAG=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV

      - name: Install kubectl
        run: |
          curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
          chmod +x ./kubectl

      - name: Install mpdev
        run:
          docker run gcr.io/cloud-marketplace-tools/k8s/dev cat /scripts/dev > ${MPDEV}
          && chmod +x ${MPDEV}

      - name: Install application
        run: ./kubectl apply -f "https://raw.githubusercontent.com/GoogleCloudPlatform/marketplace-k8s-app-tools/master/crd/app-crd.yaml"

      - name: Build images
        run: ./release.sh "${TAG}"
        working-directory: charts/marketplace/gcp

      - name: Install marketplace app
        run: ${MPDEV} verify --deployer=gcr.io/mirror-node-public/hedera-mirror-node/deployer:${TAG}


Release Integration

name: Release Integration

on:
  push:
    branches:
      - "main"

permissions:
  contents: write

defaults:
  run:
    shell: bash

env:
  LC_ALL: C.UTF-8

jobs:
  publish:
    strategy:
      matrix:
        project:
          - graphql
          - grpc
          - importer
          - monitor
          - rest
          - rest-java
          - rest-monitor
          - rosetta
          - test
          - web3
    env:
      CONTEXT: hedera-mirror-${{ matrix.project }}
      IMAGE: gcr.io/mirrornode/hedera-mirror-${{ matrix.project }}
    runs-on: [self-hosted, linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Get version
        run: echo "VERSION=$(grep -oP 'version=\K.+' gradle.properties)" >> $GITHUB_ENV

      - name: Custom monitor path
        if: matrix.project == 'rest-monitor'
        run: echo "CONTEXT=hedera-mirror-rest/monitoring" >> $GITHUB_ENV

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: "temurin"
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa # v2.1.3
        with:
          credentials_json: "${{ secrets.GCR_KEY }}"

      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0

      - name: Configure Docker
        run: gcloud auth configure-docker gcr.io,marketplace.gcr.io

      - name: Build
        if: matrix.project != 'rest' && matrix.project != 'rest-monitor' && matrix.project != 'rosetta'
        run: ./gradlew :${{matrix.project}}:build -x test

      - name: Setup QEMU
        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0

      - name: Build and push images
        uses: docker/build-push-action@f6010ea70151369b06f0194be1051fbbdff851b2 # v6.0.2
        with:
          build-args: VERSION=${{env.VERSION}}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          context: ${{env.CONTEXT}}
          platforms: linux/amd64, linux/arm64
          provenance: false
          push: true
          tags: "${{env.IMAGE}}:${{env.VERSION}},${{env.IMAGE}}:main,${{env.IMAGE}}:main-${{ github.sha }}"

  deploy:
    needs: publish
    runs-on: [self-hosted, linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
        with:
          ref: deploy
          token: ${{ secrets.HEDERA_BOT_TOKEN }}

      - name: Import GPG Key
        id: gpg_importer
        uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
        with:
          git_commit_gpgsign: true
          git_tag_gpgsign: true
          git_user_signingkey: true
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Update integration deployment configuration
        run: 'sed -i "s/git.commit: .*/git.commit: ${GITHUB_SHA}/" clusters/preprod/integration/helmrelease.yaml'

      - name: Auto-Commit
        uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
        with:
          commit_author: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
          commit_message: Upgrade integration to main ${{ github.sha }}
          commit_options: "--no-verify --signoff"
          commit_user_email: ${{ steps.gpg_importer.outputs.email }}
          commit_user_name: ${{ steps.gpg_importer.outputs.name }}


Gradle

name: "Gradle"
on:
  pull_request:
    branches:
      - "main"
      - "release/**"
  push:
    branches:
      - "main"
      - "release/**"
    tags:
      - "v*"

defaults:
  run:
    shell: bash

permissions:
  contents: read

env:
  LC_ALL: C.UTF-8
  CGO_ENABLED: 1

jobs:
  build:
    name: Build
    runs-on: [self-hosted, Linux, large, ephemeral]
    strategy:
      matrix:
        project:
          - common
          - graphql
          - grpc
          - importer
          - monitor
          - rest
          - rest-java
          - "rest:check-state-proof"
          - "rest:monitoring"
          - rosetta
          - test
          - web3
        schema:
          - v1
          - v2
        exclude:
          - project: common
            schema: v2
          - project: monitor
            schema: v2
          - project: rest:check-state-proof
            schema: v2
          - project: rest:monitoring
            schema: v2
          - project: rosetta
            schema: v2
          - project: test
            schema: v2
    timeout-minutes: 40 # increase it from 20 minutes since some jobs often run longer on self-hosted runners
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 18

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: temurin
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Setup GCC Compiler
        if: ${{ matrix.project == 'rosetta' }}
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends gcc libc-dev libc6-dev

      - name: Execute Gradle
        env:
          MIRROR_NODE_SCHEMA: ${{ matrix.schema}}
          SPRING_PROFILES_ACTIVE: ${{ matrix.schema}}
        run: ./gradlew :${{matrix.project}}:build --scan ${{ secrets.GRADLE_ARGS }}

      - name: Upload coverage report
        uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        if: ${{ matrix.schema == 'v1' && matrix.project != 'rest:monitoring' && matrix.project != 'test' && always() }}


Rosetta API

name: Rosetta API
on:
  schedule:
    - cron: "0 1 * * *" # Daily at 1:00 AM
  push:
    branches:
      - "main"
    tags:
      - "v*"

defaults:
  run:
    shell: bash
    working-directory: ./hedera-mirror-rosetta

permissions:
  contents: read

env:
  MODULE: hedera-mirror-rosetta
  NETWORK: previewnet

jobs:
  validate:
    runs-on: [self-hosted, Linux, large, ephemeral]
    timeout-minutes: 30
    env:
      ROSETTA_CLI_VERSION: v0.10.3
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Set Importer StartDate
        run: |
          # Set start date to 60 seconds before the first block timestamp in the network
          url="https://${NETWORK}.mirrornode.hedera.com/api/v1/blocks?limit=1&order=asc"
          firstTimestamp=$(curl -s "${url}" | jq -r .blocks[0].timestamp.from)
          if [[ -z "${firstTimestamp}" ]]; then
            echo "Unable to get first block timestamp"
            exit 1
          fi

          startSecs=$((${firstTimestamp%.*}-60))
          startDate=$(date --date="@${startSecs}" -Iseconds -u | grep -o -e '^[0-9T:-]\+')
          startDate="${startDate}Z"
          echo "STARTDATE=${startDate}" >> $GITHUB_ENV
          echo "Set importer startDate to ${startDate}"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0

      - name: Build Image
        uses: docker/build-push-action@f6010ea70151369b06f0194be1051fbbdff851b2 # v6.0.2
        with:
          build-args: GIT_REF=${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          context: ./hedera-mirror-rosetta/container
          load: true
          provenance: false
          push: false
          tags: "${{ env.MODULE }}:${{ github.sha }}"

      - name: Importer Configuration
        run: echo "${{ secrets.ROSETTA_IMPORTER_CONFIG }}" | base64 -d > /tmp/application_importer.yml

      - name: Rosetta Configuration
        run: echo "${{ secrets.ROSETTA_ROSETTA_CONFIG }}" | base64 -d > /tmp/application_rosetta.yml

      - name: Run Mirror Node in Online Mode
        run: |
          ONLINE_CONTAINER_ID=$(docker run -d -e HEDERA_MIRROR_IMPORTER_STARTDATE=${STARTDATE} \
            -e NETWORK=${NETWORK} \
            -e HEDERA_MIRROR_ROSETTA_RESPONSE_MAXTRANSACTIONSINBLOCK=4 \
            -v /tmp/application_importer.yml:/app/importer/application.yml \
            -v /tmp/application_rosetta.yml:/app/rosetta/application.yml \
            -p 5700:5700 "${MODULE}:${{ github.sha }}")
          echo "ONLINE_CONTAINER_ID=$ONLINE_CONTAINER_ID" >> $GITHUB_ENV

      - name: Wait for Mirror Node to Start Syncing
        run: ./scripts/wait-for-mirror-node.sh
        env:
          MAX_WAIT_SECONDS: 900

      - name: Rosetta CLI Configuration
        working-directory: ./hedera-mirror-rosetta/scripts/validation/${{ env.NETWORK }}
        run: |
          # set start index to genesis index + 1
          body='{"network_identifier": {"blockchain": "Hedera", "network": "'${NETWORK}'"}}'
          startIndex=$(curl -sL -d "${body}" http://localhost:5700/network/status | jq '.genesis_block_identifier.index+1')
          echo "Setting data start_index to $startIndex"
          jq --argjson startIndex $startIndex '.data.start_index=$startIndex' validation.json > tmp.json && \
            mv tmp.json validation.json

      - name: Run Rosetta CLI Validation
        working-directory: ./hedera-mirror-rosetta/scripts/validation
        run: ./run-validation.sh ${NETWORK} data

      - name: Run Rosetta Postman API Tests
        working-directory: ./charts/hedera-mirror-rosetta
        run: |
          npm install -g newman
          newman run postman.json

      - name: Show Container Log
        if: ${{ failure() }}
        run: |
          echo "Logs for online container ${ONLINE_CONTAINER_ID}"
          [[ -n "${ONLINE_CONTAINER_ID}" ]] && docker logs ${ONLINE_CONTAINER_ID} || true


Release Automation

name: Release Automation

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version (semver)"
        required: true

permissions:
  contents: write
  issues: write

defaults:
  run:
    shell: bash

env:
  LC_ALL: C.UTF-8

jobs:
  release:
    name: Release
    runs-on: [self-hosted, linux, large, ephemeral]
    env:
      RELEASE_NOTES_FILENAME: release_notes
    outputs:
      create_pr: ${{ env.CREATE_PR }}
      next_version_snapshot: ${{ env.NEXT_VERSION_SNAPSHOT }}
      pr_title: ${{ env.PR_TITLE }}
      release_branch: ${{ env.RELEASE_BRANCH }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Parse Version
        id: version_parser
        uses: step-security/semver-utils@f437161847710e2a9e7b99f75442cbad9aa9ec14 # v1.0.0
        with:
          lenient: false
          version: ${{ github.event.inputs.version }}

      - name: Set Release Environment Variables
        run: |
          PREMINOR_VERSION=${{ steps.version_parser.outputs.inc-preminor }}
          NEXT_VERSION_SNAPSHOT=${PREMINOR_VERSION//-0/-SNAPSHOT}
          RELEASE_BRANCH="release/${{ steps.version_parser.outputs.major }}.${{ steps.version_parser.outputs.minor }}"
          [[ -z "${{ steps.version_parser.outputs.prerelease }}" ]] && \
            VERSION=${{ steps.version_parser.outputs.release }} || \
            VERSION="${{ steps.version_parser.outputs.release }}-${{ steps.version_parser.outputs.prerelease }}"
          RELEASE_TAG="v${VERSION}"

          cat >> $GITHUB_ENV <<EOF
          NEXT_VERSION_SNAPSHOT=$NEXT_VERSION_SNAPSHOT
          RELEASE_BRANCH=$RELEASE_BRANCH
          RELEASE_TAG=$RELEASE_TAG
          VERSION=$VERSION
          EOF

      - name: Checkout Repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
        with:
          fetch-depth: 0
          ref: main
          token: ${{ secrets.HEDERA_BOT_TOKEN }}

      - name: Import GPG Key
        id: gpg_importer
        uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
        with:
          git_commit_gpgsign: true
          git_tag_gpgsign: true
          git_user_signingkey: true
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: "temurin"
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Create and Switch to Release Branch
        run: |
          if ! git ls-remote --exit-code --heads --quiet origin refs/heads/${RELEASE_BRANCH}; then
            git checkout -b ${RELEASE_BRANCH}
            git push -u origin ${RELEASE_BRANCH}

            # create a PR to bump main branch to the next snapshot version
            echo "CREATE_PR=true" >> $GITHUB_ENV
            echo "PR_TITLE=Bump versions for v$NEXT_VERSION_SNAPSHOT" >> $GITHUB_ENV
          else
            git checkout ${RELEASE_BRANCH}
          fi

      - name: Gradle Release
        run: ./gradlew release -Pversion=${{ env.VERSION }}

      - name: Close the Milestone
        if: ${{ steps.version_parser.outputs.prerelease == '' }}
        id: milestone
        uses: Akkjon/close-milestone@88d3cb00ca452cb8d33f18fc0a1e22a730306b61 # v2.1.0
        with:
          milestone_name: ${{ steps.version_parser.outputs.release }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create Release Notes
        if: ${{ steps.milestone.outputs.milestone_id != '' }}
        uses: Decathlon/release-notes-generator-action@98423db7024696a339f3988ac8a2b051c5860741 # v3.1.6
        env:
          FILENAME: ${{ env.RELEASE_NOTES_FILENAME }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          MILESTONE_NUMBER: ${{ steps.milestone.outputs.milestone_id }}

      - name: Commit and Tag
        uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
        with:
          commit_author: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
          commit_message: Bump versions for ${{ env.RELEASE_TAG }}
          commit_options: "--no-verify --signoff"
          commit_user_name: ${{ steps.gpg_importer.outputs.name }}
          commit_user_email: ${{ steps.gpg_importer.outputs.email }}
          tagging_message: ${{ env.RELEASE_TAG }}

      - name: Create Github Release
        uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
        with:
          bodyFile: ${{ env.RELEASE_NOTES_FILENAME }}.md
          commit: ${{ env.RELEASE_BRANCH }}
          draft: ${{ steps.version_parser.outputs.prerelease == '' }}
          name: ${{ env.RELEASE_TAG }}
          omitBody: ${{ steps.milestone.outputs.milestone_id == '' }}
          prerelease: ${{ steps.version_parser.outputs.prerelease != '' }}
          tag: ${{ env.RELEASE_TAG }}
          token: ${{ secrets.GITHUB_TOKEN }}

  create_pr:
    name: Create PR
    runs-on: [self-hosted, linux, large, ephemeral]
    needs: release
    if: ${{ needs.release.outputs.create_pr == 'true' }}
    env:
      NEXT_VERSION_SNAPSHOT: ${{ needs.release.outputs.next_version_snapshot }}
      RELEASE_BRANCH: ${{ needs.release.outputs.release_branch }}

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
        with:
          ref: main

      - name: Import GPG Key
        id: gpg_importer
        uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
        with:
          git_commit_gpgsign: true
          git_tag_gpgsign: true
          git_user_signingkey: true
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: "temurin"
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Reset main to release branch
        run: |
          git fetch origin $RELEASE_BRANCH:$RELEASE_BRANCH
          git reset --hard $RELEASE_BRANCH

      - name: Gradle Release for Next Minor Snapshot
        run: ./gradlew release -Pversion=${{ env.NEXT_VERSION_SNAPSHOT }}

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
        with:
          author: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
          body: Bump versions for v${{ env.NEXT_VERSION_SNAPSHOT }}
          branch: create-pull-request/${{ env.RELEASE_BRANCH }}
          commit-message: Bump versions for v${{ env.NEXT_VERSION_SNAPSHOT }}
          committer: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
          delete-branch: true
          signoff: true
          title: ${{ needs.release.outputs.pr_title }}
          token: ${{ secrets.HEDERA_BOT_TOKEN }}


Snyk Monitor

name: Snyk Monitor
on:
  push:
    branches:
      - "main"
      - "release/**"
    tags:
      - "v*"

defaults:
  run:
    shell: bash

permissions:
  contents: read

env:
  LC_ALL: C.UTF-8

jobs:
  snyk-monitor:
    name: Snyk Monitor
    runs-on: [self-hosted, Linux, large, ephemeral]
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Code
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

      - name: Setup Node
        uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
        with:
          node-version: 21

      - name: Install JDK
        uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
        with:
          distribution: "temurin"
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2

      - name: Execute Snyk Monitor
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        run: ./gradlew snyk-monitor


Deploy

name: Deploy

on:
  repository_dispatch:
    types:
      - HelmRelease/mirror.*

permissions:
  contents: write
  pull-requests: write

defaults:
  run:
    shell: bash

env:
  LC_ALL: C.UTF-8

jobs:
  promote:
    name: Deploy
    runs-on: [self-hosted, linux, large, ephemeral]
    if: github.event.client_payload.severity == 'info'
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
        with:
          egress-policy: audit

      - name: Checkout Repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
        with:
          ref: deploy
          token: ${{ secrets.HEDERA_BOT_TOKEN }}

      - name: Get chart version
        id: release
        run: |
          VERSION=$(echo ${{ github.event.client_payload.metadata.revision }} | cut -d '+' -f1)
          echo VERSION=${VERSION} >> $GITHUB_OUTPUT

      - name: Set chart version in HelmRelease
        env:
          CHART_PATH: ${{ github.event.client_payload.metadata.path }}
          CHART_VERSION: ${{ steps.release.outputs.version }}
        run: |
          echo "Setting HelmRelease ${CHART_PATH} to ${CHART_VERSION}"
          yq eval '.spec.chart.spec.version=env(CHART_VERSION)' -i ./clusters/${CHART_PATH}/helmrelease.yaml

      - name: Import GPG Key
        id: gpg_importer
        uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
        with:
          git_commit_gpgsign: true
          git_tag_gpgsign: true
          git_user_signingkey: true
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Open deployment PR
        uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
        with:
          author: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
          body: Deploy v${{ steps.release.outputs.version }} to ${{ github.event.client_payload.metadata.path }}
          branch: deploy-${{ github.event.client_payload.metadata.path }}-${{ steps.release.outputs.version }}
          commit-message: Deploy v${{ steps.release.outputs.version }} to ${{ github.event.client_payload.metadata.path }}
          committer: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
          delete-branch: true
          signoff: true
          title: Deploy v${{ steps.release.outputs.version }} to ${{ github.event.client_payload.metadata.path }}
          token: ${{ secrets.HEDERA_BOT_TOKEN }}


Java CI - Main

# This workflow will build a Java project with Ant
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-ant

name: Java CI - Main

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

permissions:
  checks: write

jobs:
  build:

    runs-on: ubuntu-latest

    #defaults:
    #  run:
    #    working-directory: ./..

    steps:
    - uses: actions/checkout@v3

    - name: Cache ivy dependencies
      id: cache-nivy
      uses: actions/cache@v3
      env:
          cache-name: cache-ivy-dependencies
      with:
          # ivy dependencies cache files are stored in `~/.ivy2` on Linux/macOS
          path: ~/.ivy2
          key: ${{ runner.os }}-build-${{ env.cache-name }}

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Generate build.xml
      run: |
        wget -N http://specs.fe.up.pt/tools/eclipse-build.jar
        java -jar eclipse-build.jar https://github.com/specs-feup/specs-java-libs https://github.com/specs-feup/lara-framework ./
        wget -N -O /usr/share/ant/lib/ivy-2.5.0.jar specs.fe.up.pt/libs/ivy-2.5.0.jar
    - name: Build with Ant
      run: |
        ant -noinput -buildfile build.xml

    - name: List directory
      run: |
        echo "Current folder"
        ls
    - name: Publish Test Report
      uses: mikepenz/action-junit-report@v3.6.1
      if: always() # always run even if the previous step fails
      with:
        report_paths: '**/reports-eclipse-build/TEST-*.xml'
        summary: true
        #exclude_sources: ''


Java CI - Staging

# This workflow will build a Java project with Ant
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-ant

name: Java CI - Staging

on:
  push:
    branches: [ staging ]
  pull_request:
    branches: [ staging ]

permissions:
  checks: write

jobs:
  build:

    runs-on: ubuntu-latest

    #defaults:
    #  run:
    #    working-directory: ./..

    steps:
    - uses: actions/checkout@v3

    - name: Cache ivy dependencies
      id: cache-nivy
      uses: actions/cache@v3
      env:
          cache-name: cache-ivy-dependencies
      with:
          # ivy dependencies cache files are stored in `~/.ivy2` on Linux/macOS
          path: ~/.ivy2
          key: ${{ runner.os }}-build-${{ env.cache-name }}

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Generate build.xml
      run: |
        wget -q -N http://specs.fe.up.pt/tools/eclipse-build.jar
        java -jar eclipse-build.jar https://github.com/specs-feup/specs-java-libs https://github.com/specs-feup/lara-framework?commit=staging ./
        wget -N -O /usr/share/ant/lib/ivy-2.5.0.jar specs.fe.up.pt/libs/ivy-2.5.0.jar
    - name: Build with Ant
      run: |
        ant -noinput -buildfile build.xml

    - name: List directory
      run: |
        echo "Current folder"
        ls
    - name: Publish Test Report
      uses: mikepenz/action-junit-report@v3.6.1
      if: always() # always run even if the previous step fails
      with:
        report_paths: '**/reports-eclipse-build/TEST-*.xml'
        summary: true
        #exclude_sources: ''


auto-merge-readme

# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  pull_request:
name: auto-merge-readme
jobs:
  approve:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'googleapis' && github.head_ref == 'autosynth-readme'
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
          script: |
            // only approve PRs from yoshi-automation
            if (context.payload.pull_request.user.login !== "yoshi-automation") {
              return;
            }

            // only approve PRs like "chore: release <release version>"
            if (!context.payload.pull_request.title === "chore: regenerate README") {
              return;
            }

            // only approve PRs with README.md and synth.metadata changes
            const files = new Set(
              (
                await github.paginate(
                  github.pulls.listFiles.endpoint({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    pull_number: context.payload.pull_request.number,
                  })
                )
              ).map(file => file.filename)
            );
            if (files.size != 2 || !files.has("README.md") || !files.has(".github/readme/synth.metadata/synth.metadata")) {
              return;
            }

            // approve README regeneration PR
            await github.pulls.createReview({
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Rubber stamped PR!',
              pull_number: context.payload.pull_request.number,
              event: 'APPROVE'
            });

            // attach automerge label
            await github.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              labels: ['automerge']
            });


auto-release

# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  pull_request:
name: auto-release
jobs:
  approve:
    runs-on: ubuntu-latest
    if: contains(github.head_ref, 'release-please')
    steps:
    - uses: actions/github-script@v7
      with:
        github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
        debug: true
        script: |
          // only approve PRs from release-please[bot]
          if (context.payload.pull_request.user.login !== "release-please[bot]") {
            return;
          }

          // only approve PRs like "chore(main): release <release version>"
          if (  !context.payload.pull_request.title.startsWith("chore(main): release") ) {
            return;
          }

          // only approve PRs with pom.xml and versions.txt changes
          const filesPromise = github.rest.pulls.listFiles.endpoint({
            owner: context.repo.owner,
            repo: context.repo.repo,
            pull_number: context.payload.pull_request.number,
          });
          const changed_files = await github.paginate(filesPromise)

          if ( changed_files.length < 1 ) {
            console.log( "Not proceeding since PR is empty!" )
            return;
          }

          if ( !changed_files.some(v => v.filename.includes("pom")) || !changed_files.some(v => v.filename.includes("versions.txt")) ) {
            console.log( "PR file changes do not have pom.xml or versions.txt -- something is wrong. PTAL!" )
            return;
          }

          // trigger auto-release when
          // 1) it is a SNAPSHOT release (auto-generated post regular release)
          // 2) there are dependency updates only
          // 3) there are no open dependency update PRs in this repo (to avoid multiple releases)
          if (
            context.payload.pull_request.body.includes("Fix") ||
            context.payload.pull_request.body.includes("Build") ||
            context.payload.pull_request.body.includes("Documentation") ||
            context.payload.pull_request.body.includes("BREAKING CHANGES") ||
            context.payload.pull_request.body.includes("Features")
          ) {
            console.log( "Not auto-releasing since it is not a dependency-update-only release." );
            return;
          }

          const promise = github.rest.pulls.list.endpoint({
            owner: context.repo.owner,
            repo: context.repo.repo,
            state: 'open'
          });
          const open_pulls = await github.paginate(promise)

          if ( open_pulls.length > 1 && !context.payload.pull_request.title.includes("SNAPSHOT") ) {
            for ( const pull of open_pulls ) {
              if ( pull.title.startsWith("deps: update dependency") ) {
                console.log( "Not auto-releasing yet since there are dependency update PRs open in this repo." );
                return;
              }
            }
          }

          // approve release PR
          await github.rest.pulls.createReview({
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: 'Rubber stamped release!',
            pull_number: context.payload.pull_request.number,
            event: 'APPROVE'
          });

          // attach kokoro:force-run and automerge labels
          await github.rest.issues.addLabels({
            owner: context.repo.owner,
            repo: context.repo.repo,
            issue_number: context.payload.pull_request.number,
            labels: ['kokoro:force-run']
          });


ci

# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  push:
    branches:
    - main
  pull_request:
name: ci
jobs:
  units:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        java: [11, 17, 21]
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: ${{matrix.java}}
    - run: java -version
    - run: .kokoro/build.sh
      env:
        JOB_TYPE: test
  units-java8:
    # Building using Java 17 and run the tests with Java 8 runtime
    name: "units (8)"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: 8
          distribution: temurin
      - name: "Set jvm system property environment variable for surefire plugin (unit tests)"
        # Maven surefire plugin (unit tests) allows us to specify JVM to run the tests.
        # https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#jvm
        run: echo "SUREFIRE_JVM_OPT=-Djvm=${JAVA_HOME}/bin/java" >> $GITHUB_ENV
        shell: bash
      - uses: actions/setup-java@v4
        with:
          java-version: 17
          distribution: temurin
      - run: .kokoro/build.sh
        env:
          JOB_TYPE: test
  windows:
    runs-on: windows-latest
    steps:
    - name: Support longpaths
      run: git config --system core.longpaths true
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 8
    - run: java -version
    - run: .kokoro/build.bat
      env:
        JOB_TYPE: test
  dependencies:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java: [17]
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: ${{matrix.java}}
    - run: java -version
    - run: .kokoro/dependencies.sh
  javadoc:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 17
    - run: java -version
    - run: .kokoro/build.sh
      env:
        JOB_TYPE: javadoc
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 11
    - run: java -version
    - run: .kokoro/build.sh
      env:
        JOB_TYPE: lint
  clirr:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 8
    - run: java -version
    - run: .kokoro/build.sh
      env:
        JOB_TYPE: clirr


Hermetic library generation upon generation config change through pull requests


Integration tests against emulator

on:
  push:
    branches:
    - main
  pull_request:
name: Integration tests against emulator
jobs:
  units:
    runs-on: ubuntu-latest

    services:
      emulator:
        image: gcr.io/cloud-spanner-emulator/emulator:latest
        ports:
          - 9010:9010
          - 9020:9020

    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 8
    - run: java -version
    - run: .kokoro/build.sh
    - run: mvn -B -Dspanner.testenv.instance="" -Penable-integration-tests -DtrimStackTrace=false -Dclirr.skip=true -Denforcer.skip=true -fae verify
      env:
        JOB_TYPE: test
        SPANNER_EMULATOR_HOST: localhost:9010
        GOOGLE_CLOUD_PROJECT: emulator-test-project


Renovate Bot Config Validation

name: Renovate Bot Config Validation

on:
  pull_request:
    paths:
      - 'renovate.json'

jobs:
  renovate_bot_config_validation:
    runs-on: ubuntu-22.04

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'

    - name: Install Renovate and Config Validator
      run: |
        npm install -g npm@latest
        npm install --global renovate
        renovate-config-validator


samples

# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  pull_request:
name: samples
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 8
      - name: Run sample tests
        run: mvn --quiet --batch-mode test
        working-directory: samples/snippets


# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  pull_request:
name: samples
jobs:
  checkstyle:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 8
      - name: Run checkstyle
        run: mvn -P lint --quiet --batch-mode checkstyle:check
        working-directory: samples/snippets


spring-data-jdbc-sample

# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  pull_request:
name: spring-data-jdbc-sample
jobs:
  spring-data-jdbc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
      - name: Run tests
        run: mvn test
        working-directory: samples/spring-data-jdbc


spring-data-mybatis-sample

# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Github action job to test core java library features on
# downstream client libraries before they are released.
on:
  pull_request:
name: spring-data-mybatis-sample
jobs:
  spring-data-jdbc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
      - name: Run tests
        run: mvn test
        working-directory: samples/spring-data-mybatis


Unmanaged dependency check

on:
  pull_request:
name: Unmanaged dependency check
jobs:
  unmanaged_dependency_check:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 11
    - name: Install modules
      shell: bash
      run: .kokoro/build.sh
    - name: Unmanaged dependency check
      uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.31.0
      with:
        bom-path: pom.xml


Continuous Integration

name: Continuous Integration
on:
  pull_request:
  push:
    branches:
      - master

jobs:
  unit_test_edge:
    name: Unit test with in-memory test service [Edge]
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
    - name: Checkout repo
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        submodules: recursive
        ref: ${{ github.event.pull_request.head.sha }}

    - name: Set up Java
      uses: actions/setup-java@v4
      with:
        java-version: "19"
        distribution: "temurin"

    - name: Set up Gradle
      uses: gradle/actions/setup-gradle@v3

    - name: Run unit tests
      env:
        USER: unittest
        USE_DOCKER_SERVICE: false
      run: ./gradlew --no-daemon test -x checkLicenseMain -x checkLicenses -x spotlessCheck -x spotlessApply -x spotlessJava -P edgeDepsTest

    - name: Run independent resource tuner test
      env:
        USER: unittest
        USE_DOCKER_SERVICE: false
      run: ./gradlew --no-daemon temporal-sdk:testResourceIndependent -x checkLicenseMain -x checkLicenses -x spotlessCheck -x spotlessApply -x spotlessJava -P edgeDepsTest

    - name: Publish Test Report
      uses: mikepenz/action-junit-report@v4
      if: success() || failure() # always run even if the previous step fails
      with:
        report_paths: '**/build/test-results/test/TEST-*.xml'

  unit_test_jdk8:
    name: Unit test with docker service [JDK8]
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
    - name: Checkout repo
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        submodules: recursive
        ref: ${{ github.event.pull_request.head.sha }}

    - name: Set up Java
      uses: actions/setup-java@v4
      with:
        java-version: "11"
        distribution: "temurin"

    - name: Set up Gradle
      uses: gradle/actions/setup-gradle@v3

    - name: Start containerized server and dependencies
      run: |
        docker compose \
          -f ./docker/github/docker-compose.yaml \
          up -d temporal

    - name: Run unit tests
      env:
        USER: unittest
        TEMPORAL_SERVICE_ADDRESS: localhost:7233
        USE_DOCKER_SERVICE: true
      run: ./gradlew --no-daemon test -x checkLicenseMain -x checkLicenses -x spotlessCheck -x spotlessApply -x spotlessJava

    - name: Publish Test Report
      uses: mikepenz/action-junit-report@v4
      if: success() || failure() # always run even if the previous step fails
      with:
        report_paths: '**/build/test-results/test/TEST-*.xml'

  copyright:
    name:  Copyright and code format
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
    - name: Checkout repo
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        submodules: recursive
        ref: ${{ github.event.pull_request.head.sha }}

    - name: Set up Java
      uses: actions/setup-java@v4
      with:
        java-version: "11"
        distribution: "temurin"

    - name: Set up Gradle
      uses: gradle/actions/setup-gradle@v3

    - name: Run copyright and code format checks
      run: ./gradlew --no-daemon checkLicenseMain checkLicenses spotlessCheck


Code Coverage

name: Code Coverage
on:
  push:
    branches:
      - master

jobs:
  code-coverage:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Set up Gradle
        uses: gradle/actions/setup-gradle@v3

      - name: Run Tests
        run: ./gradlew test -x checkLicenseMain -x checkLicenses -x spotlessCheck -x spotlessApply -Pjacoco
        continue-on-error: true

      - name: Run Test Coverage
        run: ./gradlew testCodeCoverageReport -Pjacoco

      - name: Publish Coverage
        env:
          COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
        run: ./gradlew coverallsJacoco -Pjacoco


Features Integration Tests

name: "Features Integration Tests"
on: [push, pull_request]

jobs:
  features-test:
    uses: temporalio/features/.github/workflows/java.yaml@java-breaking-update
    with:
      java-repo-path: ${{github.event.pull_request.head.repo.full_name}}
      version: ${{github.event.pull_request.head.ref}}
      version-is-repo-ref: true
      features-repo-ref: java-breaking-update


Validate Gradle Wrapper

name: "Validate Gradle Wrapper"
on: [push, pull_request]

jobs:
  validation:
    name: "Gradle wrapper validation"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: gradle/wrapper-validation-action@v3