diff --git a/.github/actions/build-custom-image/action.yaml b/.github/actions/build-custom-image/action.yaml new file mode 100644 index 0000000000..4d82fa4a60 --- /dev/null +++ b/.github/actions/build-custom-image/action.yaml @@ -0,0 +1,52 @@ +name: Build custom image +inputs: + target: + required: true + image_name: + required: true + gcp_project_id: + required: true + gcp_workload_identity_provider: + required: true + docker_repo: + required: false +runs: + using: "composite" + steps: + - uses: oursky/google-github-actions-auth@v2 + with: + project_id: ${{ inputs.gcp_project_id }} + workload_identity_provider: ${{ inputs.gcp_workload_identity_provider }} + - uses: oursky/google-github-actions-setup-gcloud@v2 + - name: Configure docker + env: + REPO: ${{ inputs.docker_repo }} + shell: bash + run: | + gcloud auth configure-docker "$REPO" + - name: Build and Push + env: + TARGET: ${{ inputs.target }} + IMAGE_NAME: ${{ inputs.image_name }} + REPO: ${{ inputs.docker_repo }} + shell: bash + run: | + METADATA_FILE="$(mktemp).json" + echo "METADATA_FILE=$METADATA_FILE" + make -C custombuild build-image \ + TARGET=$TARGET \ + BUILD_ARCH=amd64 \ + OUTPUT="type=image,name=$IMAGE_NAME,push-by-digest=true,name-canonical=true,push=true" \ + IMAGE_NAME=$IMAGE_NAME \ + METADATA_FILE="$METADATA_FILE" \ + EXTRA_BUILD_OPTS="--ssh=default" + (set -x && cat "$METADATA_FILE") + DIGEST="$(jq < "$METADATA_FILE" '.["containerimage.digest"]' -r)" + make -C custombuild tag-image SOURCE_DIGESTS="$DIGEST" IMAGE_NAME=$IMAGE_NAME + - name: docker logout + if: ${{ always() }} + env: + REPO: ${{ inputs.docker_repo }} + shell: bash + run: | + docker logout "$REPO" diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml new file mode 100644 index 0000000000..e39aa3cf48 --- /dev/null +++ b/.github/actions/build-image/action.yaml @@ -0,0 +1,61 @@ +name: Build image +inputs: + target: + required: true + image_name: + required: true + push_image: + required: true + build_arch: + required: true + docker_registry: + required: false + docker_username: + required: false + docker_password: + required: false +outputs: + image_digest: + value: ${{ steps.build_image.outputs.image_digest }} +runs: + using: "composite" + steps: + - name: Install qemu for multi arch build + shell: bash + run: docker run --privileged --rm tonistiigi/binfmt --install all + - uses: ./.github/actions/docker-buildx-create + - name: docker login + if: ${{ inputs.push_image == 'true' }} + env: + DOCKER_USERNAME: ${{ inputs.docker_username }} + DOCKER_PASSWORD: ${{ inputs.docker_password }} + DOCKER_REGISTRY: ${{ inputs.docker_registry }} + shell: bash + run: | + printf "$DOCKER_PASSWORD" | docker login --password-stdin --username "$DOCKER_USERNAME" $DOCKER_REGISTRY + - id: build_image + run: | + METADATA_FILE="$(mktemp).json" + echo "METADATA_FILE=$METADATA_FILE" + make build-image \ + BUILD_ARCH=$BUILD_ARCH \ + OUTPUT=$OUTPUT \ + TARGET=$TARGET \ + IMAGE_NAME=$IMAGE_NAME \ + METADATA_FILE="$METADATA_FILE" + (set -x && cat "$METADATA_FILE") + DIGEST="$(jq < "$METADATA_FILE" '.["containerimage.digest"]' -r)" + echo "image_digest=$DIGEST" >> "$GITHUB_OUTPUT" + shell: bash + env: + TARGET: ${{ inputs.target }} + IMAGE_NAME: ${{ inputs.image_name }} + OUTPUT: ${{ (inputs.push_image == 'true') && 'type=image,name=$$IMAGE_NAME,push-by-digest=true,name-canonical=true,push=true' || ''}} + BUILD_ARCH: ${{ inputs.build_arch }} + - name: docker logout + if: ${{ always() }} + env: + DOCKER_REGISTRY: ${{ inputs.docker_registry }} + shell: bash + run: | + docker logout $DOCKER_REGISTRY diff --git a/.github/actions/docker-buildx-create/action.yaml b/.github/actions/docker-buildx-create/action.yaml new file mode 100644 index 0000000000..0fcd20c876 --- /dev/null +++ b/.github/actions/docker-buildx-create/action.yaml @@ -0,0 +1,15 @@ +name: "Create a builder and set BUILDX_BUILDER" +description: "Create a builder and set BUILDX_BUILDER" +runs: + using: "composite" + steps: + - name: "docker buildx create" + shell: bash + env: + BUILDER_NAME: container-builder + run: | + docker buildx create \ + --name "$BUILDER_NAME" \ + --driver docker-container \ + --bootstrap --use + echo "BUILDX_BUILDER=$BUILDER_NAME" >> "$GITHUB_ENV" diff --git a/.github/workflows/ci-branches.yaml b/.github/workflows/ci-branches.yaml new file mode 100644 index 0000000000..dfff0ec10a --- /dev/null +++ b/.github/workflows/ci-branches.yaml @@ -0,0 +1,21 @@ +name: CI - Branches + +on: + push: + branches: + - '*' + - '!gh-pages' + tags: + - '*' + +jobs: + checks: + uses: ./.github/workflows/run-checks.yaml + builds: + needs: checks + uses: ./.github/workflows/run-builds.yaml + secrets: inherit + release: + needs: builds + uses: ./.github/workflows/run-release.yaml + secrets: inherit diff --git a/.github/workflows/ci-prs.yaml b/.github/workflows/ci-prs.yaml new file mode 100644 index 0000000000..72f69c51c2 --- /dev/null +++ b/.github/workflows/ci-prs.yaml @@ -0,0 +1,20 @@ +name: CI - Pull Requests + +on: + pull_request: + branches: + - '*' + - '!gh-pages' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + uses: ./.github/workflows/run-checks.yaml + builds: + needs: checks + uses: ./.github/workflows/run-builds.yaml + with: + amd64-build-only: true diff --git a/.github/workflows/custom-build.yaml b/.github/workflows/custom-build.yaml index 2773fb1779..3919a0f871 100644 --- a/.github/workflows/custom-build.yaml +++ b/.github/workflows/custom-build.yaml @@ -42,6 +42,10 @@ jobs: if: ${{ github.repository == 'authgear/authgear-server' }} steps: - uses: actions/checkout@v4 + - name: Install qemu for multi arch build + run: docker run --privileged --rm tonistiigi/binfmt --install all + - uses: ./.github/actions/docker-buildx-create + # https://aran.dev/posts/github-actions-go-private-modules/ - name: Set up SSH key env: @@ -53,7 +57,25 @@ jobs: printf "$AUTHGEAR_PRIVATE_DEPLOY_KEY" | base64 --decode | ssh-add - echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" - - run: make -C custombuild build-image TARGET=authgearx IMAGE_NAME=authgear-server-custom + + - name: Build and push to HK + uses: ./.github/actions/build-custom-image + with: + target: authgearx + image_name: "${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_HK }}/authgear-server" + gcp_project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_HK }} + gcp_workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_HK }} + docker_repo: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_HK }} + + - name: Build and push to US + uses: ./.github/actions/build-custom-image + with: + target: authgearx + image_name: "${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_US }}/authgear-server" + gcp_project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_US }} + gcp_workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_US }} + docker_repo: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_US }} + - name: Clean up SSH key if: ${{ always() }} run: | @@ -62,45 +84,15 @@ jobs: echo "SSH_AUTH_SOCK=" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=" >> "$GITHUB_ENV" - - uses: oursky/google-github-actions-auth@v2 - with: - project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_HK }} - workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_HK }} - - uses: oursky/google-github-actions-setup-gcloud@v2 - - name: Configure docker - env: - REPO: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_HK }} - run: | - gcloud auth configure-docker "$REPO" - - name: Push to HK - env: - REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_HK }} - run: | - make -C custombuild tag-image IMAGE_NAME=authgear-server-custom REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-server" - make -C custombuild push-image REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-server" || docker logout "$REPO" - - - uses: oursky/google-github-actions-auth@v2 - with: - project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_US }} - workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_US }} - - uses: oursky/google-github-actions-setup-gcloud@v2 - - name: Configure docker - env: - REPO: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_US }} - run: | - gcloud auth configure-docker "$REPO" - - name: Push to US - env: - REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_US }} - run: | - make -C custombuild tag-image IMAGE_NAME=authgear-server-custom REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-server" - make -C custombuild push-image REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-server" || docker logout "$REPO" - portal-image-custom: runs-on: ubuntu-24.04 if: ${{ github.repository == 'authgear/authgear-server' }} steps: - uses: actions/checkout@v4 + - name: Install qemu for multi arch build + run: docker run --privileged --rm tonistiigi/binfmt --install all + - uses: ./.github/actions/docker-buildx-create + # https://aran.dev/posts/github-actions-go-private-modules/ - name: Set up SSH key env: @@ -112,7 +104,25 @@ jobs: printf "$AUTHGEAR_PRIVATE_DEPLOY_KEY" | base64 --decode | ssh-add - echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" - - run: make -C custombuild build-image TARGET=portalx IMAGE_NAME=authgear-portal-custom + + - name: Build and push to HK + uses: ./.github/actions/build-custom-image + with: + target: portalx + image_name: "${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_HK }}/authgear-portal" + gcp_project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_HK }} + gcp_workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_HK }} + docker_repo: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_HK }} + + - name: Build and push to US + uses: ./.github/actions/build-custom-image + with: + target: portalx + image_name: "${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_US }}/authgear-portal" + gcp_project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_US }} + gcp_workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_US }} + docker_repo: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_US }} + - name: Clean up SSH key if: ${{ always() }} run: | @@ -120,37 +130,3 @@ jobs: ssh-agent -k echo "SSH_AUTH_SOCK=" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=" >> "$GITHUB_ENV" - - - uses: oursky/google-github-actions-auth@v2 - with: - project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_HK }} - workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_HK }} - - uses: oursky/google-github-actions-setup-gcloud@v2 - - name: Configure docker - env: - REPO: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_HK }} - run: | - gcloud auth configure-docker "$REPO" - - name: Push to HK - env: - REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_HK }} - run: | - make -C custombuild tag-image IMAGE_NAME=authgear-portal-custom REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-portal" - make -C custombuild push-image REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-portal" || docker logout "$REPO" - - - uses: oursky/google-github-actions-auth@v2 - with: - project_id: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_PROJECT_ID_US }} - workload_identity_provider: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_GOOGLE_WORKLOAD_IDENTITY_PROVIDER_US }} - - uses: oursky/google-github-actions-setup-gcloud@v2 - - name: Configure docker - env: - REPO: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_US }} - run: | - gcloud auth configure-docker "$REPO" - - name: Push to US - env: - REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX_US }} - run: | - make -C custombuild tag-image IMAGE_NAME=authgear-portal-custom REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-portal" - make -C custombuild push-image REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-portal" || docker logout "$REPO" diff --git a/.github/workflows/oursky.yaml b/.github/workflows/oursky.yaml index ea0b997fda..cde1be46f3 100644 --- a/.github/workflows/oursky.yaml +++ b/.github/workflows/oursky.yaml @@ -25,7 +25,24 @@ jobs: printf "$AUTHGEAR_PRIVATE_DEPLOY_KEY" | base64 --decode | ssh-add - echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" - - run: make -C custombuild build-image TARGET=authgearx IMAGE_NAME=authgear-server-custom + - uses: ./.github/actions/docker-buildx-create + - name: Build and Push + env: + REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX }} + run: | + METADATA_FILE="$(mktemp).json" + echo "METADATA_FILE=$METADATA_FILE" + IMAGE_NAME="$REPO_PREFIX/authgear-server" + make -C custombuild build-image \ + TARGET=authgearx \ + BUILD_ARCH=amd64 \ + OUTPUT="type=image,name=$IMAGE_NAME,push-by-digest=true,name-canonical=true,push=true" \ + IMAGE_NAME=$IMAGE_NAME \ + METADATA_FILE="$METADATA_FILE" \ + EXTRA_BUILD_OPTS="--ssh=default" + (set -x && cat "$METADATA_FILE") + DIGEST="$(jq < "$METADATA_FILE" '.["containerimage.digest"]' -r)" + make -C custombuild tag-image SOURCE_DIGESTS="$DIGEST" IMAGE_NAME=$IMAGE_NAME - name: Clean up SSH key if: ${{ always() }} run: | @@ -33,12 +50,6 @@ jobs: ssh-agent -k echo "SSH_AUTH_SOCK=" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=" >> "$GITHUB_ENV" - - name: Push - env: - REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX }} - run: | - make -C custombuild tag-image IMAGE_NAME=authgear-server-custom REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-server" - make -C custombuild push-image REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-server" portal-image-custom: if: ${{ github.repository == 'oursky/authgear-server' }} @@ -56,7 +67,24 @@ jobs: printf "$AUTHGEAR_PRIVATE_DEPLOY_KEY" | base64 --decode | ssh-add - echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" - - run: make -C custombuild build-image TARGET=portalx IMAGE_NAME=authgear-portal-custom + - uses: ./.github/actions/docker-buildx-create + - name: Build and Push + env: + REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX }} + run: | + METADATA_FILE="$(mktemp).json" + echo "METADATA_FILE=$METADATA_FILE" + IMAGE_NAME="$REPO_PREFIX/authgear-portal" + make -C custombuild build-image \ + TARGET=portalx \ + BUILD_ARCH=amd64 \ + OUTPUT="type=image,name=$IMAGE_NAME,push-by-digest=true,name-canonical=true,push=true" \ + IMAGE_NAME=$IMAGE_NAME \ + METADATA_FILE="$METADATA_FILE" \ + EXTRA_BUILD_OPTS="--ssh=default" + (set -x && cat "$METADATA_FILE") + DIGEST="$(jq < "$METADATA_FILE" '.["containerimage.digest"]' -r)" + make -C custombuild tag-image SOURCE_DIGESTS="$DIGEST" IMAGE_NAME=$IMAGE_NAME - name: Clean up SSH key if: ${{ always() }} run: | @@ -64,9 +92,3 @@ jobs: ssh-agent -k echo "SSH_AUTH_SOCK=" >> "$GITHUB_ENV" echo "SSH_AGENT_PID=" >> "$GITHUB_ENV" - - name: Push - env: - REPO_PREFIX: ${{ secrets.AUTHGEAR_CUSTOM_BUILD_REPO_PREFIX }} - run: | - make -C custombuild tag-image IMAGE_NAME=authgear-portal-custom REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-portal" - make -C custombuild push-image REMOTE_IMAGE_NAME="$REPO_PREFIX/authgear-portal" diff --git a/.github/workflows/run-builds.yaml b/.github/workflows/run-builds.yaml new file mode 100644 index 0000000000..7f537854a2 --- /dev/null +++ b/.github/workflows/run-builds.yaml @@ -0,0 +1,140 @@ +name: Run builds + +on: + workflow_call: + inputs: + amd64-build-only: + required: false + default: false + type: boolean + +jobs: + authgear-image-amd64: + if: ${{ github.repository != 'oursky/authgear-server' }} + runs-on: ubuntu-24.04 + outputs: + image_digest: ${{ steps.build_image.outputs.image_digest }} + env: + PUSH_IMAGE: "${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push') && 'true' || 'false' }}" + steps: + - uses: actions/checkout@v4 + - id: build_image + uses: ./.github/actions/build-image + with: + target: authgear + image_name: quay.io/theauthgear/authgear-server + push_image: "${{ env.PUSH_IMAGE }}" + build_arch: amd64 + docker_registry: quay.io + docker_username: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_USERNAME || '' }}" + docker_password: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_ROBOT_TOKEN || '' }}" + authgear-image-arm64: + if: ${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push' && !inputs.amd64-build-only) }} + runs-on: ubuntu-24.04 + outputs: + image_digest: ${{ steps.build_image.outputs.image_digest }} + env: + PUSH_IMAGE: "${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push') && 'true' || 'false' }}" + steps: + - uses: actions/checkout@v4 + - id: build_image + uses: ./.github/actions/build-image + with: + target: authgear + image_name: quay.io/theauthgear/authgear-server + push_image: "${{ env.PUSH_IMAGE }}" + build_arch: arm64 + docker_registry: quay.io + docker_username: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_USERNAME || '' }}" + docker_password: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_ROBOT_TOKEN || '' }}" + + authgear-image: + if: ${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push' && !inputs.amd64-build-only) }} + runs-on: ubuntu-24.04 + needs: ["authgear-image-amd64", "authgear-image-arm64"] + env: + TARGET: authgear + IMAGE_NAME: quay.io/theauthgear/authgear-server + PUSH_IMAGE: "${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push') && 'true' || 'false' }}" + AMD64_DIGEST: "${{needs.authgear-image-amd64.outputs.image_digest}}" + ARM64_DIGEST: "${{needs.authgear-image-arm64.outputs.image_digest}}" + steps: + - uses: actions/checkout@v4 + - name: docker login + if: ${{ github.repository == 'authgear/authgear-server' && github.event_name == 'push' }} + env: + DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.QUAY_ROBOT_TOKEN }} + run: | + printf "$DOCKER_PASSWORD" | docker login --password-stdin --username "$DOCKER_USERNAME" quay.io + - run: make tag-image SOURCE_DIGESTS="$AMD64_DIGEST $ARM64_DIGEST" IMAGE_NAME=$IMAGE_NAME + - name: docker logout + if: ${{ always() }} + run: | + docker logout quay.io + + + portal-image-amd64: + if: ${{ github.repository != 'oursky/authgear-server' }} + outputs: + image_digest: ${{ steps.build_image.outputs.image_digest }} + runs-on: ubuntu-24.04 + env: + PUSH_IMAGE: "${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push') && 'true' || 'false' }}" + steps: + - uses: actions/checkout@v4 + - id: build_image + uses: ./.github/actions/build-image + with: + target: portal + image_name: quay.io/theauthgear/authgear-portal + push_image: "${{ env.PUSH_IMAGE }}" + build_arch: amd64 + docker_registry: quay.io + docker_username: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_USERNAME || '' }}" + docker_password: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_ROBOT_TOKEN || '' }}" + + portal-image-arm64: + if: ${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push' && !inputs.amd64-build-only) }} + outputs: + image_digest: ${{ steps.build_image.outputs.image_digest }} + runs-on: ubuntu-24.04 + env: + PUSH_IMAGE: "${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push') && 'true' || 'false' }}" + steps: + - uses: actions/checkout@v4 + - id: build_image + uses: ./.github/actions/build-image + with: + target: portal + image_name: quay.io/theauthgear/authgear-portal + push_image: "${{ env.PUSH_IMAGE }}" + build_arch: arm64 + docker_registry: quay.io + docker_username: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_USERNAME || '' }}" + docker_password: "${{ env.PUSH_IMAGE == 'true' && secrets.QUAY_ROBOT_TOKEN || '' }}" + + portal-image: + if: ${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push' && !inputs.amd64-build-only) }} + runs-on: ubuntu-24.04 + needs: ["portal-image-amd64", "portal-image-arm64"] + env: + TARGET: authgear + IMAGE_NAME: quay.io/theauthgear/authgear-portal + PUSH_IMAGE: "${{ (github.repository == 'authgear/authgear-server' && github.event_name == 'push') && 'true' || 'false' }}" + AMD64_DIGEST: "${{needs.portal-image-amd64.outputs.image_digest}}" + ARM64_DIGEST: "${{needs.portal-image-arm64.outputs.image_digest}}" + steps: + - uses: actions/checkout@v4 + - name: docker login + if: ${{ github.repository == 'authgear/authgear-server' && github.event_name == 'push' }} + env: + DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.QUAY_ROBOT_TOKEN }} + run: | + printf "$DOCKER_PASSWORD" | docker login --password-stdin --username "$DOCKER_USERNAME" quay.io + - run: make tag-image SOURCE_DIGESTS="$AMD64_DIGEST $ARM64_DIGEST" IMAGE_NAME=$IMAGE_NAME + - name: docker logout + if: ${{ always() }} + run: | + docker logout quay.io diff --git a/.github/workflows/ci.yaml b/.github/workflows/run-checks.yaml similarity index 66% rename from .github/workflows/ci.yaml rename to .github/workflows/run-checks.yaml index 277ebefd83..9ea5f68dba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/run-checks.yaml @@ -1,16 +1,7 @@ -name: CI +name: Run checks on: - push: - branches: - - '*' - - '!gh-pages' - tags: - - '*' - pull_request: - branches: - - '*' - - '!gh-pages' + workflow_call: jobs: authui-test: @@ -115,30 +106,6 @@ jobs: working-directory: ./portal if: ${{ !cancelled() }} - portal-image: - if: ${{ github.repository != 'oursky/authgear-server' }} - runs-on: ubuntu-24.04 - needs: portal-test - env: - TARGET: portal - IMAGE_NAME: authgear-portal - steps: - - uses: actions/checkout@v4 - - run: make build-image TARGET=$TARGET IMAGE_NAME=$IMAGE_NAME - - run: make tag-image IMAGE_NAME=$IMAGE_NAME - - name: docker login - if: ${{ github.repository == 'authgear/authgear-server' && github.event_name == 'push' }} - env: - DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.QUAY_ROBOT_TOKEN }} - run: | - printf "$DOCKER_PASSWORD" | docker login --password-stdin --username "$DOCKER_USERNAME" quay.io - - run: make push-image IMAGE_NAME=$IMAGE_NAME - if: ${{ github.repository == 'authgear/authgear-server' && github.event_name == 'push' }} - - name: docker logout - if: ${{ always() }} - run: | - docker logout quay.io authgear-test: if: ${{ github.repository != 'oursky/authgear-server' }} @@ -216,49 +183,3 @@ jobs: safe_image_name=$(echo -n "$image" | tr '/:' '_') docker save "$image" -o ~/.cache/images/"$safe_image_name".tar done - authgear-image: - if: ${{ github.repository != 'oursky/authgear-server' }} - runs-on: ubuntu-24.04 - needs: ["authgear-test", "authui-test"] - env: - TARGET: authgear - IMAGE_NAME: authgear-server - steps: - - uses: actions/checkout@v4 - - run: make build-image TARGET=$TARGET IMAGE_NAME=$IMAGE_NAME - - run: make tag-image IMAGE_NAME=$IMAGE_NAME - - name: docker login - if: ${{ github.repository == 'authgear/authgear-server' && github.event_name == 'push' }} - env: - DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.QUAY_ROBOT_TOKEN }} - run: | - printf "$DOCKER_PASSWORD" | docker login --password-stdin --username "$DOCKER_USERNAME" quay.io - - run: make push-image IMAGE_NAME=$IMAGE_NAME - if: ${{ github.repository == 'authgear/authgear-server' && github.event_name == 'push' }} - - name: docker logout - if: ${{ always() }} - run: | - docker logout quay.io - - release: - if: ${{ github.repository != 'oursky/authgear-server' }} - runs-on: ubuntu-24.04 - needs: ["portal-test", "authgear-test"] - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install-native-deps - - uses: actions/setup-go@v4 - with: - go-version-file: "./go.mod" - - uses: actions/setup-node@v4 - with: - node-version-file: "./.tool-versions" - - run: make vendor - - run: make binary - - uses: oursky/action-gh-release@v2 - if: ${{ github.repository == 'authgear/authgear-server' && startsWith(github.ref, 'refs/tags/') && !startsWith(github.ref, 'refs/tags/staging-') }} - with: - draft: true - files: | - ./dist/* diff --git a/.github/workflows/run-release.yaml b/.github/workflows/run-release.yaml new file mode 100644 index 0000000000..dd35c0928a --- /dev/null +++ b/.github/workflows/run-release.yaml @@ -0,0 +1,26 @@ +name: Run releases + +on: + workflow_call: + +jobs: + release: + if: ${{ github.repository != 'oursky/authgear-server' }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/install-native-deps + - uses: actions/setup-go@v4 + with: + go-version-file: "./go.mod" + - uses: actions/setup-node@v4 + with: + node-version-file: "./.tool-versions" + - run: make vendor + - run: make binary + - uses: oursky/action-gh-release@v2 + if: ${{ github.repository == 'authgear/authgear-server' && startsWith(github.ref, 'refs/tags/') && !startsWith(github.ref, 'refs/tags/staging-') }} + with: + draft: true + files: | + ./dist/* diff --git a/Makefile b/Makefile index 1a34abdd44..6f17cc8ff6 100644 --- a/Makefile +++ b/Makefile @@ -1,46 +1,10 @@ -# The use of variables -# -# We use simply expanded variables in this Makefile. -# -# This means -# 1. You use ::= instead of = because = defines a recursively expanded variable. -# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html -# 2. You use ::= instead of := because ::= is a POSIX standard. -# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html -# 3. You do not use ?= because it is shorthand to define a recursively expanded variable. -# See https://www.gnu.org/software/make/manual/html_node/Conditional-Assignment.html -# You should use the long form documented in the above link instead. -# 4. When you override a variable in the command line, as documented in https://www.gnu.org/software/make/manual/html_node/Overriding.html -# you specify the variable with ::= instead of = or := -# If you fail to do so, the variable becomes recursively expanded variable accidentally. -# -# GIT_NAME could be empty. -ifeq ($(origin GIT_NAME), undefined) - GIT_NAME ::= $(shell git describe --exact-match 2>/dev/null) -endif -ifeq ($(origin GIT_HASH), undefined) - GIT_HASH ::= git-$(shell git rev-parse --short=12 HEAD) -endif -ifeq ($(origin LDFLAGS), undefined) - LDFLAGS ::= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}" -endif +CMD_AUTHGEAR ::= authgear +CMD_PORTAL ::= portal +BUILD_CTX ::= . -# osusergo: https://godoc.org/github.com/golang/go/src/os/user -# netgo: https://golang.org/doc/go1.5#net -# static_build: https://github.com/golang/go/issues/26492#issuecomment-635563222 -# The binary is static on Linux only. It is not static on macOS. -# timetzdata: https://golang.org/doc/go1.15#time/tzdata -GO_BUILD_TAGS ::= osusergo netgo static_build timetzdata -GO_RUN_TAGS ::= - - -.PHONY: start -start: - go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/authgear start - -.PHONY: start-portal -start-portal: - go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/portal start +include ./common.mk +include ./scripts/make/go-mod-outdated.mk +include ./scripts/make/govulncheck.mk .PHONY: authgearonce-start authgearonce-start: GO_RUN_TAGS += authgearonce @@ -67,13 +31,6 @@ vendor: $(MAKE) authui $(MAKE) portal -.PHONY: go-mod-outdated -go-mod-outdated: - # https://stackoverflow.com/questions/55866604/whats-the-go-mod-equivalent-of-npm-outdated - # Since go 1.21, this command will exit 2 when one of the dependencies require a go version newer than us. - # This implies we have to use the latest verion of Go whenever possible. - go list -u -m -f '{{if .Update}}{{if not .Indirect}}{{.}}{{end}}{{end}}' all - .PHONY: ensure-important-modules-up-to-date ensure-important-modules-up-to-date: # If grep matches something, it exits 0, otherwise it exits 1. @@ -124,14 +81,6 @@ fmt: find ./devtools ./pkg ./cmd ./e2e -name '*.go' -not -name 'wire_gen.go' -not -name '*_mock_test.go' | sort | xargs goimports -w -format-only -local github.com/authgear/authgear-server $(MAKE) sort-translations -.PHONY: govulncheck -govulncheck: - govulncheck -show traces,version,verbose ./... - -.PHONY: build -build: - go build -o $(BIN_NAME) -tags "$(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET) - .PHONY: binary binary: GO_BUILD_TAGS += authgearlite binary: @@ -160,29 +109,6 @@ check-tidy: make -C authui check-tidy make -C portal check-tidy -.PHONY: build-image -build-image: - # Add --pull so that we are using the latest base image. - docker build --pull --file ./cmd/$(TARGET)/Dockerfile --tag $(IMAGE_NAME) --build-arg GIT_HASH=$(GIT_HASH) . - -.PHONY: tag-image -tag-image: DOCKER_IMAGE ::= quay.io/theauthgear/$(IMAGE_NAME) -tag-image: - docker tag $(IMAGE_NAME) $(DOCKER_IMAGE):latest - docker tag $(IMAGE_NAME) $(DOCKER_IMAGE):$(GIT_HASH) - if [ ! -z $(GIT_NAME) ]; then docker tag $(IMAGE_NAME) $(DOCKER_IMAGE):$(GIT_NAME); fi - -.PHONY: push-image -push-image: DOCKER_IMAGE ::= quay.io/theauthgear/$(IMAGE_NAME) -push-image: - docker manifest inspect $(DOCKER_IMAGE):$(GIT_HASH) > /dev/null; if [ $$? -eq 0 ]; then \ - echo "$(DOCKER_IMAGE):$(GIT_HASH) exists. Skip push"; \ - else \ - docker push $(DOCKER_IMAGE):latest ;\ - docker push $(DOCKER_IMAGE):$(GIT_HASH) ;\ - if [ ! -z $(GIT_NAME) ]; then docker push $(DOCKER_IMAGE):$(GIT_NAME); fi ;\ - fi - .PHONY: html-email html-email: # Generate `.mjml` templates from `.mjml.gotemplate` files diff --git a/cmd/authgear/Dockerfile b/cmd/authgear/Dockerfile index 0cc86ad817..fd0093662a 100644 --- a/cmd/authgear/Dockerfile +++ b/cmd/authgear/Dockerfile @@ -21,7 +21,7 @@ RUN make build BIN_NAME=authgear TARGET=authgear GIT_HASH=$GIT_HASH # RUN readelf -d ./authgear | grep 'There is no dynamic section in this file' # Stage 2: Build the static files -FROM node:20.9.0-bookworm as stage2 +FROM --platform=$BUILDPLATFORM node:20.9.0-bookworm as stage2 ARG GIT_HASH WORKDIR /usr/src/app COPY ./scripts/npm/package.json ./scripts/npm/package-lock.json ./scripts/npm/ diff --git a/cmd/portal/Dockerfile b/cmd/portal/Dockerfile index 4a9720ee14..d1a792c689 100644 --- a/cmd/portal/Dockerfile +++ b/cmd/portal/Dockerfile @@ -21,7 +21,7 @@ RUN make build BIN_NAME=authgear-portal TARGET=portal GIT_HASH=$GIT_HASH # RUN readelf -d ./authgear | grep 'There is no dynamic section in this file' # Stage 2: Build the static files -FROM node:20.9.0-bookworm as stage2 +FROM --platform=$BUILDPLATFORM node:20.9.0-bookworm as stage2 ARG GIT_HASH WORKDIR /usr/src/app COPY ./scripts/npm/package.json ./scripts/npm/package-lock.json ./scripts/npm/ @@ -36,7 +36,7 @@ COPY . . RUN make authui GIT_HASH=$GIT_HASH # Stage 3: Build the portal static files -FROM node:20.9.0-bookworm as stage3 +FROM --platform=$BUILDPLATFORM node:20.9.0-bookworm as stage3 ARG GIT_HASH # If the working directory is /src, Parcel will have some problem with it. WORKDIR /usr/src/app diff --git a/common.mk b/common.mk new file mode 100644 index 0000000000..684b532c3f --- /dev/null +++ b/common.mk @@ -0,0 +1,93 @@ +# The use of variables +# +# We use simply expanded variables in this Makefile. +# +# This means +# 1. You use ::= instead of = because = defines a recursively expanded variable. +# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html +# 2. You use ::= instead of := because ::= is a POSIX standard. +# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html +# 3. You do not use ?= because it is shorthand to define a recursively expanded variable. +# See https://www.gnu.org/software/make/manual/html_node/Conditional-Assignment.html +# You should use the long form documented in the above link instead. +# 4. When you override a variable in the command line, as documented in https://www.gnu.org/software/make/manual/html_node/Overriding.html +# you specify the variable with ::= instead of = or := +# If you fail to do so, the variable becomes recursively expanded variable accidentally. +# +# GIT_NAME could be empty. +ifeq ($(origin GIT_NAME), undefined) + GIT_NAME ::= $(shell git describe --exact-match 2>/dev/null) +endif +ifeq ($(origin GIT_HASH), undefined) + GIT_HASH ::= git-$(shell git rev-parse --short=12 HEAD) +endif +ifeq ($(origin LDFLAGS), undefined) + LDFLAGS ::= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}" +endif + + +# osusergo: https://godoc.org/github.com/golang/go/src/os/user +# netgo: https://golang.org/doc/go1.5#net +# static_build: https://github.com/golang/go/issues/26492#issuecomment-635563222 +# The binary is static on Linux only. It is not static on macOS. +# timetzdata: https://golang.org/doc/go1.15#time/tzdata +GO_BUILD_TAGS ::= osusergo netgo static_build timetzdata +GO_RUN_TAGS ::= + + +.PHONY: start +start: + go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/${CMD_AUTHGEAR} start + +.PHONY: start-portal +start-portal: + go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/${CMD_PORTAL} start + +.PHONY: build +build: + go build -o $(BIN_NAME) -tags "$(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET) + + + +.PHONY: build-image +build-image: +IMAGE_TAG_BASE ::= $(IMAGE_NAME):$(GIT_HASH) +BUILD_OPTS ::= +ifeq ($(BUILD_ARCH),amd64) +BUILD_OPTS += --platform linux/$(BUILD_ARCH) +else ifeq ($(BUILD_ARCH),arm64) +BUILD_OPTS += --platform linux/$(BUILD_ARCH) +endif +ifneq ($(OUTPUT),) +BUILD_OPTS += --output=$(OUTPUT) +endif +ifneq ($(EXTRA_BUILD_OPTS),) +BUILD_OPTS += $(EXTRA_BUILD_OPTS) +endif +ifneq ($(METADATA_FILE),) +BUILD_OPTS += --metadata-file $(METADATA_FILE) +endif +build-image: + @# Add --pull so that we are using the latest base image. + @# The build context is the parent directory + @# --provenance=false because we have no idea to figure out how to deal with the unknown manifest yet. + @# See https://github.com/authgear/authgear-server/pull/4943#discussion_r1891263998 + docker build --pull \ + --provenance=false \ + --file ./cmd/$(TARGET)/Dockerfile \ + $(BUILD_OPTS) \ + --build-arg GIT_HASH=$(GIT_HASH) ${BUILD_CTX} + +.PHONY: tag-image +tag-image: +IMAGE_SOURCES ::= +TAGS ::= --tag $(IMAGE_NAME):latest +TAGS += --tag $(IMAGE_NAME):$(GIT_HASH) +ifneq (${GIT_NAME},) +TAGS += --tag $(IMAGE_NAME):$(GIT_NAME) +endif +IMAGE_SOURCES := $(foreach digest,$(SOURCE_DIGESTS),${IMAGE_NAME}@${digest} ) +tag-image: + docker buildx imagetools create \ + $(TAGS) \ + ${IMAGE_SOURCES} diff --git a/custombuild/Makefile b/custombuild/Makefile index 71b8860bce..d498461315 100644 --- a/custombuild/Makefile +++ b/custombuild/Makefile @@ -1,60 +1,7 @@ -# The use of variables -# -# We use simply expanded variables in this Makefile. -# -# This means -# 1. You use ::= instead of = because = defines a recursively expanded variable. -# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html -# 2. You use ::= instead of := because ::= is a POSIX standard. -# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html -# 3. You do not use ?= because it is shorthand to define a recursively expanded variable. -# See https://www.gnu.org/software/make/manual/html_node/Conditional-Assignment.html -# You should use the long form documented in the above link instead. -# 4. When you override a variable in the command line, as documented in https://www.gnu.org/software/make/manual/html_node/Overriding.html -# you specify the variable with ::= instead of = or := -# If you fail to do so, the variable becomes recursively expanded variable accidentally. -# -# GIT_NAME could be empty. -ifeq ($(origin GIT_NAME), undefined) - GIT_NAME ::= $(shell git describe --exact-match 2>/dev/null) -endif -ifeq ($(origin GIT_HASH), undefined) - GIT_HASH ::= git-$(shell git rev-parse --short=12 HEAD) -endif -ifeq ($(origin LDFLAGS), undefined) - LDFLAGS ::= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}" -endif +CMD_AUTHGEAR ::= authgearx +CMD_PORTAL ::= portalx +BUILD_CTX ::= ../ -# osusergo: https://godoc.org/github.com/golang/go/src/os/user -# netgo: https://golang.org/doc/go1.5#net -# static_build: https://github.com/golang/go/issues/26492#issuecomment-635563222 -# The binary is static on Linux only. It is not static on macOS. -# timetzdata: https://golang.org/doc/go1.15#time/tzdata -GO_BUILD_TAGS ::= osusergo netgo static_build timetzdata -GO_RUN_TAGS ::= - -.PHONY: start -start: - go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/authgearx start - -.PHONY: start-portal -start-portal: - go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/portalx start - -.PHONY: build -build: - go build -o $(BIN_NAME) -tags "$(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET) - -.PHONY: build-image -build-image: - # Add --pull so that we are using the latest base image. - # The build context is the parent directory - docker build --pull --ssh=default --file ./cmd/$(TARGET)/Dockerfile --tag $(IMAGE_NAME) --build-arg GIT_HASH=$(GIT_HASH) ../ - -.PHONY: tag-image -tag-image: - docker tag $(IMAGE_NAME) $(REMOTE_IMAGE_NAME):$(GIT_HASH) - -.PHONY: push-image -push-image: - docker push $(REMOTE_IMAGE_NAME):$(GIT_HASH) +include ../common.mk +include ../scripts/make/go-mod-outdated.mk +include ../scripts/make/govulncheck.mk diff --git a/e2e/Makefile b/e2e/Makefile index cfe520e937..ec0e055346 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,3 +1,6 @@ +include ../scripts/make/go-mod-outdated.mk +include ../scripts/make/govulncheck.mk + .PHONY: ci ci: @# Check if any `focus: true` is present in the test files diff --git a/k6/Makefile b/k6/Makefile index 7ada571185..ff8193d643 100644 --- a/k6/Makefile +++ b/k6/Makefile @@ -1,3 +1,6 @@ +include ../scripts/make/go-mod-outdated.mk +include ../scripts/make/govulncheck.mk + #export K6_HTTP_DEBUG=true # We cannot use K6_DURATION and K6_VUS because # using them will make k6 to ignore scenarios. diff --git a/scripts/make/go-mod-outdated.mk b/scripts/make/go-mod-outdated.mk new file mode 100644 index 0000000000..94cbf676b0 --- /dev/null +++ b/scripts/make/go-mod-outdated.mk @@ -0,0 +1,6 @@ +.PHONY: go-mod-outdated +go-mod-outdated: + # https://stackoverflow.com/questions/55866604/whats-the-go-mod-equivalent-of-npm-outdated + # Since go 1.21, this command will exit 2 when one of the dependencies require a go version newer than us. + # This implies we have to use the latest verion of Go whenever possible. + go list -u -m -f '{{if .Update}}{{if not .Indirect}}{{.}}{{end}}{{end}}' all diff --git a/scripts/make/govulncheck.mk b/scripts/make/govulncheck.mk new file mode 100644 index 0000000000..53fdb95d68 --- /dev/null +++ b/scripts/make/govulncheck.mk @@ -0,0 +1,3 @@ +.PHONY: govulncheck +govulncheck: + govulncheck -show traces,version,verbose ./...