From d979b7dc86b348721546a81b454868ef2ca51852 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Fri, 3 Oct 2025 18:22:44 -0700 Subject: [PATCH 01/56] chore: update java version for java formatter (#336) --- .github/workflows/lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ab57ead9..bda7755d 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -46,7 +46,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: - java-version: 17.x + java-version: 21.x distribution: temurin - name: Run formatter id: formatter From 49f60f86dfa883f80a316a1fd09177edf1cb1b46 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 4 Oct 2025 02:29:37 +0100 Subject: [PATCH 02/56] chore(deps): update actions/setup-go action to v6 (#335) Co-authored-by: Andras Kerekes --- .github/workflows/conformance.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 82e50d85..81fe0f31 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -39,7 +39,7 @@ jobs: distribution: temurin - name: Setup Go - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version: '1.21' From 15be91d8d6503a45e32eaf501cbdae195a814562 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 4 Oct 2025 02:34:37 +0100 Subject: [PATCH 03/56] chore(deps): update actions/checkout action to v5 (#334) Co-authored-by: Andras Kerekes --- .github/workflows/codeql.yml | 2 +- .github/workflows/conformance.yaml | 2 +- .github/workflows/lint.yaml | 4 ++-- .github/workflows/scorecard.yml | 2 +- .github/workflows/unit.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c28404fd..5ff23715 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,7 +42,7 @@ jobs: uploads.github.com:443 - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 81fe0f31..33d05f71 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -30,7 +30,7 @@ jobs: repo.maven.apache.org:443 storage.googleapis.com:443 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index bda7755d..6f445acf 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -20,7 +20,7 @@ jobs: allowed-endpoints: > github.com:443 repo.maven.apache.org:443 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: @@ -42,7 +42,7 @@ jobs: with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # v2 minimum required + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # v2 minimum required - name: Set up JDK uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 752afbf3..11679d87 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -45,7 +45,7 @@ jobs: *.github.com:443 - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 01522215..6781915c 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -28,7 +28,7 @@ jobs: repo.maven.apache.org:443 api.adoptium.net:443 *.githubusercontent.com:443 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: From 1d42e96b97bd3b14123d2e60b093a06502dbc3d2 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 6 Oct 2025 10:26:51 -0700 Subject: [PATCH 04/56] chore: add release-assets.githubusercontent.com to allowed sites for harden runner (#337) --- .github/workflows/codeql.yml | 9 ++++----- .github/workflows/scorecard.yml | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5ff23715..134120d7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -14,7 +14,7 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest - + permissions: actions: read contents: read @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false - matrix: + matrix: # Autobuild each of these seperate maven projects working-directory: ['invoker', 'functions-framework-api', 'function-maven-plugin'] @@ -37,10 +37,11 @@ jobs: github.com:443 objects.githubusercontent.com:443 proxy.golang.org:443 + release-assets.githubusercontent.com:443 repo.maven.apache.org:443 storage.googleapis.com:443 uploads.github.com:443 - + - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -57,8 +58,6 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - - - name: Build run: | (cd functions-framework-api/ && mvn install) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 11679d87..80ffb070 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -43,7 +43,7 @@ jobs: www.bestpractices.dev:443 *.sigstore.dev:443 *.github.com:443 - + - name: "Checkout code" uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: From 177f1d92e41f6db3596dffb19e66dcf941e02819 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 6 Oct 2025 11:14:00 -0700 Subject: [PATCH 05/56] chore: add release-assets.githubusercontent.com to allowed sites for harden runner (#338) * chore: add release-assets.githubusercontent.com to allowed sites for harden runner * chore: add release-assets.githubusercontent.com to allowed sites for harden runner --- .github/workflows/conformance.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 33d05f71..21745f56 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -27,6 +27,7 @@ jobs: github.com:443 objects.githubusercontent.com:443 proxy.golang.org:443 + release-assets.githubusercontent.com:443 repo.maven.apache.org:443 storage.googleapis.com:443 @@ -94,4 +95,4 @@ jobs: useBuildpacks: false validateConcurrency: true cmd: "'mvn -f invoker/conformance/pom.xml function:run -Drun.functionTarget=com.google.cloud.functions.conformance.ConcurrentHttpConformanceFunction'" - startDelay: 10 \ No newline at end of file + startDelay: 10 From 88e3f96fdc7edc854a032807120b6268a36e63be Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 6 Oct 2025 19:22:00 +0100 Subject: [PATCH 06/56] chore(deps): update all non-major dependencies (#331) Co-authored-by: Andras Kerekes --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/conformance.yaml | 6 +++--- .github/workflows/lint.yaml | 8 ++++---- .github/workflows/scorecard.yml | 6 +++--- .github/workflows/unit.yaml | 4 ++-- function-maven-plugin/pom.xml | 12 ++++++------ functions-framework-api/pom.xml | 6 +++--- invoker/conformance/pom.xml | 2 +- invoker/core/pom.xml | 16 ++++++++-------- invoker/pom.xml | 4 ++-- invoker/testfunction/pom.xml | 4 ++-- 11 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 134120d7..32d7e54e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: disable-sudo: true egress-policy: block @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support languages: java @@ -65,6 +65,6 @@ jobs: (cd function-maven-plugin && mvn install) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: category: ${{ matrix.working-directory }} diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 21745f56..88e5d8d9 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -18,7 +18,7 @@ jobs: ] steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: disable-sudo: true egress-policy: block @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: ${{ matrix.java }} distribution: temurin @@ -42,7 +42,7 @@ jobs: - name: Setup Go uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: - go-version: '1.21' + go-version: '1.25' - name: Build API with Maven run: (cd functions-framework-api/ && mvn install) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6f445acf..6cc5a37b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: disable-sudo: true egress-policy: block @@ -22,7 +22,7 @@ jobs: repo.maven.apache.org:443 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: 11.x distribution: temurin @@ -38,13 +38,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # v2 minimum required - name: Set up JDK - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: 21.x distribution: temurin diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 80ffb070..a6b8e986 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: disable-sudo: true egress-policy: block @@ -50,7 +50,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -62,6 +62,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: sarif_file: results.sarif diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 6781915c..2826605a 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -19,7 +19,7 @@ jobs: ] steps: - name: Harden Runner - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: disable-sudo: true egress-policy: block @@ -30,7 +30,7 @@ jobs: *.githubusercontent.com:443 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: ${{ matrix.java }} distribution: temurin diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index 76de071a..4ddebd00 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -41,12 +41,12 @@ org.apache.maven maven-plugin-api - 3.9.9 + 3.9.11 org.apache.maven maven-core - 3.9.9 + 3.9.11 org.apache.maven.plugin-tools @@ -58,7 +58,7 @@ com.google.cloud.functions.invoker java-function-invoker - 1.4.0 + 1.4.1 @@ -71,7 +71,7 @@ com.google.truth truth - 1.4.4 + 1.4.5 test @@ -132,7 +132,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.2 + 3.12.0 attach-javadocs @@ -145,7 +145,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + 3.2.8 sign-artifacts diff --git a/functions-framework-api/pom.xml b/functions-framework-api/pom.xml index 438f2898..3d6226c7 100644 --- a/functions-framework-api/pom.xml +++ b/functions-framework-api/pom.xml @@ -28,8 +28,8 @@ UTF-8 - 3.14.0 - 3.11.2 + 3.14.1 + 3.12.0 5.3.2 @@ -177,7 +177,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + 3.2.8 sign-artifacts diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index 61ef6c47..9b008ae9 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -33,7 +33,7 @@ com.google.code.gson gson - 2.12.1 + 2.13.2 io.cloudevents diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index dc17c779..45d9ceda 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -69,7 +69,7 @@ com.google.code.gson gson - 2.12.1 + 2.13.2 com.ryanharter.auto.value @@ -98,12 +98,12 @@ org.eclipse.jetty jetty-servlet - 9.4.57.v20241219 + 9.4.58.v20250814 org.eclipse.jetty jetty-server - 9.4.57.v20241219 + 9.4.58.v20250814 com.beust @@ -122,7 +122,7 @@ org.mockito mockito-core - 5.16.0 + 5.20.0 test @@ -139,19 +139,19 @@ com.google.truth truth - 1.4.4 + 1.4.5 test com.google.truth.extensions truth-java8-extension - 1.4.4 + 1.4.5 test org.eclipse.jetty jetty-client - 9.4.57.v20241219 + 9.4.58.v20250814 test @@ -174,7 +174,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.6.0 + 3.6.1 package diff --git a/invoker/pom.xml b/invoker/pom.xml index f5a92d6e..c7db44cb 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -80,7 +80,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.2 + 3.12.0 attach-javadocs @@ -93,7 +93,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + 3.2.8 sign-artifacts diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index 00f65f19..b6a18a5f 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -31,12 +31,12 @@ com.google.guava guava - 33.4.0-jre + 33.5.0-jre com.google.code.gson gson - 2.12.1 + 2.13.2 From b2f7ed083338e24681214745b002d34ecce32a6b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 6 Oct 2025 21:23:24 +0100 Subject: [PATCH 07/56] chore(deps): update googlecloudplatform/functions-framework-conformance digest to 6ae50bd (#325) Co-authored-by: Andras Kerekes --- .github/workflows/conformance.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 88e5d8d9..9b03a3c6 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -54,7 +54,7 @@ jobs: run: (cd function-maven-plugin/ && mvn install) - name: Run HTTP conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@1041a97e93a463d9efb17dda821f3ddc0bf0024f # main + uses: GoogleCloudPlatform/functions-framework-conformance/action@6ae50bd9db5ed980c5ceff3cf7b200f705be94fc # main with: functionType: 'http' useBuildpacks: false @@ -62,7 +62,7 @@ jobs: startDelay: 10 - name: Run Typed conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@1041a97e93a463d9efb17dda821f3ddc0bf0024f # main + uses: GoogleCloudPlatform/functions-framework-conformance/action@6ae50bd9db5ed980c5ceff3cf7b200f705be94fc # main with: functionType: 'http' declarativeType: 'typed' @@ -71,7 +71,7 @@ jobs: startDelay: 10 - name: Run background event conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@1041a97e93a463d9efb17dda821f3ddc0bf0024f # main + uses: GoogleCloudPlatform/functions-framework-conformance/action@6ae50bd9db5ed980c5ceff3cf7b200f705be94fc # main with: functionType: 'legacyevent' useBuildpacks: false @@ -80,7 +80,7 @@ jobs: startDelay: 10 - name: Run cloudevent conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@1041a97e93a463d9efb17dda821f3ddc0bf0024f # main + uses: GoogleCloudPlatform/functions-framework-conformance/action@6ae50bd9db5ed980c5ceff3cf7b200f705be94fc # main with: functionType: 'cloudevent' useBuildpacks: false @@ -89,7 +89,7 @@ jobs: startDelay: 10 - name: Run HTTP concurrency conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@1041a97e93a463d9efb17dda821f3ddc0bf0024f # main + uses: GoogleCloudPlatform/functions-framework-conformance/action@6ae50bd9db5ed980c5ceff3cf7b200f705be94fc # main with: functionType: 'http' useBuildpacks: false From e4d8e677043bac5f0310f4a6744bf9caa36410f0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:47:11 -0700 Subject: [PATCH 08/56] chore(main): release java-function-invoker 1.4.2-SNAPSHOT (#328) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- invoker/conformance/pom.xml | 4 ++-- invoker/core/pom.xml | 6 +++--- invoker/pom.xml | 2 +- invoker/testfunction/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index 9b008ae9..f882d4ad 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -4,12 +4,12 @@ java-function-invoker-parent com.google.cloud.functions.invoker - 1.4.1 + 1.4.2-SNAPSHOT com.google.cloud.functions.invoker conformance - 1.4.1 + 1.4.2-SNAPSHOT GCF Confromance Tests diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 45d9ceda..28eaf73d 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.1 + 1.4.2-SNAPSHOT com.google.cloud.functions.invoker java-function-invoker - 1.4.1 + 1.4.2-SNAPSHOT GCF Java Invoker Application that invokes a GCF Java function. This application is a @@ -115,7 +115,7 @@ com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.1 + 1.4.2-SNAPSHOT test-jar test diff --git a/invoker/pom.xml b/invoker/pom.xml index c7db44cb..0800eb37 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -8,7 +8,7 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.1 + 1.4.2-SNAPSHOT pom GCF Java Invoker Parent diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index b6a18a5f..78df65aa 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.1 + 1.4.2-SNAPSHOT com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.1 + 1.4.2-SNAPSHOT Example GCF Function Jar An example of a GCF function packaged into a jar. We use this in tests. From 420327967b96b4aa66c0edd6753a9d6db781478d Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Tue, 7 Oct 2025 15:27:48 -0700 Subject: [PATCH 09/56] fix: add autovalue plugin config to invoker/core/pom.xml to fix local development (#339) --- invoker/core/pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 28eaf73d..06dccab1 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -158,6 +158,24 @@ + + maven-compiler-plugin + 3.14.1 + + + + com.google.auto.value + auto-value + 1.11.0 + + + com.ryanharter.auto.value + auto-value-gson + 1.3.1 + + + + maven-jar-plugin 3.4.2 From e152e0d697c1a3f4a90a6480347b4ddf0b73f3e3 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Tue, 7 Oct 2025 17:58:00 -0700 Subject: [PATCH 10/56] fix: several minor fixes (#341) * fix: several minor fixes Changed a couple of maven plugins's scope to provided as recommended by Maven. Fixed #117: during first deploy unless the allowUnauthenticated property was set to true, the gcloud command was waiting on a prompt that was not visible and could not be interacted with. This change removes the allowUnauthenticated property's default value and relies on gcloud's behavior which will default this value to false if prompting is turned off with '--quiet'. If the allowUnauthenticated is explicitly set, it will correctly append the --[no-]allow-unauthenticated flag to the command. If the gcloud command fails, the plugin will not report a successful build. * fix formatting --- function-maven-plugin/pom.xml | 2 ++ .../functions/plugin/DeployFunction.java | 19 +++++++++++++------ .../functions/plugin/DeployFunctionTest.java | 3 ++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index 4ddebd00..a7e8764c 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -42,11 +42,13 @@ org.apache.maven maven-plugin-api 3.9.11 + provided org.apache.maven maven-core 3.9.11 + provided org.apache.maven.plugin-tools diff --git a/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java b/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java index 35dab3f8..d0067ded 100644 --- a/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java +++ b/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java @@ -63,9 +63,8 @@ public class DeployFunction extends CloudSdkMojo { */ @Parameter( alias = "deploy.allowunauthenticated", - property = "function.deploy.allowunauthenticated", - defaultValue = "false") - boolean allowUnauthenticated; + property = "function.deploy.allowunauthenticated") + Boolean allowUnauthenticated; /** * Name of a Google Cloud Function (as defined in source code) that will be executed. Defaults to @@ -314,8 +313,12 @@ public List getCommands() { if (triggerEvent != null) { commands.add("--trigger-event=" + triggerEvent); } - if (allowUnauthenticated) { - commands.add("--allow-unauthenticated"); + if (allowUnauthenticated != null) { + if (allowUnauthenticated) { + commands.add("--allow-unauthenticated"); + } else { + commands.add("--no-allow-unauthenticated"); + } } if (functionTarget != null) { commands.add("--entry-point=" + functionTarget); @@ -371,6 +374,8 @@ public List getCommands() { if (projectId != null) { commands.add("--project=" + projectId); } + + commands.add("--quiet"); return Collections.unmodifiableList(commands); } @@ -382,7 +387,9 @@ public void execute() throws MojoExecutionException { System.out.println("Executing Cloud SDK command: gcloud " + String.join(" ", params)); gcloud.runCommand(params); } catch (CloudSdkNotFoundException | IOException | ProcessHandlerException ex) { - Logger.getLogger(DeployFunction.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(DeployFunction.class.getName()) + .log(Level.SEVERE, "Function deployment failed", ex); + throw new MojoExecutionException("Function deployment failed", ex); } } } diff --git a/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java b/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java index e5a2913d..2b441d4e 100644 --- a/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java +++ b/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java @@ -53,7 +53,8 @@ public void testDeployFunctionCommandLine() { "--env-vars-file=myfile", "--set-build-env-vars=env1=a,env2=b", "--build-env-vars-file=myfile2", - "--runtime=java11"); + "--runtime=java11", + "--quiet"); assertThat(mojo.getCommands()).isEqualTo(expected); } } From 043f647ef10cb8993441106be12ae495f86d7972 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Thu, 16 Oct 2025 11:48:06 -0700 Subject: [PATCH 11/56] chore: update relese process (#343) --- .kokoro/release.sh | 45 ++++++++++++++++++++++++----------- function-maven-plugin/pom.xml | 25 ++++++++++++++----- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 66ef1e6d..7a7e067e 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -3,21 +3,19 @@ # Stop execution when any command fails. set -e -# update the Maven version to 3.6.3 +# update the Maven version to 3.9.11 pushd /usr/local -wget https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.tar.gz -tar -xvzf apache-maven-3.6.3-bin.tar.gz apache-maven-3.6.3 +wget https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.tar.gz +tar -xvzf apache-maven-3.9.11-bin.tar.gz apache-maven-3.9.11 rm -f /usr/local/apache-maven -ln -s /usr/local/apache-maven-3.6.3 /usr/local/apache-maven -rm apache-maven-3.6.3-bin.tar.gz +ln -s /usr/local/apache-maven-3.9.11 /usr/local/apache-maven +rm apache-maven-3.9.11-bin.tar.gz popd # Get secrets from keystore and set and environment variables. setup_environment_secrets() { export GPG_TTY=$(tty) - export SONATYPE_USERNAME=$(cat ${KOKORO_KEYSTORE_DIR}/75669_functions-framework-java-release-bot-sonatype-password | cut -f1 -d':') - export SONATYPE_PASSWORD=$(cat ${KOKORO_KEYSTORE_DIR}/75669_functions-framework-java-release-bot-sonatype-password | cut -f2 -d':') export GPG_PASSPHRASE=$(cat ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-passphrase) # Add the key ring files to $GNUPGHOME to verify the GPG credentials. @@ -26,6 +24,29 @@ setup_environment_secrets() { mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-pubkeyring $GNUPGHOME/pubring.gpg mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-keyring $GNUPGHOME/secring.gpg gpg -k + + echo "KOKORO_GFILE_DIR: ${KOKORO_GFILE_DIR}" + SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" + echo "Creating folder for secrets: ${SECRET_LOCATION} (if not exists)" + mkdir -p "${SECRET_LOCATION}" + echo "Content of ${SECRET_LOCATION}:" + ls -alt "${SECRET_LOCATION}" + echo "------" + + export SECRET_MANAGER_PROJECT_ID="serverless-runtimes" + export CENTRAL_REPO_USER="sonatype-central-repo-user" + export CENTRAL_REPO_TOKEN="sonatype-central-repo-token" + SECRET_MANAGER_KEYS="${CENTRAL_REPO_USER} ${CENTRAL_REPO_TOKEN}" + echo "SECRET_MANAGER_PROJECT_ID: ${SECRET_MANAGER_PROJECT_ID}, SECRET_MANAGER_KEYS: ${SECRET_MANAGER_KEYS}" + + for key in $(echo "${SECRET_MANAGER_KEYS}" | sed "s/,/ /g") + do + gcloud secrets versions access latest \ + --project "${SECRET_MANAGER_PROJECT_ID}" \ + --secret "${key}" \ + --out-file "${SECRET_LOCATION}/${key}" + echo "Created ${SECRET_LOCATION}/${key}" + done } create_settings_xml_file() { @@ -42,14 +63,10 @@ create_settings_xml_file() { - sonatype-nexus-staging - ${SONATYPE_USERNAME} - ${SONATYPE_PASSWORD} + sonatype-central-portal + $(cat "${SECRET_LOCATION}/${CENTRAL_REPO_USER}") + $(cat "${SECRET_LOCATION}/${CENTRAL_REPO_TOKEN}") - - sonatype-nexus-snapshots - ${SONATYPE_USERNAME} - ${SONATYPE_PASSWORD} " > $1 diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index a7e8764c..a1678bd6 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -23,6 +23,21 @@ HEAD + + + Andras Kerekes + akerekes@google.com + Google LLC + http://www.google.com + + + Di Xu + dixuswe@google.com + Google LLC + http://www.google.com + + + Apache License, Version 2.0 @@ -159,14 +174,12 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - sonatype-nexus-snapshots - https://oss.sonatype.org/ - true + sonatype-central-portal From 5f847d3daf2367526445815e0952e0fdcf2f22af Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Fri, 17 Oct 2025 13:59:43 -0700 Subject: [PATCH 12/56] chore: update release process (#344) --- .kokoro/release.cfg | 8 ++++++++ .kokoro/release.sh | 27 ++------------------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/.kokoro/release.cfg b/.kokoro/release.cfg index f2ee4665..08d0ac9f 100644 --- a/.kokoro/release.cfg +++ b/.kokoro/release.cfg @@ -6,6 +6,14 @@ before_action { keystore_config_id: 75669 keyname: "functions-framework-java-release-bot-sonatype-password" } + keystore_resource { + keystore_config_id: 75669 + keyname: "functions-framework-release-sonatype-central-portal-username" + } + keystore_resource { + keystore_config_id: 75669 + keyname: "functions-framework-release-sonatype-central-portal-password" + } keystore_resource { keystore_config_id: 70247 keyname: "maven-gpg-pubkeyring" diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 7a7e067e..1aa8d455 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -24,29 +24,6 @@ setup_environment_secrets() { mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-pubkeyring $GNUPGHOME/pubring.gpg mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-keyring $GNUPGHOME/secring.gpg gpg -k - - echo "KOKORO_GFILE_DIR: ${KOKORO_GFILE_DIR}" - SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" - echo "Creating folder for secrets: ${SECRET_LOCATION} (if not exists)" - mkdir -p "${SECRET_LOCATION}" - echo "Content of ${SECRET_LOCATION}:" - ls -alt "${SECRET_LOCATION}" - echo "------" - - export SECRET_MANAGER_PROJECT_ID="serverless-runtimes" - export CENTRAL_REPO_USER="sonatype-central-repo-user" - export CENTRAL_REPO_TOKEN="sonatype-central-repo-token" - SECRET_MANAGER_KEYS="${CENTRAL_REPO_USER} ${CENTRAL_REPO_TOKEN}" - echo "SECRET_MANAGER_PROJECT_ID: ${SECRET_MANAGER_PROJECT_ID}, SECRET_MANAGER_KEYS: ${SECRET_MANAGER_KEYS}" - - for key in $(echo "${SECRET_MANAGER_KEYS}" | sed "s/,/ /g") - do - gcloud secrets versions access latest \ - --project "${SECRET_MANAGER_PROJECT_ID}" \ - --secret "${key}" \ - --out-file "${SECRET_LOCATION}/${key}" - echo "Created ${SECRET_LOCATION}/${key}" - done } create_settings_xml_file() { @@ -64,8 +41,8 @@ create_settings_xml_file() { sonatype-central-portal - $(cat "${SECRET_LOCATION}/${CENTRAL_REPO_USER}") - $(cat "${SECRET_LOCATION}/${CENTRAL_REPO_TOKEN}") + $(cat "${KOKORO_KEYSTORE_DIR}/75699_functions-framework-release-sonatype-central-portal-username") + $(cat "${KOKORO_KEYSTORE_DIR}/75699_functions-framework-release-sonatype-central-portal-password") From 3ce8832b498113f84e9a6607e30c885cf6f5b834 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 10:07:45 -0700 Subject: [PATCH 13/56] chore: fix generated release m2 settings content (#345) --- .kokoro/release.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 1aa8d455..59580d66 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -44,7 +44,6 @@ create_settings_xml_file() { $(cat "${KOKORO_KEYSTORE_DIR}/75699_functions-framework-release-sonatype-central-portal-username") $(cat "${KOKORO_KEYSTORE_DIR}/75699_functions-framework-release-sonatype-central-portal-password") - " > $1 } From 9aa60907c749fcb14efc4a5919a5ea518d14df96 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 10:27:33 -0700 Subject: [PATCH 14/56] chore: fix generated release m2 settings content - attempt2 (#346) --- .kokoro/release.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 59580d66..c6e1ee05 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -53,6 +53,7 @@ setup_environment_secrets # Pick the right package to release based on the Kokoro job name. cd ${KOKORO_ARTIFACTS_DIR}/github/functions-framework-java create_settings_xml_file "settings.xml" +cat settings.xml echo "KOKORO_JOB_NAME=${KOKORO_JOB_NAME}" if [[ $KOKORO_JOB_NAME == *"function-maven-plugin"* ]]; then cd function-maven-plugin From 1cbda1a0be06ba61d6e89b7c4c82bd9b2d152e41 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 10:32:30 -0700 Subject: [PATCH 15/56] chore: fix generated release m2 settings content - attempt3 (#347) --- .kokoro/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index c6e1ee05..ffc31a87 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -41,8 +41,8 @@ create_settings_xml_file() { sonatype-central-portal - $(cat "${KOKORO_KEYSTORE_DIR}/75699_functions-framework-release-sonatype-central-portal-username") - $(cat "${KOKORO_KEYSTORE_DIR}/75699_functions-framework-release-sonatype-central-portal-password") + $(cat "${KOKORO_KEYSTORE_DIR}/75669_functions-framework-release-sonatype-central-portal-username") + $(cat "${KOKORO_KEYSTORE_DIR}/75669_functions-framework-release-sonatype-central-portal-password") " > $1 From bd78f345bc99686763259b4e196b53e0ebfd8183 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 10:42:54 -0700 Subject: [PATCH 16/56] chore: fix snapshot repo url (#348) --- .kokoro/release.sh | 1 - function-maven-plugin/pom.xml | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index ffc31a87..2804846d 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -53,7 +53,6 @@ setup_environment_secrets # Pick the right package to release based on the Kokoro job name. cd ${KOKORO_ARTIFACTS_DIR}/github/functions-framework-java create_settings_xml_file "settings.xml" -cat settings.xml echo "KOKORO_JOB_NAME=${KOKORO_JOB_NAME}" if [[ $KOKORO_JOB_NAME == *"function-maven-plugin"* ]]; then cd function-maven-plugin diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index a1678bd6..ee45a444 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -116,18 +116,6 @@ - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - - sonatype-nexus-staging - Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - sonatype-oss-release From a2eec52e3046b797e675468440bcb43572cbd7ef Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 10:56:09 -0700 Subject: [PATCH 17/56] chore: fix snapshot repo url (#349) * chore: fix snapshot repo url * chore: fix snapshot repo url --- function-maven-plugin/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index ee45a444..c00117ce 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -168,6 +168,7 @@ true sonatype-central-portal + https://central.sonatype.com/repository/maven-snapshots/ From f9a70f6b082d8d5c82cc2cc1a277b4ff2ca30710 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 11:34:51 -0700 Subject: [PATCH 18/56] chore: fix release (#350) * chore: fix snapshot repo url * chore: fix snapshot repo url * chore: fix release --- functions-framework-api/pom.xml | 38 +++++++++++++++++---------------- invoker/pom.xml | 34 +++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/functions-framework-api/pom.xml b/functions-framework-api/pom.xml index 3d6226c7..e9994047 100644 --- a/functions-framework-api/pom.xml +++ b/functions-framework-api/pom.xml @@ -41,6 +41,21 @@ + + + Andras Kerekes + akerekes@google.com + Google LLC + http://www.google.com + + + Di Xu + dixuswe@google.com + Google LLC + http://www.google.com + + + scm:git:https://github.com/GoogleCloudPlatform/functions-framework-java.git scm:git:git@github.com:GoogleCloudPlatform/functions-framework-java.git @@ -131,18 +146,6 @@ - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - - sonatype-nexus-staging - Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - sonatype-oss-release @@ -189,14 +192,13 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - sonatype-nexus-snapshots - https://oss.sonatype.org/ - true + sonatype-central-portal + https://central.sonatype.com/repository/maven-snapshots/ diff --git a/invoker/pom.xml b/invoker/pom.xml index 0800eb37..0698f631 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -24,6 +24,29 @@ HEAD + + + Andras Kerekes + akerekes@google.com + Google LLC + http://www.google.com + + + Di Xu + dixuswe@google.com + Google LLC + http://www.google.com + + + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + core testfunction @@ -105,14 +128,13 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - sonatype-nexus-snapshots - https://oss.sonatype.org/ - true + sonatype-central-portal + https://central.sonatype.com/repository/maven-snapshots/ From 3a568368dc1b1ce2ab173f7e9df5d6f6a25a2210 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:20:05 -0700 Subject: [PATCH 19/56] chore(main): release java-function-invoker 1.4.2 (#340) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- invoker/CHANGELOG.md | 7 +++++++ invoker/conformance/pom.xml | 4 ++-- invoker/core/pom.xml | 6 +++--- invoker/pom.xml | 2 +- invoker/testfunction/pom.xml | 4 ++-- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index e86b66fc..f6e3d0a5 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1 +1 @@ -{"functions-framework-api":"1.1.4","invoker":"1.4.1","function-maven-plugin":"0.11.1"} +{"functions-framework-api":"1.1.4","invoker":"1.4.2","function-maven-plugin":"0.11.1"} diff --git a/invoker/CHANGELOG.md b/invoker/CHANGELOG.md index 9fb244aa..1333434e 100644 --- a/invoker/CHANGELOG.md +++ b/invoker/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.4.2](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/java-function-invoker-v1.4.1...java-function-invoker-v1.4.2) (2025-10-20) + + +### Bug Fixes + +* add autovalue plugin config to invoker/core/pom.xml to fix local development ([#339](https://github.com/GoogleCloudPlatform/functions-framework-java/issues/339)) ([4203279](https://github.com/GoogleCloudPlatform/functions-framework-java/commit/420327967b96b4aa66c0edd6753a9d6db781478d)) + ## [1.4.1](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/java-function-invoker-v1.4.0...java-function-invoker-v1.4.1) (2025-03-07) diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index f882d4ad..ccebd793 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -4,12 +4,12 @@ java-function-invoker-parent com.google.cloud.functions.invoker - 1.4.2-SNAPSHOT + 1.4.2 com.google.cloud.functions.invoker conformance - 1.4.2-SNAPSHOT + 1.4.2 GCF Confromance Tests diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 06dccab1..787341d0 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.2-SNAPSHOT + 1.4.2 com.google.cloud.functions.invoker java-function-invoker - 1.4.2-SNAPSHOT + 1.4.2 GCF Java Invoker Application that invokes a GCF Java function. This application is a @@ -115,7 +115,7 @@ com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.2-SNAPSHOT + 1.4.2 test-jar test diff --git a/invoker/pom.xml b/invoker/pom.xml index 0698f631..cd969f0b 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -8,7 +8,7 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.2-SNAPSHOT + 1.4.2 pom GCF Java Invoker Parent diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index 78df65aa..cca7015d 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.2-SNAPSHOT + 1.4.2 com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.2-SNAPSHOT + 1.4.2 Example GCF Function Jar An example of a GCF function packaged into a jar. We use this in tests. From ac8dd01013d41fd8236e79252bd089d7ccc4c53e Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 16:57:38 -0700 Subject: [PATCH 20/56] chore: surpress mvn logging during release (#352) --- .kokoro/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 2804846d..8e1b30b0 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -66,7 +66,7 @@ echo "pwd=$(pwd)" # Make sure `JAVA_HOME` is set and using jdk11. export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64 echo "JAVA_HOME=$JAVA_HOME" -mvn clean deploy -B \ +mvn clean deploy -B -q \ -P sonatype-oss-release \ --settings=../settings.xml \ -Dgpg.executable=gpg \ From 251ef75a105b9f6400edff3d59020846e742c40b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:09:05 -0700 Subject: [PATCH 21/56] chore(main): release java-function-invoker 1.4.3 (#351) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Andras Kerekes --- .github/.release-please-manifest.json | 2 +- invoker/CHANGELOG.md | 7 +++++++ invoker/conformance/pom.xml | 4 ++-- invoker/core/pom.xml | 6 +++--- invoker/pom.xml | 2 +- invoker/testfunction/pom.xml | 4 ++-- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index f6e3d0a5..87a47513 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1 +1 @@ -{"functions-framework-api":"1.1.4","invoker":"1.4.2","function-maven-plugin":"0.11.1"} +{"functions-framework-api":"1.1.4","invoker":"1.4.3","function-maven-plugin":"0.11.1"} diff --git a/invoker/CHANGELOG.md b/invoker/CHANGELOG.md index 1333434e..9502293b 100644 --- a/invoker/CHANGELOG.md +++ b/invoker/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.4.3](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/java-function-invoker-v1.4.2...java-function-invoker-v1.4.3) (2025-10-20) + + +### Bug Fixes + +* add autovalue plugin config to invoker/core/pom.xml to fix local development ([#339](https://github.com/GoogleCloudPlatform/functions-framework-java/issues/339)) ([4203279](https://github.com/GoogleCloudPlatform/functions-framework-java/commit/420327967b96b4aa66c0edd6753a9d6db781478d)) + ## [1.4.2](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/java-function-invoker-v1.4.1...java-function-invoker-v1.4.2) (2025-10-20) diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index ccebd793..3f87d3c5 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -4,12 +4,12 @@ java-function-invoker-parent com.google.cloud.functions.invoker - 1.4.2 + 1.4.3 com.google.cloud.functions.invoker conformance - 1.4.2 + 1.4.3 GCF Confromance Tests diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 787341d0..3e73e84b 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.2 + 1.4.3 com.google.cloud.functions.invoker java-function-invoker - 1.4.2 + 1.4.3 GCF Java Invoker Application that invokes a GCF Java function. This application is a @@ -115,7 +115,7 @@ com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.2 + 1.4.3 test-jar test diff --git a/invoker/pom.xml b/invoker/pom.xml index cd969f0b..0feb32eb 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -8,7 +8,7 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.2 + 1.4.3 pom GCF Java Invoker Parent diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index cca7015d..3b619b35 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.2 + 1.4.3 com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.2 + 1.4.3 Example GCF Function Jar An example of a GCF function packaged into a jar. We use this in tests. From 0e24d42573d104f8b73921513f6f296ada4a255c Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Mon, 20 Oct 2025 17:31:33 -0700 Subject: [PATCH 22/56] fix: update dependency com.google.cloud.functions.invoker to 1.4.3 (#354) --- function-maven-plugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index c00117ce..fb12fd9c 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -75,7 +75,7 @@ com.google.cloud.functions.invoker java-function-invoker - 1.4.1 + 1.4.3 From 25d8eb70afcfa223aad6a8e52c11d0d1229e82a4 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:37:36 -0700 Subject: [PATCH 23/56] chore(main): release function-maven-plugin 0.11.2 (#342) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .github/.release-please-manifest.json | 2 +- function-maven-plugin/CHANGELOG.md | 8 ++++++++ function-maven-plugin/pom.xml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 87a47513..9bc86bf6 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1 +1 @@ -{"functions-framework-api":"1.1.4","invoker":"1.4.3","function-maven-plugin":"0.11.1"} +{"functions-framework-api":"1.1.4","invoker":"1.4.3","function-maven-plugin":"0.11.2"} diff --git a/function-maven-plugin/CHANGELOG.md b/function-maven-plugin/CHANGELOG.md index f1fbe49a..8b636bc6 100644 --- a/function-maven-plugin/CHANGELOG.md +++ b/function-maven-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.11.2](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/function-maven-plugin-v0.11.1...function-maven-plugin-v0.11.2) (2025-10-21) + + +### Bug Fixes + +* several minor fixes ([#341](https://github.com/GoogleCloudPlatform/functions-framework-java/issues/341)) ([e152e0d](https://github.com/GoogleCloudPlatform/functions-framework-java/commit/e152e0d697c1a3f4a90a6480347b4ddf0b73f3e3)) +* update dependency com.google.cloud.functions.invoker to 1.4.3 ([#354](https://github.com/GoogleCloudPlatform/functions-framework-java/issues/354)) ([0e24d42](https://github.com/GoogleCloudPlatform/functions-framework-java/commit/0e24d42573d104f8b73921513f6f296ada4a255c)) + ## [0.11.1](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/function-maven-plugin-v0.11.0...function-maven-plugin-v0.11.1) (2024-11-27) diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index fb12fd9c..bbd0bfa6 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -10,7 +10,7 @@ com.google.cloud.functions function-maven-plugin maven-plugin - 0.11.2-SNAPSHOT + 0.11.2 Functions Framework Plugin A Maven plugin that allows functions to be deployed, and to be run locally using the Java Functions Framework. From 8db1dec242d8f7d9a6e789fb08fc6be44cfa02e3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:34:29 -0700 Subject: [PATCH 24/56] chore(main): release java-function-invoker 1.4.4-SNAPSHOT (#353) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- invoker/conformance/pom.xml | 4 ++-- invoker/core/pom.xml | 6 +++--- invoker/pom.xml | 2 +- invoker/testfunction/pom.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index 3f87d3c5..dbb5bbc5 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -4,12 +4,12 @@ java-function-invoker-parent com.google.cloud.functions.invoker - 1.4.3 + 1.4.4-SNAPSHOT com.google.cloud.functions.invoker conformance - 1.4.3 + 1.4.4-SNAPSHOT GCF Confromance Tests diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 3e73e84b..05af393a 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.3 + 1.4.4-SNAPSHOT com.google.cloud.functions.invoker java-function-invoker - 1.4.3 + 1.4.4-SNAPSHOT GCF Java Invoker Application that invokes a GCF Java function. This application is a @@ -115,7 +115,7 @@ com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.3 + 1.4.4-SNAPSHOT test-jar test diff --git a/invoker/pom.xml b/invoker/pom.xml index 0feb32eb..40185fcf 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -8,7 +8,7 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.3 + 1.4.4-SNAPSHOT pom GCF Java Invoker Parent diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index 3b619b35..4ce12501 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -4,12 +4,12 @@ com.google.cloud.functions.invoker java-function-invoker-parent - 1.4.3 + 1.4.4-SNAPSHOT com.google.cloud.functions.invoker java-function-invoker-testfunction - 1.4.3 + 1.4.4-SNAPSHOT Example GCF Function Jar An example of a GCF function packaged into a jar. We use this in tests. From b6d37e50ddc482a7095f8203779a9f0d24505c65 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:43:32 -0700 Subject: [PATCH 25/56] chore(main): release function-maven-plugin 0.11.3-SNAPSHOT (#355) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- function-maven-plugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index bbd0bfa6..a2e96114 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -10,7 +10,7 @@ com.google.cloud.functions function-maven-plugin maven-plugin - 0.11.2 + 0.11.3-SNAPSHOT Functions Framework Plugin A Maven plugin that allows functions to be deployed, and to be run locally using the Java Functions Framework. From c1f27d289e3b9da2ec936fb4d2197f42a2eaa983 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Wed, 22 Oct 2025 13:52:27 -0700 Subject: [PATCH 26/56] feat!: remove java11 support and expand java21 test coverage (#356) --- .github/workflows/buildpack-integration-test.yml | 6 +++--- .github/workflows/conformance.yaml | 3 ++- .github/workflows/lint.yaml | 2 +- .github/workflows/unit.yaml | 5 ++--- .kokoro/release.sh | 2 +- CONTRIBUTING.md | 8 ++++---- function-maven-plugin/pom.xml | 4 ++-- .../com/google/cloud/functions/plugin/DeployFunction.java | 6 +++--- .../google/cloud/functions/plugin/DeployFunctionTest.java | 2 +- functions-framework-api/pom.xml | 4 ++-- invoker/conformance/pom.xml | 4 ++-- invoker/core/pom.xml | 4 ++-- invoker/pom.xml | 4 ++-- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/buildpack-integration-test.yml b/.github/workflows/buildpack-integration-test.yml index 41182ce3..c97377e7 100644 --- a/.github/workflows/buildpack-integration-test.yml +++ b/.github/workflows/buildpack-integration-test.yml @@ -14,7 +14,7 @@ on: permissions: read-all jobs: - java11-buildpack-test: + java21-buildpack-test: uses: GoogleCloudPlatform/functions-framework-conformance/.github/workflows/buildpack-integration-test.yml@main with: http-builder-source: '/tmp/tests/conformance' @@ -22,8 +22,8 @@ jobs: cloudevent-builder-source: '/tmp/tests/conformance' cloudevent-builder-target: 'com.google.cloud.functions.conformance.CloudEventsConformanceFunction' prerun: 'invoker/conformance/prerun.sh' - builder-runtime: 'java11' - builder-runtime-version: '11' + builder-runtime: 'java21' + builder-runtime-version: '21' builder-url: gcr.io/serverless-runtimes/google-22-full/builder/java:latest java17-buildpack-test: uses: GoogleCloudPlatform/functions-framework-conformance/.github/workflows/buildpack-integration-test.yml@main diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 9b03a3c6..074d8c14 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -14,7 +14,8 @@ jobs: strategy: matrix: java: [ - 11.x, + 17.x, + 21.x ] steps: - name: Harden Runner diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6cc5a37b..3a5860de 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -24,7 +24,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: - java-version: 11.x + java-version: 17.x distribution: temurin - name: Build API with Maven run: (cd functions-framework-api/ && mvn install) diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 2826605a..2ddc0a5c 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -13,9 +13,8 @@ jobs: strategy: matrix: java: [ - 11.x, 17.x, - 21-ea + 21.x ] steps: - name: Harden Runner @@ -37,4 +36,4 @@ jobs: - name: Build with Maven run: (cd functions-framework-api/ && mvn install) - name: Test - run: (cd invoker/ && mvn test) \ No newline at end of file + run: (cd invoker/ && mvn test) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 8e1b30b0..89d7ebcd 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -64,7 +64,7 @@ fi echo "pwd=$(pwd)" # Make sure `JAVA_HOME` is set and using jdk11. -export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64 +export JAVA_HOME=/usr/lib/jvm/java-1.17.0-openjdk-amd64 echo "JAVA_HOME=$JAVA_HOME" mvn clean deploy -B -q \ -P sonatype-oss-release \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7e4114c..5d18935a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,15 +38,15 @@ This project is divided into multiple packages, primarily: - `function-maven-plugin` - The Maven plugin for building functions - `conformance` - A set of functions used for conformance testing -### Setup JDK 11 / 17 +### Setup JDK 17 / 21 -Install JDK 11 and 17. One way to install these is through [SDK man](https://sdkman.io/). +Install JDK 17 and 21. One way to install these is through [SDK man](https://sdkman.io/). ```sh -sdk install java 11.0.2-open sdk install java 17-open +sdk install java 21-open sdk use java 17-open -sdk use java 11.0.2-open +sdk use java 21-open ``` Verify Java version with: diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index a2e96114..657ce775 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -47,8 +47,8 @@ - 11 - 11 + 17 + 17 diff --git a/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java b/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java index d0067ded..08bff714 100644 --- a/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java +++ b/function-maven-plugin/src/main/java/com/google/cloud/functions/plugin/DeployFunction.java @@ -98,13 +98,13 @@ public class DeployFunction extends CloudSdkMojo { * Runtime in which to run the function. * *

Required when deploying a new function; optional when updating an existing function. Default - * to Java11. + * to Java17. */ @Parameter( alias = "deploy.runtime", - defaultValue = "java11", + defaultValue = "java17", property = "function.deploy.runtime") - String runtime = "java11"; + String runtime = "java17"; /** * The email address of the IAM service account associated with the function at runtime. The diff --git a/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java b/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java index 2b441d4e..6f107cff 100644 --- a/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java +++ b/function-maven-plugin/src/test/java/com/google/cloud/functions/plugin/DeployFunctionTest.java @@ -53,7 +53,7 @@ public void testDeployFunctionCommandLine() { "--env-vars-file=myfile", "--set-build-env-vars=env1=a,env2=b", "--build-env-vars-file=myfile2", - "--runtime=java11", + "--runtime=java17", "--quiet"); assertThat(mojo.getCommands()).isEqualTo(expected); } diff --git a/functions-framework-api/pom.xml b/functions-framework-api/pom.xml index e9994047..6f4ce316 100644 --- a/functions-framework-api/pom.xml +++ b/functions-framework-api/pom.xml @@ -77,8 +77,8 @@ maven-compiler-plugin ${maven-compiler-plugin.version} - 11 - 11 + 17 + 17 diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index dbb5bbc5..11a51c29 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -20,8 +20,8 @@ UTF-8 - 11 - 11 + 17 + 17 diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 05af393a..8108c526 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -20,8 +20,8 @@ UTF-8 5.3.2 - 11 - 11 + 17 + 17 4.0.1 diff --git a/invoker/pom.xml b/invoker/pom.xml index 40185fcf..c52aba99 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -56,8 +56,8 @@ UTF-8 3.8.1 - 11 - 11 + 17 + 17 From 2297f3eb74a461bc226a60c69bf48d33debae73f Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Wed, 22 Oct 2025 14:57:52 -0700 Subject: [PATCH 27/56] chore: fix kokoro java17 setup (#360) --- .kokoro/release.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 89d7ebcd..0d7c8b26 100644 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -63,9 +63,14 @@ else fi echo "pwd=$(pwd)" -# Make sure `JAVA_HOME` is set and using jdk11. -export JAVA_HOME=/usr/lib/jvm/java-1.17.0-openjdk-amd64 +# Make sure `JAVA_HOME` is set and using jdk17. +JDK_VERSION=17 +apt-get update +# Install new JDK version +apt-get install -y openjdk-"${JDK_VERSION}"-jdk +export JAVA_HOME="$(update-java-alternatives -l | grep "1.${JDK_VERSION}" | head -n 1 | tr -s " " | cut -d " " -f 3)" echo "JAVA_HOME=$JAVA_HOME" + mvn clean deploy -B -q \ -P sonatype-oss-release \ --settings=../settings.xml \ From e23f98f2dc7cbcdbd036a46423c99f82bddd80bc Mon Sep 17 00:00:00 2001 From: Lachlan Date: Thu, 6 Nov 2025 10:26:57 +1100 Subject: [PATCH 28/56] chore(implementation): use Jetty-12.1 core without servlets (#333) * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts * chore(implementation)!: use Jetty-12 core without servlets Port the invoker to upgrade to Eclipse Jetty-12 version 12. Specifically using the new core APIs of Eclipse Jetty-12 that allow the overhead of a Servlet container to be avoided. BREAKING CHANGE: use Java 17 or above, as required by Eclipse Jetty-12. Signed-off-by: Lachlan Roberts --------- Signed-off-by: Lachlan Roberts --- invoker/core/pom.xml | 18 +- .../invoker/BackgroundFunctionExecutor.java | 60 ++++-- .../invoker/HttpFunctionExecutor.java | 24 ++- .../invoker/TypedFunctionExecutor.java | 25 ++- .../invoker/gcf/ExecutionIdUtil.java | 8 +- .../invoker/http/HttpRequestImpl.java | 146 +++++++------ .../invoker/http/HttpResponseImpl.java | 199 ++++++++++++++---- .../functions/invoker/http/HttpUtil.java | 32 +++ .../functions/invoker/http/TimeoutFilter.java | 71 ------- .../invoker/http/TimeoutHandler.java | 82 ++++++++ .../functions/invoker/runner/Invoker.java | 119 +++++------ .../functions/invoker/IntegrationTest.java | 129 +++++++++--- .../functions/invoker/http/HttpTest.java | 148 +++++++------ .../invoker/testfunctions/BufferedWrites.java | 27 +++ 14 files changed, 702 insertions(+), 386 deletions(-) create mode 100644 invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpUtil.java delete mode 100644 invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutFilter.java create mode 100644 invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutHandler.java create mode 100644 invoker/core/src/test/java/com/google/cloud/functions/invoker/testfunctions/BufferedWrites.java diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index 8108c526..d1a94d3d 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -23,6 +23,7 @@ 17 17 4.0.1 + 12.1.3 @@ -46,11 +47,6 @@ functions-framework-api 1.1.4 - - javax.servlet - javax.servlet-api - 4.0.1 - io.cloudevents cloudevents-core @@ -97,13 +93,13 @@ org.eclipse.jetty - jetty-servlet - 9.4.58.v20250814 + jetty-server + ${jetty.version} - org.eclipse.jetty - jetty-server - 9.4.58.v20250814 + org.slf4j + slf4j-jdk14 + 2.0.9 com.beust @@ -151,7 +147,7 @@ org.eclipse.jetty jetty-client - 9.4.58.v20250814 + ${jetty.version} test diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java index 331c9586..097b9a67 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java @@ -30,25 +30,32 @@ import io.cloudevents.http.HttpMessageFactory; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; /** Executes the user's background function. */ -public final class BackgroundFunctionExecutor extends HttpServlet { +public final class BackgroundFunctionExecutor extends Handler.Abstract { private static final Logger logger = Logger.getLogger("com.google.cloud.functions.invoker"); private final FunctionExecutor functionExecutor; @@ -177,8 +184,13 @@ static Optional backgroundFunctionTypeArgument( .findFirst(); } - private static Event parseLegacyEvent(HttpServletRequest req) throws IOException { - try (BufferedReader bodyReader = req.getReader()) { + private static Event parseLegacyEvent(Request req) throws IOException { + try (BufferedReader bodyReader = + new BufferedReader( + new InputStreamReader( + Content.Source.asInputStream(req), + Objects.requireNonNullElse( + Request.getCharset(req), StandardCharsets.ISO_8859_1)))) { return parseLegacyEvent(bodyReader); } } @@ -225,7 +237,7 @@ private static Context contextFromCloudEvent(CloudEvent cloudEvent) { * for the various triggers. CloudEvents are ones that follow the standards defined by cloudevents.io. * - * @param the type to be used in the {@link Unmarshallers} call when + * @param the type to be used in the {code Unmarshallers} call when * unmarshalling this event, if it is a CloudEvent. */ private abstract static class FunctionExecutor { @@ -322,23 +334,25 @@ void serviceCloudEvent(CloudEvent cloudEvent) throws Exception { /** Executes the user's background function. This can handle all HTTP methods. */ @Override - public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { - String contentType = req.getContentType(); + public boolean handle(Request req, Response res, Callback callback) throws Exception { + String contentType = req.getHeaders().get(HttpHeader.CONTENT_TYPE); try { executionIdUtil.storeExecutionId(req); if ((contentType != null && contentType.startsWith("application/cloudevents+json")) - || req.getHeader("ce-specversion") != null) { + || req.getHeaders().get("ce-specversion") != null) { serviceCloudEvent(req); } else { serviceLegacyEvent(req); } - res.setStatus(HttpServletResponse.SC_OK); + res.setStatus(HttpStatus.OK_200); + callback.succeeded(); } catch (Throwable t) { - res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); logger.log(Level.SEVERE, "Failed to execute " + functionExecutor.functionName(), t); + Response.writeError(req, res, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, null); } finally { executionIdUtil.removeExecutionId(); } + return true; } private enum CloudEventKind { @@ -352,10 +366,14 @@ private enum CloudEventKind { * @param a fake type parameter, which corresponds to the type parameter of {@link * FunctionExecutor}. */ - private void serviceCloudEvent(HttpServletRequest req) throws Exception { + private void serviceCloudEvent(Request req) throws Exception { @SuppressWarnings("unchecked") FunctionExecutor executor = (FunctionExecutor) functionExecutor; - byte[] body = req.getInputStream().readAllBytes(); + + // Read the entire request body into a byte array. + // TODO: this method is deprecated for removal, use the method introduced by + // https://github.com/jetty/jetty.project/pull/13939 when it is released. + byte[] body = Content.Source.asByteArrayAsync(req, -1).get(); MessageReader reader = HttpMessageFactory.createReaderFromMultimap(headerMap(req), body); // It's important not to set the context ClassLoader earlier, because MessageUtils will use // ServiceLoader.load(EventFormat.class) to find a handler to deserialize a binary CloudEvent @@ -369,17 +387,17 @@ private void serviceCloudEvent(HttpServletRequest req) throws Exce // https://github.com/cloudevents/sdk-java/pull/259. } - private static Map> headerMap(HttpServletRequest req) { + private static Map> headerMap(Request req) { Map> headerMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (String header : Collections.list(req.getHeaderNames())) { - for (String value : Collections.list(req.getHeaders(header))) { - headerMap.computeIfAbsent(header, unused -> new ArrayList<>()).add(value); - } + for (HttpField field : req.getHeaders()) { + headerMap + .computeIfAbsent(field.getName(), unused -> new ArrayList<>()) + .addAll(field.getValueList()); } return headerMap; } - private void serviceLegacyEvent(HttpServletRequest req) throws Exception { + private void serviceLegacyEvent(Request req) throws Exception { Event event = parseLegacyEvent(req); runWithContextClassLoader(() -> functionExecutor.serviceLegacyEvent(event)); } diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/HttpFunctionExecutor.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/HttpFunctionExecutor.java index 01f07e74..b414f110 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/HttpFunctionExecutor.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/HttpFunctionExecutor.java @@ -20,12 +20,14 @@ import com.google.cloud.functions.invoker.http.HttpResponseImpl; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; /** Executes the user's method. */ -public class HttpFunctionExecutor extends HttpServlet { +public class HttpFunctionExecutor extends Handler.Abstract { private static final Logger logger = Logger.getLogger("com.google.cloud.functions.invoker"); private final HttpFunction function; @@ -65,21 +67,23 @@ public static HttpFunctionExecutor forClass(Class functionClass) { /** Executes the user's method, can handle all HTTP type methods. */ @Override - public void service(HttpServletRequest req, HttpServletResponse res) { - HttpRequestImpl reqImpl = new HttpRequestImpl(req); - HttpResponseImpl respImpl = new HttpResponseImpl(res); + public boolean handle(Request request, Response response, Callback callback) throws Exception { + + HttpRequestImpl reqImpl = new HttpRequestImpl(request); + HttpResponseImpl respImpl = new HttpResponseImpl(response); ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader(); try { - executionIdUtil.storeExecutionId(req); + executionIdUtil.storeExecutionId(request); Thread.currentThread().setContextClassLoader(function.getClass().getClassLoader()); function.service(reqImpl, respImpl); + respImpl.close(callback); } catch (Throwable t) { logger.log(Level.SEVERE, "Failed to execute " + function.getClass().getName(), t); - res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, null); } finally { Thread.currentThread().setContextClassLoader(oldContextLoader); executionIdUtil.removeExecutionId(); - respImpl.flush(); } + return true; } } diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/TypedFunctionExecutor.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/TypedFunctionExecutor.java index a6edfc32..63418705 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/TypedFunctionExecutor.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/TypedFunctionExecutor.java @@ -15,11 +15,13 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; -public class TypedFunctionExecutor extends HttpServlet { +public class TypedFunctionExecutor extends Handler.Abstract { private static final String APPLY_METHOD = "apply"; private static final Logger logger = Logger.getLogger("com.google.cloud.functions.invoker"); @@ -94,7 +96,7 @@ static Optional handlerTypeArgument(Class> f /** Executes the user's method, can handle all HTTP type methods. */ @Override - public void service(HttpServletRequest req, HttpServletResponse res) { + public boolean handle(Request req, Response res, Callback callback) throws Exception { HttpRequestImpl reqImpl = new HttpRequestImpl(req); HttpResponseImpl resImpl = new HttpResponseImpl(res); ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); @@ -102,10 +104,13 @@ public void service(HttpServletRequest req, HttpServletResponse res) { try { Thread.currentThread().setContextClassLoader(function.getClass().getClassLoader()); handleRequest(reqImpl, resImpl); + resImpl.close(callback); + } catch (Throwable t) { + Response.writeError(req, res, callback, HttpStatus.INTERNAL_SERVER_ERROR_500, null, t); } finally { Thread.currentThread().setContextClassLoader(oldContextClassLoader); - resImpl.flush(); } + return true; } private void handleRequest(HttpRequest req, HttpResponse res) { @@ -114,7 +119,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) { reqObj = format.deserialize(req, argType); } catch (Throwable t) { logger.log(Level.SEVERE, "Failed to parse request for " + function.getClass().getName(), t); - res.setStatusCode(HttpServletResponse.SC_BAD_REQUEST); + res.setStatusCode(HttpStatus.BAD_REQUEST_400); return; } @@ -123,7 +128,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) { resObj = function.apply(reqObj); } catch (Throwable t) { logger.log(Level.SEVERE, "Failed to execute " + function.getClass().getName(), t); - res.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + res.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); return; } @@ -132,7 +137,7 @@ private void handleRequest(HttpRequest req, HttpResponse res) { } catch (Throwable t) { logger.log( Level.SEVERE, "Failed to serialize response for " + function.getClass().getName(), t); - res.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + res.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); return; } } @@ -147,7 +152,7 @@ private static class GsonWireFormat implements TypedFunction.WireFormat { @Override public void serialize(Object object, HttpResponse response) throws Exception { if (object == null) { - response.setStatusCode(HttpServletResponse.SC_NO_CONTENT); + response.setStatusCode(HttpStatus.NO_CONTENT_204); return; } try (BufferedWriter bodyWriter = response.getWriter()) { diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/gcf/ExecutionIdUtil.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/gcf/ExecutionIdUtil.java index 7987317d..becf2c5c 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/gcf/ExecutionIdUtil.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/gcf/ExecutionIdUtil.java @@ -5,7 +5,7 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.logging.Handler; import java.util.logging.Logger; -import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Request; /** * A helper class that either fetches a unique execution id from request HTTP headers or generates a @@ -23,7 +23,7 @@ public final class ExecutionIdUtil { * Add mapping to root logger from current thread id to execution id. This mapping will be used to * append the execution id to log lines. */ - public void storeExecutionId(HttpServletRequest request) { + public void storeExecutionId(Request request) { if (!executionIdLoggingEnabled()) { return; } @@ -47,8 +47,8 @@ public void removeExecutionId() { } } - private String getOrGenerateExecutionId(HttpServletRequest request) { - String executionId = request.getHeader(EXECUTION_ID_HTTP_HEADER); + private String getOrGenerateExecutionId(Request request) { + String executionId = request.getHeaders().get(EXECUTION_ID_HTTP_HEADER); if (executionId == null) { byte[] array = new byte[EXECUTION_ID_LENGTH]; random.nextBytes(array); diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpRequestImpl.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpRequestImpl.java index 2119645a..31ee4ac6 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpRequestImpl.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpRequestImpl.java @@ -14,33 +14,33 @@ package com.google.cloud.functions.invoker.http; -import static java.util.stream.Collectors.toMap; - import com.google.cloud.functions.HttpRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.Part; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.MultiPart.Part; +import org.eclipse.jetty.http.MultiPartFormData; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.Fields; public class HttpRequestImpl implements HttpRequest { - private final HttpServletRequest request; + private final Request request; + private InputStream inputStream; + private BufferedReader reader; - public HttpRequestImpl(HttpServletRequest request) { + public HttpRequestImpl(Request request) { this.request = request; } @@ -51,133 +51,155 @@ public String getMethod() { @Override public String getUri() { - String url = request.getRequestURL().toString(); - if (request.getQueryString() != null) { - url += "?" + request.getQueryString(); - } - return url; + return request.getHttpURI().asString(); } @Override public String getPath() { - return request.getRequestURI(); + return request.getHttpURI().getCanonicalPath(); } @Override public Optional getQuery() { - return Optional.ofNullable(request.getQueryString()); + return Optional.ofNullable(request.getHttpURI().getQuery()); } @Override public Map> getQueryParameters() { - return request.getParameterMap().entrySet().stream() - .collect(toMap(Map.Entry::getKey, e -> Arrays.asList(e.getValue()))); + Fields fields = Request.extractQueryParameters(request); + if (fields.isEmpty()) { + return Collections.emptyMap(); + } + + Map> map = new HashMap<>(); + fields.forEach( + field -> map.put(field.getName(), Collections.unmodifiableList(field.getValues()))); + return Collections.unmodifiableMap(map); } @Override public Map getParts() { - String contentType = request.getContentType(); - if (contentType == null || !request.getContentType().startsWith("multipart/form-data")) { + String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); + if (contentType == null + || !contentType.startsWith(MimeTypes.Type.MULTIPART_FORM_DATA.asString())) { throw new IllegalStateException("Content-Type must be multipart/form-data: " + contentType); } - try { - return request.getParts().stream().collect(toMap(Part::getName, HttpPartImpl::new)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (ServletException e) { - throw new RuntimeException(e.getMessage(), e); + + // The multipart parsing is done by the EagerContentHandler, so we just call getParts. + MultiPartFormData.Parts parts = MultiPartFormData.getParts(request); + if (parts == null) { + throw new IllegalStateException(); + } + + if (parts.size() == 0) { + return Collections.emptyMap(); } + + Map map = new HashMap<>(); + parts.forEach(part -> map.put(part.getName(), new HttpPartImpl(part))); + return Collections.unmodifiableMap(map); } @Override public Optional getContentType() { - return Optional.ofNullable(request.getContentType()); + return Optional.ofNullable(request.getHeaders().get(HttpHeader.CONTENT_TYPE)); } @Override public long getContentLength() { - return request.getContentLength(); + return request.getLength(); } @Override public Optional getCharacterEncoding() { - return Optional.ofNullable(request.getCharacterEncoding()); + Charset charset = Request.getCharset(request); + return Optional.ofNullable(charset == null ? null : charset.name()); } @Override public InputStream getInputStream() throws IOException { - return request.getInputStream(); + if (reader != null) { + throw new IllegalStateException("getReader() already called"); + } + if (inputStream == null) { + inputStream = Content.Source.asInputStream(request); + } + return inputStream; } @Override public BufferedReader getReader() throws IOException { - return request.getReader(); + if (reader == null) { + if (inputStream != null) { + throw new IllegalStateException("getInputStream already called"); + } + inputStream = Content.Source.asInputStream(request); + reader = + new BufferedReader( + new InputStreamReader( + getInputStream(), + Objects.requireNonNullElse(Request.getCharset(request), StandardCharsets.UTF_8))); + } + return reader; } @Override public Map> getHeaders() { - return Collections.list(request.getHeaderNames()).stream() - .collect( - toMap( - name -> name, - name -> Collections.list(request.getHeaders(name)), - (a, b) -> b, - () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER))); + return HttpUtil.toStringListMap(request.getHeaders()); } private static class HttpPartImpl implements HttpPart { private final Part part; + private final String contentType; private HttpPartImpl(Part part) { this.part = part; + contentType = part.getHeaders().get(HttpHeader.CONTENT_TYPE); } @Override public Optional getFileName() { - return Optional.ofNullable(part.getSubmittedFileName()); + return Optional.ofNullable(part.getFileName()); } @Override public Optional getContentType() { - return Optional.ofNullable(part.getContentType()); + return Optional.ofNullable(contentType); } @Override public long getContentLength() { - return part.getSize(); + return part.getLength(); } @Override public Optional getCharacterEncoding() { - String contentType = getContentType().orElse(null); - if (contentType == null) { - return Optional.empty(); - } - Pattern charsetPattern = Pattern.compile("(?i).*;\\s*charset\\s*=([^;\\s]*)\\s*(;|$)"); - Matcher matcher = charsetPattern.matcher(contentType); - return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty(); + return Optional.ofNullable(MimeTypes.getCharsetFromContentType(contentType)); } @Override public InputStream getInputStream() throws IOException { - return part.getInputStream(); + Content.Source contentSource = part.createContentSource(); + return Content.Source.asInputStream(contentSource); } @Override public BufferedReader getReader() throws IOException { - String encoding = getCharacterEncoding().orElse("utf-8"); - return new BufferedReader(new InputStreamReader(getInputStream(), encoding)); + return new BufferedReader( + new InputStreamReader( + getInputStream(), + Objects.requireNonNullElse( + MimeTypes.DEFAULTS.getCharset(contentType), StandardCharsets.UTF_8))); } @Override public Map> getHeaders() { - return part.getHeaderNames().stream() - .map(name -> new SimpleEntry<>(name, list(part.getHeaders(name)))) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + return HttpUtil.toStringListMap(part.getHeaders()); } - private static List list(Collection collection) { - return (collection instanceof List) ? (List) collection : new ArrayList<>(collection); + @Override + public String toString() { + return "%s{%s}".formatted(super.toString(), part); } } } diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpResponseImpl.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpResponseImpl.java index c02246f0..5773de4b 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpResponseImpl.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpResponseImpl.java @@ -14,24 +14,33 @@ package com.google.cloud.functions.invoker.http; -import static java.util.stream.Collectors.toMap; - import com.google.cloud.functions.HttpResponse; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.TreeMap; -import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.WriteThroughWriter; +import org.eclipse.jetty.io.content.BufferedContentSink; +import org.eclipse.jetty.io.content.ContentSinkOutputStream; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; public class HttpResponseImpl implements HttpResponse { - private final HttpServletResponse response; + private final Response response; + private ContentSinkOutputStream outputStream; + private BufferedWriter writer; + private Charset charset; - public HttpResponseImpl(HttpServletResponse response) { + public HttpResponseImpl(Response response) { this.response = response; } @@ -43,75 +52,173 @@ public void setStatusCode(int code) { @Override @SuppressWarnings("deprecation") public void setStatusCode(int code, String message) { - response.setStatus(code, message); + response.setStatus(code); } @Override public void setContentType(String contentType) { - response.setContentType(contentType); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, contentType); + charset = response.getRequest().getContext().getMimeTypes().getCharset(contentType); } @Override public Optional getContentType() { - return Optional.ofNullable(response.getContentType()); + return Optional.ofNullable(response.getHeaders().get(HttpHeader.CONTENT_TYPE)); } @Override public void appendHeader(String key, String value) { - response.addHeader(key, value); + if (HttpHeader.CONTENT_TYPE.is(key)) { + setContentType(value); + } else { + response.getHeaders().add(key, value); + } } @Override public Map> getHeaders() { - return response.getHeaderNames().stream() - .collect( - toMap( - name -> name, - name -> new ArrayList<>(response.getHeaders(name)), - (a, b) -> b, - () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER))); - } - - private static List list(Collection collection) { - return (collection instanceof List) ? (List) collection : new ArrayList<>(collection); + return HttpUtil.toStringListMap(response.getHeaders()); } @Override - public OutputStream getOutputStream() throws IOException { - return response.getOutputStream(); + public OutputStream getOutputStream() { + if (writer != null) { + throw new IllegalStateException("getWriter called"); + } + if (outputStream == null) { + Request request = response.getRequest(); + int outputBufferSize = + request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(); + BufferedContentSink bufferedContentSink = + new BufferedContentSink( + response, + request.getComponents().getByteBufferPool(), + false, + outputBufferSize / 2, + outputBufferSize); + + // TODO: remove override of close() when changes from + // https://github.com/jetty/jetty.project/pull/13972 are released. + outputStream = + new ContentSinkOutputStream(bufferedContentSink) { + boolean closed = false; + + @Override + public void close(Callback callback) throws IOException { + if (closed) { + callback.succeeded(); + } + + closed = true; + super.close(callback); + } + }; + } + return outputStream; } - private BufferedWriter writer; - @Override public synchronized BufferedWriter getWriter() throws IOException { if (writer == null) { - // Unfortunately this means that we get two intermediate objects between the object we return - // and the underlying Writer that response.getWriter() wraps. We could try accessing the - // PrintWriter.out field via reflection, but that sort of access to non-public fields of - // platform classes is now frowned on and may draw warnings or even fail in subsequent - // versions. We could instead wrap the OutputStream, but that would require us to deduce the - // appropriate Charset, using logic like this: - // https://github.com/eclipse/jetty.project/blob/923ec38adf/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java#L731 - // We may end up doing that if performance is an issue. - writer = new BufferedWriter(response.getWriter()); + if (outputStream != null) { + throw new IllegalStateException("getOutputStream called"); + } + + writer = + new NonBufferedWriter( + WriteThroughWriter.newWriter( + getOutputStream(), Objects.requireNonNullElse(charset, StandardCharsets.UTF_8))); } return writer; } - public void flush() { + /** + * Close the response, flushing all content. + * + * @param callback a {@link Callback} to be completed when the response is closed. + */ + public void close(Callback callback) { try { - // We can't use HttpServletResponse.flushBuffer() because we wrap the - // PrintWriter returned by HttpServletResponse in our own BufferedWriter - // to match our API. So we have to flush whichever of getWriter() or - // getOutputStream() works. - try { - getOutputStream().flush(); - } catch (IllegalStateException e) { - getWriter().flush(); + // The writer has been constructed to do no buffering, so it does not need to be flushed + if (outputStream != null) { + // Do an asynchronous close, so large buffered content may be written without blocking + outputStream.close(callback); + } else { + callback.succeeded(); } } catch (IOException e) { - // Too bad, can't flush. + // Too bad, can't close. + } + } + + /** + * A {@link BufferedWriter} that does not buffer. It is generally more efficient to buffer at the + * {@link Content.Sink} level, since frequently total content is smaller than a single buffer and + * the {@link Content.Sink} can turn a close into a last write that will avoid chunking the + * response if at all possible. However, {@link BufferedWriter} is in the API for {@link + * HttpResponse}, so we must return a writer of that type. + */ + private static class NonBufferedWriter extends BufferedWriter { + private final Writer writer; + + public NonBufferedWriter(Writer out) { + super(out, 1); + writer = out; + } + + @Override + public void write(int c) throws IOException { + writer.write(c); + } + + @Override + public void write(char[] cbuf) throws IOException { + writer.write(cbuf); + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + writer.write(cbuf, off, len); + } + + @Override + public void write(String str) throws IOException { + writer.write(str); + } + + @Override + public void write(String str, int off, int len) throws IOException { + writer.write(str, off, len); + } + + @Override + public Writer append(CharSequence csq) throws IOException { + return writer.append(csq); + } + + @Override + public Writer append(CharSequence csq, int start, int end) throws IOException { + return writer.append(csq, start, end); + } + + @Override + public Writer append(char c) throws IOException { + return writer.append(c); + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.close(); + } + + @Override + public void newLine() throws IOException { + writer.write(System.lineSeparator()); } } } diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpUtil.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpUtil.java new file mode 100644 index 00000000..042af255 --- /dev/null +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/HttpUtil.java @@ -0,0 +1,32 @@ +// Copyright 2025 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. + +package com.google.cloud.functions.invoker.http; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; + +class HttpUtil { + public static Map> toStringListMap(HttpFields headers) { + Map> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (HttpField field : headers) { + map.computeIfAbsent(field.getName(), key -> new ArrayList<>()).add(field.getValue()); + } + return map; + } +} diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutFilter.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutFilter.java deleted file mode 100644 index e0577f9b..00000000 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -// 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. - -package com.google.cloud.functions.invoker.http; - -import java.io.IOException; -import java.util.Timer; -import java.util.TimerTask; -import java.util.logging.Logger; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; - -public class TimeoutFilter implements Filter { - - private static final Logger logger = Logger.getLogger(TimeoutFilter.class.getName()); - private final int timeoutMs; - - public TimeoutFilter(int timeoutSeconds) { - this.timeoutMs = timeoutSeconds * 1000; // Convert seconds to milliseconds - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - Timer timer = new Timer(true); - TimerTask timeoutTask = - new TimerTask() { - @Override - public void run() { - if (response instanceof HttpServletResponse) { - try { - ((HttpServletResponse) response) - .sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, "Request timed out"); - } catch (IOException e) { - logger.warning("Error while sending HTTP response: " + e.toString()); - } - } else { - try { - response.getWriter().write("Request timed out"); - } catch (IOException e) { - logger.warning("Error while writing response: " + e.toString()); - } - } - } - }; - - timer.schedule(timeoutTask, timeoutMs); - - try { - chain.doFilter(request, response); - timeoutTask.cancel(); - } finally { - timer.purge(); - } - } -} diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutHandler.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutHandler.java new file mode 100644 index 00000000..96295fd2 --- /dev/null +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/http/TimeoutHandler.java @@ -0,0 +1,82 @@ +// Copyright 2025 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. + +package com.google.cloud.functions.invoker.http; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.Scheduler; + +public class TimeoutHandler extends Handler.Wrapper { + private final Duration timeout; + + public TimeoutHandler(int timeoutSeconds, Handler handler) { + setHandler(handler); + timeout = Duration.ofSeconds(timeoutSeconds); + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + // Wrap the callback to ensure it is only completed once between the + // handler and the timeout task. + Callback wrappedCallback = new ProtectedCallback(callback); + Scheduler.Task timeoutTask = + request + .getComponents() + .getScheduler() + .schedule( + () -> + wrappedCallback.failed( + new BadMessageException( + HttpStatus.REQUEST_TIMEOUT_408, "Function execution timed out")), + timeout); + + // Cancel the timeout if the request completes the callback first. + return super.handle(request, response, Callback.from(timeoutTask::cancel, wrappedCallback)); + } + + private static class ProtectedCallback implements Callback { + private final Callback callback; + private final AtomicBoolean completed = new AtomicBoolean(false); + + public ProtectedCallback(Callback callback) { + this.callback = callback; + } + + @Override + public void succeeded() { + if (completed.compareAndSet(false, true)) { + callback.succeeded(); + } + } + + @Override + public void failed(Throwable x) { + if (completed.compareAndSet(false, true)) { + callback.failed(x); + } + } + + @Override + public InvocationType getInvocationType() { + return callback.getInvocationType(); + } + } +} diff --git a/invoker/core/src/main/java/com/google/cloud/functions/invoker/runner/Invoker.java b/invoker/core/src/main/java/com/google/cloud/functions/invoker/runner/Invoker.java index da5e72ec..20c3f248 100644 --- a/invoker/core/src/main/java/com/google/cloud/functions/invoker/runner/Invoker.java +++ b/invoker/core/src/main/java/com/google/cloud/functions/invoker/runner/Invoker.java @@ -25,7 +25,7 @@ import com.google.cloud.functions.invoker.HttpFunctionExecutor; import com.google.cloud.functions.invoker.TypedFunctionExecutor; import com.google.cloud.functions.invoker.gcf.JsonLogHandler; -import com.google.cloud.functions.invoker.http.TimeoutFilter; +import com.google.cloud.functions.invoker.http.TimeoutHandler; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; @@ -39,32 +39,25 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; -import javax.servlet.DispatcherType; -import javax.servlet.MultipartConfigElement; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.http.MultiPartConfig; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.server.handler.EagerContentHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.QueuedThreadPool; /** @@ -91,7 +84,7 @@ public class Invoker { // if we arrange for them to be formatted using StackDriver's "structured // logging" JSON format. Remove the JDK's standard logger and replace it with // the JSON one. - for (Handler handler : rootLogger.getHandlers()) { + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { rootLogger.removeHandler(handler); } rootLogger.addHandler(new JsonLogHandler(System.out, false)); @@ -242,7 +235,7 @@ ClassLoader getFunctionClassLoader() { * unit or integration test, use {@link #startTestServer()} instead. * * @see #stopServer() - * @throws Exception + * @throws Exception If there was a problem starting the server */ public void startServer() throws Exception { startServer(true); @@ -274,7 +267,7 @@ public void startServer() throws Exception { * } * * @see #stopServer() - * @throws Exception + * @throws Exception If there was a problem starting the server */ public void startTestServer() throws Exception { startServer(false); @@ -287,34 +280,46 @@ private void startServer(boolean join) throws Exception { QueuedThreadPool pool = new QueuedThreadPool(1024); server = new Server(pool); + server.setErrorHandler( + new ErrorHandler() { + @Override + protected void generateResponse( + Request request, + Response response, + int code, + String message, + Throwable cause, + Callback callback) { + // Suppress error body + callback.succeeded(); + } + }); ServerConnector connector = new ServerConnector(server); connector.setPort(port); - server.setConnectors(new Connector[] {connector}); - - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setContextPath("/"); - server.setHandler(NotFoundHandler.forServlet(servletContextHandler)); + connector.setReuseAddress(true); + connector.setReusePort(true); + server.addConnector(connector); Class functionClass = loadFunctionClass(); - HttpServlet servlet; + Handler handler; if (functionSignatureType == null) { - servlet = servletForDeducedSignatureType(functionClass); + handler = handlerForDeducedSignatureType(functionClass); } else { switch (functionSignatureType) { case "http": if (TypedFunction.class.isAssignableFrom(functionClass)) { - servlet = TypedFunctionExecutor.forClass(functionClass); + handler = TypedFunctionExecutor.forClass(functionClass); } else { - servlet = HttpFunctionExecutor.forClass(functionClass); + handler = HttpFunctionExecutor.forClass(functionClass); } break; case "event": case "cloudevent": - servlet = BackgroundFunctionExecutor.forClass(functionClass); + handler = BackgroundFunctionExecutor.forClass(functionClass); break; case "typed": - servlet = TypedFunctionExecutor.forClass(functionClass); + handler = TypedFunctionExecutor.forClass(functionClass); break; default: String error = @@ -325,10 +330,18 @@ private void startServer(boolean join) throws Exception { throw new RuntimeException(error); } } - ServletHolder servletHolder = new ServletHolder(servlet); - servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement("")); - servletContextHandler.addServlet(servletHolder, "/*"); - servletContextHandler = addTimerFilterForRequestTimeout(servletContextHandler); + + // Possibly wrap with TimeoutHandler if CLOUD_RUN_TIMEOUT_SECONDS is set. + handler = addTimerHandlerForRequestTimeout(handler); + server.setHandler(handler); + + // Add a handler to asynchronously parse multipart before invoking the function. + MultiPartConfig config = new MultiPartConfig.Builder().maxMemoryPartSize(-1).build(); + EagerContentHandler.MultiPartContentLoaderFactory factory = + new EagerContentHandler.MultiPartContentLoaderFactory(config); + server.insertHandler(new EagerContentHandler(factory)); + + server.insertHandler(new NotFoundHandler()); server.start(); logServerInfo(); @@ -376,7 +389,7 @@ private Class loadFunctionClass() throws ClassNotFoundException { } } - private HttpServlet servletForDeducedSignatureType(Class functionClass) { + private Handler handlerForDeducedSignatureType(Class functionClass) { if (HttpFunction.class.isAssignableFrom(functionClass)) { return HttpFunctionExecutor.forClass(functionClass); } @@ -398,16 +411,13 @@ private HttpServlet servletForDeducedSignatureType(Class functionClass) { throw new RuntimeException(error); } - private ServletContextHandler addTimerFilterForRequestTimeout( - ServletContextHandler servletContextHandler) { + private Handler addTimerHandlerForRequestTimeout(Handler handler) { String timeoutSeconds = System.getenv("CLOUD_RUN_TIMEOUT_SECONDS"); if (timeoutSeconds == null) { - return servletContextHandler; + return handler; } int seconds = Integer.parseInt(timeoutSeconds); - FilterHolder holder = new FilterHolder(new TimeoutFilter(seconds)); - servletContextHandler.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST)); - return servletContextHandler; + return new TimeoutHandler(seconds, handler); } static URL[] classpathToUrls(String classpath) { @@ -468,32 +478,24 @@ private static boolean isGcf() { /** * Wrapper that intercepts requests for {@code /favicon.ico} and {@code /robots.txt} and causes - * them to produce a 404 status. Otherwise they would be sent to the function code, like any other - * URL, meaning that someone testing their function by using a browser as an HTTP client can see - * two requests, one for {@code /favicon.ico} and one for {@code /} (or whatever). + * them to produce a 404 status. Otherwise, they would be sent to the function code, like any + * other URL, meaning that someone testing their function by using a browser as an HTTP client can + * see two requests, one for {@code /favicon.ico} and one for {@code /} (or whatever). */ - private static class NotFoundHandler extends HandlerWrapper { - static NotFoundHandler forServlet(ServletContextHandler servletHandler) { - NotFoundHandler handler = new NotFoundHandler(); - handler.setHandler(servletHandler); - return handler; - } + private static class NotFoundHandler extends Handler.Wrapper { private static final Set NOT_FOUND_PATHS = new HashSet<>(Arrays.asList("/favicon.ico", "/robots.txt")); @Override - public void handle( - String target, - Request baseRequest, - HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException { - if (NOT_FOUND_PATHS.contains(request.getRequestURI())) { - response.sendError(HttpStatus.NOT_FOUND_404, "Not Found"); - return; + public boolean handle(Request request, Response response, Callback callback) throws Exception { + if (NOT_FOUND_PATHS.contains(request.getHttpURI().getCanonicalPath())) { + response.setStatus(HttpStatus.NOT_FOUND_404); + callback.succeeded(); + return true; } - super.handle(target, baseRequest, request, response); + + return super.handle(request, response, callback); } } @@ -522,7 +524,6 @@ private static class OnlyApiClassLoader extends ClassLoader { protected Class findClass(String name) throws ClassNotFoundException { String prefix = "com.google.cloud.functions."; if ((name.startsWith(prefix) && Character.isUpperCase(name.charAt(prefix.length()))) - || name.startsWith("javax.servlet.") || isCloudEventsApiClass(name)) { return runtimeClassLoader.loadClass(name); } diff --git a/invoker/core/src/test/java/com/google/cloud/functions/invoker/IntegrationTest.java b/invoker/core/src/test/java/com/google/cloud/functions/invoker/IntegrationTest.java index 2f1d8bc8..d6e3b14a 100644 --- a/invoker/core/src/test/java/com/google/cloud/functions/invoker/IntegrationTest.java +++ b/invoker/core/src/test/java/com/google/cloud/functions/invoker/IntegrationTest.java @@ -47,6 +47,7 @@ import java.net.URI; import java.net.URL; import java.net.URLEncoder; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -66,16 +67,17 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; +import org.eclipse.jetty.client.ByteBufferRequestContent; +import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.MultiPartContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.MultiPartRequestContent; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MultiPart; +import org.eclipse.jetty.http.MultiPart.ContentSourcePart; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -91,7 +93,7 @@ public class IntegrationTest { @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @Rule public final TestName testName = new TestName(); - private static final String SERVER_READY_STRING = "Started ServerConnector"; + private static final String SERVER_READY_STRING = "Started oejs.ServerConnector"; private static final String EXECUTION_ID_HTTP_HEADER = "HTTP_FUNCTION_EXECUTION_ID"; private static final String EXECUTION_ID = "1234abcd"; @@ -167,10 +169,18 @@ abstract static class TestCase { abstract String url(); - abstract ContentProvider requestContent(); + abstract Request.Content requestContent(); abstract int expectedResponseCode(); + /** + * Expected response headers map, header name -> value. Value "*" asserts the header is present + * with any value. Value "-" asserts the header is not present. + * + * @return the expected response headers for this test case. + */ + abstract Optional> expectedResponseHeaders(); + abstract Optional expectedResponseText(); abstract Optional expectedJson(); @@ -190,6 +200,7 @@ static Builder builder() { .setUrl("/") .setRequestText("") .setExpectedResponseCode(HttpStatus.OK_200) + .setExpectedResponseHeaders(ImmutableMap.of()) .setExpectedResponseText("") .setHttpContentType("text/plain") .setHttpHeaders(ImmutableMap.of()); @@ -200,14 +211,16 @@ abstract static class Builder { abstract Builder setUrl(String x); - abstract Builder setRequestContent(ContentProvider x); + abstract Builder setRequestContent(Request.Content x); Builder setRequestText(String text) { - return setRequestContent(new StringContentProvider(text)); + return setRequestContent(new StringRequestContent(text)); } abstract Builder setExpectedResponseCode(int x); + abstract Builder setExpectedResponseHeaders(Map x); + abstract Builder setExpectedResponseText(String x); abstract Builder setExpectedResponseText(Optional x); @@ -253,7 +266,10 @@ public void helloWorld() throws Exception { testHttpFunction( fullTarget("HelloWorld"), ImmutableList.of( - TestCase.builder().setExpectedResponseText("hello\n").build(), + TestCase.builder() + .setExpectedResponseHeaders(ImmutableMap.of("Content-Length", "*")) + .setExpectedResponseText("hello\n") + .build(), FAVICON_TEST_CASE, ROBOTS_TXT_TEST_CASE)); } @@ -286,12 +302,42 @@ public void timeoutHttpTimesOut() throws Exception { ImmutableMap.of("CLOUD_RUN_TIMEOUT_SECONDS", "1")); } + @Test + public void bufferedWrites() throws Exception { + // This test checks that writes are buffered, and are written + // in an efficient way with known content-length if possible + // instead of doing a chunked response. + testHttpFunction( + fullTarget("BufferedWrites"), + ImmutableList.of( + TestCase.builder() + .setUrl("/target?writes=2") + .setExpectedResponseText("write 0\nwrite 1\n") + .setExpectedResponseHeaders( + ImmutableMap.of( + "x-write-0", "true", + "x-write-1", "true", + "x-written", "true", + "Content-Length", "16")) + .build(), + TestCase.builder() + .setUrl("/target?writes=2&flush=true") + .setExpectedResponseText("write 0\nwrite 1\n") + .setExpectedResponseHeaders( + ImmutableMap.of( + "x-write-0", "true", + "x-write-1", "true", + "x-written", "-", + "Transfer-Encoding", "chunked")) + .build())); + } + @Test public void exceptionHttp() throws Exception { String exceptionExpectedOutput = "\"severity\": \"ERROR\", \"logging.googleapis.com/sourceLocation\": {\"file\":" + " \"com/google/cloud/functions/invoker/HttpFunctionExecutor.java\", \"method\":" - + " \"service\"}, \"execution_id\": \"" + + " \"handle\"}, \"execution_id\": \"" + EXECUTION_ID + "\"," + " \"message\": \"Failed to execute" @@ -302,6 +348,7 @@ public void exceptionHttp() throws Exception { ImmutableList.of( TestCase.builder() .setExpectedResponseCode(500) + .setExpectedResponseText("") .setHttpHeaders(ImmutableMap.of(EXECUTION_ID_HTTP_HEADER, EXECUTION_ID)) .setExpectedOutput(exceptionExpectedOutput) .build())); @@ -312,7 +359,7 @@ public void exceptionBackground() throws Exception { String exceptionExpectedOutput = "\"severity\": \"ERROR\", \"logging.googleapis.com/sourceLocation\": {\"file\":" + " \"com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java\", \"method\":" - + " \"service\"}, \"execution_id\": \"" + + " \"handle\"}, \"execution_id\": \"" + EXECUTION_ID + "\", " + "\"message\": \"Failed to execute" @@ -583,7 +630,7 @@ public void nativeCloudEventException() throws Exception { String exceptionExpectedOutput = "\"severity\": \"ERROR\", \"logging.googleapis.com/sourceLocation\": {\"file\":" + " \"com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java\", \"method\":" - + " \"service\"}, \"execution_id\": \"" + + " \"handle\"}, \"execution_id\": \"" + EXECUTION_ID + "\", " + "\"message\": \"Failed to execute" @@ -632,11 +679,16 @@ public void packageless() throws Exception { @Test public void multipart() throws Exception { - MultiPartContentProvider multiPartProvider = new MultiPartContentProvider(); + MultiPartRequestContent multiPartRequestContent = new MultiPartRequestContent(); byte[] bytes = new byte[17]; - multiPartProvider.addFieldPart("bytes", new BytesContentProvider(bytes), new HttpFields()); - String string = "1234567890"; - multiPartProvider.addFieldPart("string", new StringContentProvider(string), new HttpFields()); + multiPartRequestContent.addPart( + new ContentSourcePart( + "bytes", null, HttpFields.EMPTY, new ByteBufferRequestContent(ByteBuffer.wrap(bytes)))); + multiPartRequestContent.addPart( + new MultiPart.ContentSourcePart( + "string", null, HttpFields.EMPTY, new StringRequestContent("1234567890"))); + multiPartRequestContent.close(); + String expectedResponse = "part bytes type application/octet-stream length 17\n" + "part string type text/plain;charset=UTF-8 length 10\n"; @@ -644,8 +696,8 @@ public void multipart() throws Exception { fullTarget("Multipart"), ImmutableList.of( TestCase.builder() - .setHttpContentType(Optional.empty()) - .setRequestContent(multiPartProvider) + .setHttpContentType(multiPartRequestContent.getContentType()) + .setRequestContent(multiPartRequestContent) .setExpectedResponseText(expectedResponse) .build())); } @@ -782,23 +834,47 @@ private void testFunction( throws Exception { ServerProcess serverProcess = startServer(signatureType, target, extraArgs, environmentVariables); + HttpClient httpClient = new HttpClient(); try { - HttpClient httpClient = new HttpClient(); httpClient.start(); for (TestCase testCase : testCases) { testCase.snoopFile().ifPresent(File::delete); String uri = "http://localhost:" + serverPort + testCase.url(); Request request = httpClient.POST(uri); - testCase - .httpContentType() - .ifPresent(contentType -> request.header(HttpHeader.CONTENT_TYPE, contentType)); - testCase.httpHeaders().forEach((header, value) -> request.header(header, value)); - request.content(testCase.requestContent()); + + request.headers( + headers -> { + testCase + .httpContentType() + .ifPresent(contentType -> headers.put(HttpHeader.CONTENT_TYPE, contentType)); + testCase.httpHeaders().forEach(headers::put); + }); + request.body(testCase.requestContent()); ContentResponse response = request.send(); expect .withMessage("Response to %s is %s %s", uri, response.getStatus(), response.getReason()) .that(response.getStatus()) .isEqualTo(testCase.expectedResponseCode()); + testCase + .expectedResponseHeaders() + .ifPresent( + expectedResponseHeaders -> { + for (Map.Entry entry : expectedResponseHeaders.entrySet()) { + if ("*".equals(entry.getValue())) { + expect + .that(response.getHeaders().getFieldNamesCollection()) + .contains(entry.getKey()); + } else if ("-".equals(entry.getValue())) { + expect + .that(response.getHeaders().getFieldNamesCollection()) + .doesNotContain(entry.getKey()); + } else { + expect + .that(response.getHeaders().getValuesList(entry.getKey())) + .contains(entry.getValue()); + } + } + }); testCase .expectedResponseText() .ifPresent(text -> expect.that(response.getContentAsString()).isEqualTo(text)); @@ -811,6 +887,7 @@ private void testFunction( } } finally { serverProcess.close(); + httpClient.stop(); } for (TestCase testCase : testCases) { testCase diff --git a/invoker/core/src/test/java/com/google/cloud/functions/invoker/http/HttpTest.java b/invoker/core/src/test/java/com/google/cloud/functions/invoker/http/HttpTest.java index e0ca4675..a9794cd2 100644 --- a/invoker/core/src/test/java/com/google/cloud/functions/invoker/http/HttpTest.java +++ b/invoker/core/src/test/java/com/google/cloud/functions/invoker/http/HttpTest.java @@ -27,6 +27,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ServerSocket; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -34,22 +35,23 @@ import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import javax.servlet.MultipartConfigElement; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.ByteBufferRequestContent; +import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.BytesContentProvider; -import org.eclipse.jetty.client.util.MultiPartContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.client.MultiPartRequestContent; +import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpStatus.Code; +import org.eclipse.jetty.http.MultiPart; +import org.eclipse.jetty.http.MultiPartConfig; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.server.handler.EagerContentHandler; +import org.eclipse.jetty.util.Callback; import org.junit.BeforeClass; import org.junit.Test; @@ -81,21 +83,22 @@ public static void allocateServerPort() throws IOException { } /** - * Wrapper class that allows us to start a Jetty server with a single servlet for {@code /*} - * within a try-with-resources statement. The servlet will be configured to support multipart + * Wrapper class that allows us to start a Jetty server with a single handler for {@code /*} + * within a try-with-resources statement. The handler will be configured to support multipart * requests. */ private static class SimpleServer implements AutoCloseable { private final Server server; - SimpleServer(HttpServlet servlet) throws Exception { + SimpleServer(Handler handler) throws Exception { this.server = new Server(serverPort); - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server.setHandler(context); - ServletHolder servletHolder = new ServletHolder(servlet); - servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement("tiddly")); - context.addServlet(servletHolder, "/*"); + server.setHandler(handler); + + MultiPartConfig config = new MultiPartConfig.Builder().maxMemoryPartSize(-1).build(); + EagerContentHandler.MultiPartContentLoaderFactory factory = + new EagerContentHandler.MultiPartContentLoaderFactory(config); + server.insertHandler(new EagerContentHandler(factory)); + server.start(); } @@ -112,16 +115,16 @@ private interface HttpRequestTest { /** * Tests methods on the {@link HttpRequest} object while the request is being serviced. We are not - * guaranteed that the underlying {@link HttpServletRequest} object will still be valid when the - * request completes, and in fact in Jetty it isn't. So we perform the checks in the context of - * the servlet, and report any exception back to the test method. + * guaranteed that the underlying {@link Request} object will still be valid when the request + * completes, and in fact in Jetty it isn't. So we perform the checks in the context of the + * handler, and report any exception back to the test method. */ @Test public void httpRequestMethods() throws Exception { AtomicReference testReference = new AtomicReference<>(); AtomicReference exceptionReference = new AtomicReference<>(); - HttpRequestServlet testServlet = new HttpRequestServlet(testReference, exceptionReference); - try (SimpleServer server = new SimpleServer(testServlet)) { + HttpRequestHandler testHandler = new HttpRequestHandler(testReference, exceptionReference); + try (SimpleServer server = new SimpleServer(testHandler)) { httpRequestMethods(testReference, exceptionReference); } } @@ -192,14 +195,17 @@ private void httpRequestMethods( }; for (HttpRequestTest test : tests) { testReference.set(test); - Request request = + org.eclipse.jetty.client.Request request = httpClient .POST(uri) - .header(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8") - .header("foo", "bar") - .header("foo", "baz") - .header("CaSe-SeNsItIvE", "VaLuE") - .content(new StringContentProvider(TEST_BODY)); + .headers( + m -> { + m.add(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8"); + m.add("foo", "bar"); + m.add("foo", "baz"); + m.add("CaSe-SeNsItIvE", "VaLuE"); + }) + .body(new StringRequestContent(TEST_BODY)); ContentResponse response = request.send(); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK_200); throwIfNotNull(exceptionReference.get()); @@ -222,8 +228,8 @@ public void emptyRequest() throws Exception { }; AtomicReference exceptionReference = new AtomicReference<>(); AtomicReference testReference = new AtomicReference<>(test); - HttpRequestServlet testServlet = new HttpRequestServlet(testReference, exceptionReference); - try (SimpleServer server = new SimpleServer(testServlet)) { + HttpRequestHandler testHandler = new HttpRequestHandler(testReference, exceptionReference); + try (SimpleServer server = new SimpleServer(testHandler)) { ContentResponse response = httpClient.POST(uri).send(); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK_200); throwIfNotNull(exceptionReference.get()); @@ -239,20 +245,24 @@ private void validateReader(BufferedReader reader) { public void multiPartRequest() throws Exception { AtomicReference testReference = new AtomicReference<>(); AtomicReference exceptionReference = new AtomicReference<>(); - HttpRequestServlet testServlet = new HttpRequestServlet(testReference, exceptionReference); + HttpRequestHandler testHandler = new HttpRequestHandler(testReference, exceptionReference); HttpClient httpClient = new HttpClient(); httpClient.start(); String uri = "http://localhost:" + serverPort + "/"; - MultiPartContentProvider multiPart = new MultiPartContentProvider(); - HttpFields textHttpFields = new HttpFields(); - textHttpFields.add("foo", "bar"); - multiPart.addFieldPart("text", new StringContentProvider(TEST_BODY), textHttpFields); - HttpFields bytesHttpFields = new HttpFields(); - bytesHttpFields.add("foo", "baz"); - bytesHttpFields.add("foo", "buh"); + MultiPartRequestContent multiPart = new MultiPartRequestContent(); + HttpFields textHttpFields = HttpFields.build().add("foo", "bar"); + multiPart.addPart( + new MultiPart.ContentSourcePart( + "text", null, textHttpFields, new StringRequestContent(TEST_BODY))); + HttpFields.Mutable bytesHttpFields = HttpFields.build().add("foo", "baz").add("foo", "buh"); assertThat(bytesHttpFields.getValuesList("foo")).containsExactly("baz", "buh"); - multiPart.addFilePart( - "binary", "/tmp/binary.x", new BytesContentProvider(RANDOM_BYTES), bytesHttpFields); + multiPart.addPart( + new MultiPart.ContentSourcePart( + "binary", + "/tmp/binary.x", + bytesHttpFields, + new ByteBufferRequestContent(ByteBuffer.wrap(RANDOM_BYTES)))); + multiPart.close(); HttpRequestTest test = request -> { // The Content-Type header will also have a boundary=something attribute. @@ -271,10 +281,9 @@ public void multiPartRequest() throws Exception { assertThat(bytesPart.getFileName()).hasValue("/tmp/binary.x"); assertThat(bytesPart.getContentLength()).isEqualTo(RANDOM_BYTES.length); assertThat(bytesPart.getContentType()).hasValue("application/octet-stream"); - // We only see ["buh"] here, not ["baz", "buh"], apparently due to a Jetty bug. - // Repeated headers on multi-part content are not a big problem anyway. List foos = bytesPart.getHeaders().get("foo"); - assertThat(foos).contains("buh"); + assertThat(foos).containsExactly("baz", "buh"); + byte[] bytes = new byte[RANDOM_BYTES.length]; try (InputStream inputStream = bytesPart.getInputStream()) { assertThat(inputStream.read(bytes)).isEqualTo(bytes.length); @@ -282,20 +291,21 @@ public void multiPartRequest() throws Exception { assertThat(bytes).isEqualTo(RANDOM_BYTES); } }; - try (SimpleServer server = new SimpleServer(testServlet)) { + try (SimpleServer server = new SimpleServer(testHandler)) { testReference.set(test); - Request request = httpClient.POST(uri).header("foo", "oof").content(multiPart); + org.eclipse.jetty.client.Request request = + httpClient.POST(uri).headers(m -> m.put("foo", "oof")).body(multiPart); ContentResponse response = request.send(); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK_200); throwIfNotNull(exceptionReference.get()); } } - private static class HttpRequestServlet extends HttpServlet { + private static class HttpRequestHandler extends Handler.Abstract { private final AtomicReference testReference; private final AtomicReference exceptionReference; - private HttpRequestServlet( + private HttpRequestHandler( AtomicReference testReference, AtomicReference exceptionReference) { this.testReference = testReference; @@ -303,12 +313,15 @@ private HttpRequestServlet( } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + public boolean handle(Request request, Response response, Callback callback) { try { - testReference.get().test(new HttpRequestImpl(req)); + testReference.get().test(new HttpRequestImpl(request)); } catch (Throwable t) { exceptionReference.set(t); + Response.writeError(request, response, callback, t); } + callback.succeeded(); + return true; } } @@ -326,8 +339,8 @@ private interface HttpResponseTest { public void httpResponseSetAndGet() throws Exception { AtomicReference testReference = new AtomicReference<>(); AtomicReference exceptionReference = new AtomicReference<>(); - HttpResponseServlet testServlet = new HttpResponseServlet(testReference, exceptionReference); - try (SimpleServer server = new SimpleServer(testServlet)) { + HttpResponseHandler testHandler = new HttpResponseHandler(testReference, exceptionReference); + try (SimpleServer server = new SimpleServer(testHandler)) { httpResponseSetAndGet(testReference, exceptionReference); } } @@ -349,8 +362,7 @@ private void httpResponseSetAndGet( .containsAtLeast("Content-Type", Arrays.asList("application/octet-stream")); }, response -> { - Map> initialHeaders = response.getHeaders(); - // The servlet spec says this should be empty, but actually we get a Date header here. + // The fields are initialized with a Date header as per the HTTP RFCs. // So we just check that we can add our own headers. response.appendHeader("foo", "bar"); response.appendHeader("wibbly", "wobbly"); @@ -365,18 +377,18 @@ private void httpResponseSetAndGet( HttpClient httpClient = new HttpClient(); httpClient.start(); String uri = "http://localhost:" + serverPort; - Request request = httpClient.POST(uri); + org.eclipse.jetty.client.Request request = httpClient.POST(uri); ContentResponse response = request.send(); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK_200); throwIfNotNull(exceptionReference.get()); } } - private static class HttpResponseServlet extends HttpServlet { + private static class HttpResponseHandler extends Handler.Abstract { private final AtomicReference testReference; private final AtomicReference exceptionReference; - private HttpResponseServlet( + private HttpResponseHandler( AtomicReference testReference, AtomicReference exceptionReference) { this.testReference = testReference; @@ -384,12 +396,15 @@ private HttpResponseServlet( } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + public boolean handle(Request request, Response response, Callback callback) { try { - testReference.get().test(new HttpResponseImpl(resp)); + testReference.get().test(new HttpResponseImpl(response)); + callback.succeeded(); } catch (Throwable t) { exceptionReference.set(t); + Response.writeError(request, response, callback, t); } + return true; } } @@ -416,15 +431,15 @@ private static ResponseTest responseTest( /** * Tests that operations on the {@link HttpResponse} have the appropriate effect on the HTTP * response that ends up being sent. Here, for each check, we have two operations: the operation - * on the {@link HttpResponse}, which happens inside the servlet, and the operation to check the + * on the {@link HttpResponse}, which happens inside the handler, and the operation to check the * HTTP result, which happens in the client thread. */ @Test public void httpResponseEffects() throws Exception { AtomicReference testReference = new AtomicReference<>(); AtomicReference exceptionReference = new AtomicReference<>(); - HttpResponseServlet testServlet = new HttpResponseServlet(testReference, exceptionReference); - try (SimpleServer server = new SimpleServer(testServlet)) { + HttpResponseHandler testHandler = new HttpResponseHandler(testReference, exceptionReference); + try (SimpleServer server = new SimpleServer(testHandler)) { httpResponseEffects(testReference, exceptionReference); } } @@ -447,7 +462,8 @@ private void httpResponseEffects( response -> response.setStatusCode(HttpStatus.IM_A_TEAPOT_418, "Je suis une théière"), response -> { assertThat(response.getStatus()).isEqualTo(HttpStatus.IM_A_TEAPOT_418); - assertThat(response.getReason()).isEqualTo("Je suis une théière"); + // Reason string cannot be set by the application. + assertThat(response.getReason()).isEqualTo(Code.IM_A_TEAPOT.getMessage()); }), responseTest( response -> response.setContentType("application/noddy"), @@ -490,7 +506,7 @@ private void httpResponseEffects( HttpClient httpClient = new HttpClient(); httpClient.start(); String uri = "http://localhost:" + serverPort; - Request request = httpClient.POST(uri); + org.eclipse.jetty.client.Request request = httpClient.POST(uri); ContentResponse response = request.send(); throwIfNotNull(exceptionReference.get()); test.responseCheck.test(response); diff --git a/invoker/core/src/test/java/com/google/cloud/functions/invoker/testfunctions/BufferedWrites.java b/invoker/core/src/test/java/com/google/cloud/functions/invoker/testfunctions/BufferedWrites.java new file mode 100644 index 00000000..a7989a74 --- /dev/null +++ b/invoker/core/src/test/java/com/google/cloud/functions/invoker/testfunctions/BufferedWrites.java @@ -0,0 +1,27 @@ +package com.google.cloud.functions.invoker.testfunctions; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import java.io.BufferedWriter; +import java.util.List; +import java.util.Map; + +public class BufferedWrites implements HttpFunction { + @Override + public void service(HttpRequest request, HttpResponse response) throws Exception { + Map> queryParameters = request.getQueryParameters(); + int writes = Integer.parseInt(request.getFirstQueryParameter("writes").orElse("0")); + boolean flush = Boolean.parseBoolean(request.getFirstQueryParameter("flush").orElse("false")); + + BufferedWriter writer = response.getWriter(); + for (int i = 0; i < writes; i++) { + response.appendHeader("x-write-" + i, "true"); + writer.write("write " + i + "\n"); + } + if (flush) { + writer.flush(); + } + response.appendHeader("x-written", "true"); + } +} From 3fb1c02a3dbb7773b306e3877167980afe907bc4 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 5 Nov 2025 23:32:46 +0000 Subject: [PATCH 29/56] chore(deps): update actions/setup-java action to v5 (#363) Co-authored-by: Andras Kerekes --- .github/workflows/conformance.yaml | 2 +- .github/workflows/lint.yaml | 4 ++-- .github/workflows/unit.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 074d8c14..08529f77 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: ${{ matrix.java }} distribution: temurin diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3a5860de..4457b8fa 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -22,7 +22,7 @@ jobs: repo.maven.apache.org:443 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: 17.x distribution: temurin @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # v2 minimum required - name: Set up JDK - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: 21.x distribution: temurin diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 2ddc0a5c..d6b8e3c6 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -29,7 +29,7 @@ jobs: *.githubusercontent.com:443 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: ${{ matrix.java }} distribution: temurin From 1ff16ba426c4605c4a23313bae111e6ccc071736 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 5 Nov 2025 23:38:04 +0000 Subject: [PATCH 30/56] chore(deps): update all non-major dependencies (#362) Co-authored-by: Andras Kerekes --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/conformance.yaml | 2 +- .github/workflows/lint.yaml | 4 ++-- .github/workflows/scorecard.yml | 4 ++-- .github/workflows/unit.yaml | 2 +- function-maven-plugin/pom.xml | 6 +++--- invoker/conformance/pom.xml | 2 +- invoker/core/pom.xml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 32d7e54e..ef1c713e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: disable-sudo: true egress-policy: block @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 + uses: github/codeql-action/init@5d5cd550d3e189c569da8f16ea8de2d821c9bf7a # v3.31.2 with: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support languages: java @@ -65,6 +65,6 @@ jobs: (cd function-maven-plugin && mvn install) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 + uses: github/codeql-action/analyze@5d5cd550d3e189c569da8f16ea8de2d821c9bf7a # v3.31.2 with: category: ${{ matrix.working-directory }} diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index 08529f77..cbc4fb6b 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -19,7 +19,7 @@ jobs: ] steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: disable-sudo: true egress-policy: block diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4457b8fa..b492004f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: disable-sudo: true egress-policy: block @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a6b8e986..1fab1ae1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: disable-sudo: true egress-policy: block @@ -62,6 +62,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 + uses: github/codeql-action/upload-sarif@5d5cd550d3e189c569da8f16ea8de2d821c9bf7a # v3.31.2 with: sarif_file: results.sarif diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index d6b8e3c6..0468a01f 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -18,7 +18,7 @@ jobs: ] steps: - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: disable-sudo: true egress-policy: block diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index 657ce775..9a841b16 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -68,7 +68,7 @@ org.apache.maven.plugin-tools maven-plugin-annotations - 3.15.1 + 3.15.2 provided @@ -81,7 +81,7 @@ com.google.cloud.tools appengine-maven-plugin - 2.8.3 + 2.8.4 jar @@ -104,7 +104,7 @@ org.apache.maven.plugins maven-plugin-plugin - 3.15.1 + 3.15.2 help-goal diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index 11a51c29..8a1af674 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -53,7 +53,7 @@ com.google.cloud.functions function-maven-plugin - 0.11.1 + 0.11.2 diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index d1a94d3d..a6d2fe13 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -99,7 +99,7 @@ org.slf4j slf4j-jdk14 - 2.0.9 + 2.0.17 com.beust From a868a62a803bf9c1ff450295838db98a1e873d19 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:04:57 -0800 Subject: [PATCH 31/56] chore(main): release functions-framework-api 2.0.0 (#357) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Andras Kerekes --- .github/.release-please-manifest.json | 2 +- functions-framework-api/CHANGELOG.md | 11 +++++++++++ functions-framework-api/pom.xml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 9bc86bf6..4a932202 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1 +1 @@ -{"functions-framework-api":"1.1.4","invoker":"1.4.3","function-maven-plugin":"0.11.2"} +{"functions-framework-api":"2.0.0","invoker":"1.4.3","function-maven-plugin":"0.11.2"} diff --git a/functions-framework-api/CHANGELOG.md b/functions-framework-api/CHANGELOG.md index b97246fe..f588d527 100644 --- a/functions-framework-api/CHANGELOG.md +++ b/functions-framework-api/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [2.0.0](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/functions-framework-api-v1.1.4...functions-framework-api-v2.0.0) (2025-11-05) + + +### ⚠ BREAKING CHANGES + +* remove java11 support and expand java21 test coverage ([#356](https://github.com/GoogleCloudPlatform/functions-framework-java/issues/356)) + +### Features + +* remove java11 support and expand java21 test coverage ([#356](https://github.com/GoogleCloudPlatform/functions-framework-java/issues/356)) ([c1f27d2](https://github.com/GoogleCloudPlatform/functions-framework-java/commit/c1f27d289e3b9da2ec936fb4d2197f42a2eaa983)) + ## [1.1.4](https://github.com/GoogleCloudPlatform/functions-framework-java/compare/functions-framework-api-v1.1.3...functions-framework-api-v1.1.4) (2024-11-22) diff --git a/functions-framework-api/pom.xml b/functions-framework-api/pom.xml index 6f4ce316..06fb4f2a 100644 --- a/functions-framework-api/pom.xml +++ b/functions-framework-api/pom.xml @@ -24,7 +24,7 @@ com.google.cloud.functions functions-framework-api - 1.1.5-SNAPSHOT + 2.0.0 UTF-8 From daa0cc51f6e79c552dfde785d3519526d6cd4041 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Wed, 5 Nov 2025 16:48:43 -0800 Subject: [PATCH 32/56] chore: fix project metadata in poms (#366) --- function-maven-plugin/pom.xml | 2 +- functions-framework-api/pom.xml | 3 +++ invoker/conformance/pom.xml | 2 +- invoker/core/pom.xml | 1 + invoker/pom.xml | 2 +- invoker/testfunction/pom.xml | 3 ++- 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/function-maven-plugin/pom.xml b/function-maven-plugin/pom.xml index 9a841b16..8c8036d9 100644 --- a/function-maven-plugin/pom.xml +++ b/function-maven-plugin/pom.xml @@ -14,7 +14,7 @@ Functions Framework Plugin A Maven plugin that allows functions to be deployed, and to be run locally using the Java Functions Framework. - http://maven.apache.org + https://github.com/GoogleCloudPlatform/functions-framework-java http://github.com/GoogleCloudPlatform/functions-framework-java diff --git a/functions-framework-api/pom.xml b/functions-framework-api/pom.xml index 06fb4f2a..aceb7a79 100644 --- a/functions-framework-api/pom.xml +++ b/functions-framework-api/pom.xml @@ -25,6 +25,9 @@ com.google.cloud.functions functions-framework-api 2.0.0 + Functions Framework Java API + An open source FaaS (Function as a service) framework for writing portable Java functions. + https://github.com/GoogleCloudPlatform/functions-framework-java UTF-8 diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index 8a1af674..5e537771 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -16,7 +16,7 @@ A GCF project used to validate conformance to the Functions Framework contract using the Functions Framework Conformance tools. - https://github.com/GoogleCloudPlatform/functions-framework-conformance + https://github.com/GoogleCloudPlatform/functions-framework-java UTF-8 diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index a6d2fe13..e21b61f3 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -16,6 +16,7 @@ complete HTTP server that interprets incoming HTTP requests appropriately and forwards them to the function code. + https://github.com/GoogleCloudPlatform/functions-framework-java UTF-8 diff --git a/invoker/pom.xml b/invoker/pom.xml index c52aba99..9d936ef8 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -15,7 +15,7 @@ Parent POM for the GCF Java Invoker. The project is structured like this so that we can have modules that build jar files for use in tests. - https://github.com/GoogleCloudPlatform/functions-framework-java/tree/main/invoker + https://github.com/GoogleCloudPlatform/functions-framework-java http://github.com/GoogleCloudPlatform/functions-framework-java diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index 4ce12501..fad3df07 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -14,6 +14,7 @@ An example of a GCF function packaged into a jar. We use this in tests. + https://github.com/GoogleCloudPlatform/functions-framework-java @@ -93,4 +94,4 @@ - \ No newline at end of file + From f351c1ade94a08bc4116b40e2d343e1b5d9a6db6 Mon Sep 17 00:00:00 2001 From: Andras Kerekes Date: Wed, 5 Nov 2025 17:05:25 -0800 Subject: [PATCH 33/56] chore!: update functions-framework-api dependency to 2.0.0 (#365) --- invoker/conformance/pom.xml | 2 +- invoker/core/pom.xml | 2 +- invoker/pom.xml | 2 +- invoker/testfunction/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invoker/conformance/pom.xml b/invoker/conformance/pom.xml index 5e537771..e5f39c82 100644 --- a/invoker/conformance/pom.xml +++ b/invoker/conformance/pom.xml @@ -28,7 +28,7 @@ com.google.cloud.functions functions-framework-api - 1.1.4 + 2.0.0 com.google.code.gson diff --git a/invoker/core/pom.xml b/invoker/core/pom.xml index e21b61f3..7fa923a8 100644 --- a/invoker/core/pom.xml +++ b/invoker/core/pom.xml @@ -46,7 +46,7 @@ com.google.cloud.functions functions-framework-api - 1.1.4 + 2.0.0 io.cloudevents diff --git a/invoker/pom.xml b/invoker/pom.xml index 9d936ef8..b449fa4c 100644 --- a/invoker/pom.xml +++ b/invoker/pom.xml @@ -65,7 +65,7 @@ com.google.cloud.functions functions-framework-api - 1.1.4 + 2.0.0 diff --git a/invoker/testfunction/pom.xml b/invoker/testfunction/pom.xml index fad3df07..a0613c87 100644 --- a/invoker/testfunction/pom.xml +++ b/invoker/testfunction/pom.xml @@ -20,7 +20,7 @@ com.google.cloud.functions functions-framework-api - 1.1.4 + 2.0.0 + + airlock-mirror + Airlock Maven Central mirror + https://us-maven.pkg.dev/artifact-foundry-prod/maven-3p-trusted + * + + + + + + airlock-mirror + oauth2accesstoken + ${MAVEN_TOKEN} + + + + exit-gate-ar + oauth2accesstoken + ${MAVEN_TOKEN} + + + +EOF + +# ============================================================================== +# 2. Retrieve GPG keys from Secret Manager +# ============================================================================== +GPG_KEYRING="${KOKORO_ARTIFACTS_DIR}/gpg-keyring" +GPG_PASSPHRASE_FILE="${KOKORO_ARTIFACTS_DIR}/gpg-passphrase" + +# Read names from environment variables injected by Louhi +PROJECT_ID="${_LOUHI_SECRET_PROJECT_ID}" +KEYRING_NAME="${_LOUHI_GPG_KEYRING_SECRET_NAME}" +PASSPHRASE_NAME="${_LOUHI_GPG_PASSPHRASE_SECRET_NAME}" + +echo "Fetching secrets from project: ${PROJECT_ID}" +gcloud secrets versions access latest --secret="${KEYRING_NAME}" --project="${PROJECT_ID}" > "${GPG_KEYRING}" +gcloud secrets versions access latest --secret="${PASSPHRASE_NAME}" --project="${PROJECT_ID}" > "${GPG_PASSPHRASE_FILE}" + +export GPG_TTY=$(tty) +export GPG_PASSPHRASE=$(cat "${GPG_PASSPHRASE_FILE}") +export GNUPGHOME=/tmp/gpg +mkdir -p "${GNUPGHOME}" +gpg --batch --import "${GPG_KEYRING}" + +# ============================================================================== +# 3. Build, Sign, and Deploy +# ============================================================================== +# Detect which package to build based on the Louhi trigger tag +if [[ -n "${_LOUHI_REF_NAME:-}" ]]; then + echo "Triggered by Louhi tag: ${_LOUHI_REF_NAME}" + if [[ "${_LOUHI_REF_NAME}" == *functions-framework-api* ]]; then + PACKAGE_DIR="functions-framework-api" + elif [[ "${_LOUHI_REF_NAME}" == *function-maven-plugin* ]]; then + PACKAGE_DIR="function-maven-plugin" + elif [[ "${_LOUHI_REF_NAME}" == *java-function-invoker* ]]; then + PACKAGE_DIR="invoker" + else + echo "Unknown tag format: ${_LOUHI_REF_NAME}. Defaulting to invoker." + PACKAGE_DIR="invoker" + fi +else + # Fallback for manual/non-tag builds (e.g. testing) + echo "No Louhi tag detected. Falling back to KOKORO_JOB_NAME detection." + if [[ $KOKORO_JOB_NAME == *"function-maven-plugin"* ]]; then + PACKAGE_DIR="function-maven-plugin" + elif [[ $KOKORO_JOB_NAME == *"functions-framework-api"* ]]; then + PACKAGE_DIR="functions-framework-api" + else + PACKAGE_DIR="invoker" + fi +fi + +echo "Building package in directory: ${PACKAGE_DIR}" +cd "${PACKAGE_DIR}" + +# Run maven deploy using the temporary settings.xml +# We use altDeploymentRepository to override the deploy target without editing pom.xml +mvn clean deploy -B \ + -P sonatype-oss-release \ + --settings=../settings.xml \ + -DaltDeploymentRepository=exit-gate-ar::https://us-maven.pkg.dev/oss-exit-gate-prod/ff-releases--mavencentral \ + -Dgpg.executable=gpg \ + -Dgpg.passphrase="${GPG_PASSPHRASE}" \ + -Dgpg.homedir="${GNUPGHOME}" + +# ============================================================================== +# 4. Copy artifacts to 'artifacts/' folder for Kokoro Attestation Generation +# ============================================================================== +ARTIFACTS_DIR="${REPO_DIR}/artifacts" +mkdir -p "${ARTIFACTS_DIR}" + +# Copy target jars and poms (excluding test jars) to be captured by build.cfg +find target/ -maxdepth 1 -name "*.jar" -o -name "*.pom" | grep -v "test" | xargs -I {} cp {} "${ARTIFACTS_DIR}/" diff --git a/.kokoro/release.cfg b/.kokoro/release.cfg index 08d0ac9f..c617e165 100644 --- a/.kokoro/release.cfg +++ b/.kokoro/release.cfg @@ -1,30 +1,23 @@ +# -*- protobuffer -*- +# proto-file: google3/devtools/kokoro/config/proto/build.proto +# proto-message: BuildConfig + build_file: "functions-framework-java/.kokoro/release.sh" +container_properties { + docker_image: "us-docker.pkg.dev/artifact-foundry-prod/docker-3p-trusted/ubuntu:22.04" +} -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 75669 - keyname: "functions-framework-java-release-bot-sonatype-password" - } - keystore_resource { - keystore_config_id: 75669 - keyname: "functions-framework-release-sonatype-central-portal-username" - } - keystore_resource { - keystore_config_id: 75669 - keyname: "functions-framework-release-sonatype-central-portal-password" - } - keystore_resource { - keystore_config_id: 70247 - keyname: "maven-gpg-pubkeyring" - } - keystore_resource { - keystore_config_id: 70247 - keyname: "maven-gpg-keyring" - } - keystore_resource { - keystore_config_id: 70247 - keyname: "maven-gpg-passphrase" +fileset_artifacts { + name: "manifest" + artifact_globs: "manifest.json" + error_if_missing: true + destinations { + store_attestation: false + gcs { + gcs_root_path: "oss-exit-gate-prod-projects-bucket/ff-releases/mavencentral/manifests" + populate_content_type: true } } + generate_sbom_from_fileset: false + generate_attestation: false } diff --git a/.kokoro/release.sh b/.kokoro/release.sh old mode 100644 new mode 100755 index b85e6003..58b865a6 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -1,84 +1,10 @@ #!/bin/bash +set -euo pipefail -# Stop execution when any command fails. -set -e +cd "${KOKORO_ARTIFACTS_DIR}" -# update the Maven version to 3.9.11 -pushd /usr/local -wget https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.tar.gz -tar -xvzf apache-maven-3.9.11-bin.tar.gz apache-maven-3.9.11 -rm -f /usr/local/apache-maven -ln -s /usr/local/apache-maven-3.9.11 /usr/local/apache-maven -rm apache-maven-3.9.11-bin.tar.gz -popd - - -# Get secrets from keystore and set and environment variables. -setup_environment_secrets() { - export GPG_TTY=$(tty) - export GPG_PASSPHRASE=$(cat ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-passphrase) - - # Add the key ring files to $GNUPGHOME to verify the GPG credentials. - export GNUPGHOME=/tmp/gpg - mkdir $GNUPGHOME - mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-pubkeyring $GNUPGHOME/pubring.gpg - mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-keyring $GNUPGHOME/secring.gpg - gpg -k -} - -create_settings_xml_file() { - echo " - - - - true - - - ${GPG_PASSPHRASE} - - - - - - sonatype-central-portal - $(cat "${KOKORO_KEYSTORE_DIR}/75669_functions-framework-release-sonatype-central-portal-username") - $(cat "${KOKORO_KEYSTORE_DIR}/75669_functions-framework-release-sonatype-central-portal-password") - - -" > $1 +cat > manifest.json <<'EOF' +{ + "publish_all": true } - -setup_environment_secrets - -# Pick the right package to release based on the Kokoro job name. -cd ${KOKORO_ARTIFACTS_DIR}/github/functions-framework-java -create_settings_xml_file "settings.xml" -echo "KOKORO_JOB_NAME=${KOKORO_JOB_NAME}" -if [[ $KOKORO_JOB_NAME == *"function-maven-plugin"* ]]; then - cd function-maven-plugin -elif [[ $KOKORO_JOB_NAME == *"functions-framework-api"* ]]; then - cd functions-framework-api -else - cd invoker -fi -echo "pwd=$(pwd)" - -# Make sure `JAVA_HOME` is set and using jdk17. -JDK_VERSION=17 -apt-get update -# Install new JDK version -apt-get install -y openjdk-"${JDK_VERSION}"-jdk -export JAVA_HOME="$(update-java-alternatives -l | grep "1.${JDK_VERSION}" | head -n 1 | tr -s " " | cut -d " " -f 3)" -echo "JAVA_HOME=$JAVA_HOME" - -SUPPRESS_LOGS='-q' -if [[ -n "${ENABLE_LOGS}" ]]; then - SUPPRESS_LOGS='' -fi - -mvn clean deploy -B ${SUPPRESS_LOGS} \ - -P sonatype-oss-release \ - --settings=../settings.xml \ - -Dgpg.executable=gpg \ - -Dgpg.passphrase=${GPG_PASSPHRASE} \ - -Dgpg.homedir=${GNUPGHOME} +EOF