From 62ebefacaf91615bcb4a291f15bf00b20faa3ffc Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:47:15 -0500 Subject: [PATCH 01/11] chore(main): release 2.33.1-SNAPSHOT (#2415) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .cloudbuild/cloudbuild-test-a.yaml | 2 +- .cloudbuild/cloudbuild-test-b.yaml | 2 +- .cloudbuild/cloudbuild.yaml | 2 +- WORKSPACE | 2 +- api-common-java/pom.xml | 4 +-- coverage-report/pom.xml | 8 ++--- gapic-generator-java-bom/pom.xml | 26 +++++++-------- gapic-generator-java-pom-parent/pom.xml | 2 +- gapic-generator-java/pom.xml | 6 ++-- gax-java/dependencies.properties | 8 ++--- gax-java/gax-bom/pom.xml | 20 ++++++------ gax-java/gax-grpc/pom.xml | 4 +-- gax-java/gax-httpjson/pom.xml | 4 +-- gax-java/gax/pom.xml | 4 +-- gax-java/pom.xml | 14 ++++---- .../grpc-google-common-protos/pom.xml | 4 +-- java-common-protos/pom.xml | 10 +++--- .../proto-google-common-protos/pom.xml | 4 +-- java-core/google-cloud-core-bom/pom.xml | 10 +++--- java-core/google-cloud-core-grpc/pom.xml | 4 +-- java-core/google-cloud-core-http/pom.xml | 4 +-- java-core/google-cloud-core/pom.xml | 4 +-- java-core/pom.xml | 6 ++-- java-iam/grpc-google-iam-v1/pom.xml | 4 +-- java-iam/grpc-google-iam-v2/pom.xml | 4 +-- java-iam/grpc-google-iam-v2beta/pom.xml | 4 +-- java-iam/pom.xml | 22 ++++++------- java-iam/proto-google-iam-v1/pom.xml | 4 +-- java-iam/proto-google-iam-v2/pom.xml | 4 +-- java-iam/proto-google-iam-v2beta/pom.xml | 4 +-- .../dependency-convergence-check/pom.xml | 2 +- .../first-party-dependencies/pom.xml | 10 +++--- java-shared-dependencies/pom.xml | 8 ++--- .../third-party-dependencies/pom.xml | 2 +- .../upper-bound-check/pom.xml | 4 +-- sdk-platform-java-config/pom.xml | 4 +-- showcase/pom.xml | 2 +- versions.txt | 32 +++++++++---------- 38 files changed, 132 insertions(+), 132 deletions(-) diff --git a/.cloudbuild/cloudbuild-test-a.yaml b/.cloudbuild/cloudbuild-test-a.yaml index 6be1ab2a77..ce86b57f64 100644 --- a/.cloudbuild/cloudbuild-test-a.yaml +++ b/.cloudbuild/cloudbuild-test-a.yaml @@ -14,7 +14,7 @@ timeout: 7200s # 2 hours substitutions: - _SHARED_DEPENDENCIES_VERSION: '3.23.0' # {x-version-update:google-cloud-shared-dependencies:current} + _SHARED_DEPENDENCIES_VERSION: '3.23.1-SNAPSHOT' # {x-version-update:google-cloud-shared-dependencies:current} _JAVA_SHARED_CONFIG_VERSION: '1.7.1' steps: diff --git a/.cloudbuild/cloudbuild-test-b.yaml b/.cloudbuild/cloudbuild-test-b.yaml index ce809694e1..2962473493 100644 --- a/.cloudbuild/cloudbuild-test-b.yaml +++ b/.cloudbuild/cloudbuild-test-b.yaml @@ -14,7 +14,7 @@ timeout: 7200s # 2 hours substitutions: - _SHARED_DEPENDENCIES_VERSION: '3.23.0' # {x-version-update:google-cloud-shared-dependencies:current} + _SHARED_DEPENDENCIES_VERSION: '3.23.1-SNAPSHOT' # {x-version-update:google-cloud-shared-dependencies:current} _JAVA_SHARED_CONFIG_VERSION: '1.7.1' steps: diff --git a/.cloudbuild/cloudbuild.yaml b/.cloudbuild/cloudbuild.yaml index 24f823d83a..bdb68a394e 100644 --- a/.cloudbuild/cloudbuild.yaml +++ b/.cloudbuild/cloudbuild.yaml @@ -14,7 +14,7 @@ timeout: 7200s # 2 hours substitutions: - _SHARED_DEPENDENCIES_VERSION: '3.23.0' # {x-version-update:google-cloud-shared-dependencies:current} + _SHARED_DEPENDENCIES_VERSION: '3.23.1-SNAPSHOT' # {x-version-update:google-cloud-shared-dependencies:current} _JAVA_SHARED_CONFIG_VERSION: '1.7.1' steps: # GraalVM A build diff --git a/WORKSPACE b/WORKSPACE index df66454427..9587d18a8e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -60,7 +60,7 @@ maven_install( repositories = ["https://repo.maven.apache.org/maven2/"], ) -_gapic_generator_java_version = "2.33.0" # {x-version-update:gapic-generator-java:current} +_gapic_generator_java_version = "2.33.1-SNAPSHOT" # {x-version-update:gapic-generator-java:current} maven_install( artifacts = [ diff --git a/api-common-java/pom.xml b/api-common-java/pom.xml index 559e4bc7b3..1e0aa6ea2e 100644 --- a/api-common-java/pom.xml +++ b/api-common-java/pom.xml @@ -5,14 +5,14 @@ com.google.api api-common jar - 2.24.0 + 2.24.1-SNAPSHOT API Common Common utilities for Google APIs in Java com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index 4536cb6cce..1a329e354a 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -31,22 +31,22 @@ com.google.api gax - 2.41.0 + 2.41.1-SNAPSHOT com.google.api gax-grpc - 2.41.0 + 2.41.1-SNAPSHOT com.google.api gax-httpjson - 2.41.0 + 2.41.1-SNAPSHOT com.google.api api-common - 2.24.0 + 2.24.1-SNAPSHOT diff --git a/gapic-generator-java-bom/pom.xml b/gapic-generator-java-bom/pom.xml index 5f4c620562..f803972c4f 100644 --- a/gapic-generator-java-bom/pom.xml +++ b/gapic-generator-java-bom/pom.xml @@ -4,7 +4,7 @@ com.google.api gapic-generator-java-bom pom - 2.33.0 + 2.33.1-SNAPSHOT GAPIC Generator Java BOM BOM for the libraries in gapic-generator-java repository. Users should not @@ -15,7 +15,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -75,61 +75,61 @@ com.google.api api-common - 2.24.0 + 2.24.1-SNAPSHOT com.google.api gax-bom - 2.41.0 + 2.41.1-SNAPSHOT pom import com.google.api gapic-generator-java - 2.33.0 + 2.33.1-SNAPSHOT com.google.api.grpc grpc-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT com.google.api.grpc proto-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT com.google.api.grpc proto-google-iam-v1 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc proto-google-iam-v2 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc proto-google-iam-v2beta - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc grpc-google-iam-v1 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc grpc-google-iam-v2 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc grpc-google-iam-v2beta - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/gapic-generator-java-pom-parent/pom.xml b/gapic-generator-java-pom-parent/pom.xml index 7e120c3f3b..98607a685e 100644 --- a/gapic-generator-java-pom-parent/pom.xml +++ b/gapic-generator-java-pom-parent/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT pom GAPIC Generator Java POM Parent https://github.com/googleapis/sdk-platform-java diff --git a/gapic-generator-java/pom.xml b/gapic-generator-java/pom.xml index 7800c7fc16..74bc39c81e 100644 --- a/gapic-generator-java/pom.xml +++ b/gapic-generator-java/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.google.api gapic-generator-java - 2.33.0 + 2.33.1-SNAPSHOT GAPIC Generator Java GAPIC generator Java @@ -22,7 +22,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -31,7 +31,7 @@ com.google.api gapic-generator-java-bom - 2.33.0 + 2.33.1-SNAPSHOT pom import diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index fdf72a7dde..46145b1551 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -8,16 +8,16 @@ # Versions of oneself # {x-version-update-start:gax:current} -version.gax=2.41.0 +version.gax=2.41.1-SNAPSHOT # {x-version-update-end} # {x-version-update-start:gax:current} -version.gax_grpc=2.41.0 +version.gax_grpc=2.41.1-SNAPSHOT # {x-version-update-end} # {x-version-update-start:gax:current} -version.gax_bom=2.41.0 +version.gax_bom=2.41.1-SNAPSHOT # {x-version-update-end} # {x-version-update-start:gax:current} -version.gax_httpjson=2.41.0 +version.gax_httpjson=2.41.1-SNAPSHOT # {x-version-update-end} # Versions for dependencies which actual artifacts differ between Bazel and Gradle. diff --git a/gax-java/gax-bom/pom.xml b/gax-java/gax-bom/pom.xml index aa7a444187..0f9c05eaa2 100644 --- a/gax-java/gax-bom/pom.xml +++ b/gax-java/gax-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.api gax-bom - 2.41.0 + 2.41.1-SNAPSHOT pom GAX (Google Api eXtensions) for Java (BOM) Google Api eXtensions for Java (BOM) @@ -43,55 +43,55 @@ com.google.api gax - 2.41.0 + 2.41.1-SNAPSHOT com.google.api gax - 2.41.0 + 2.41.1-SNAPSHOT test-jar testlib com.google.api gax - 2.41.0 + 2.41.1-SNAPSHOT testlib com.google.api gax-grpc - 2.41.0 + 2.41.1-SNAPSHOT com.google.api gax-grpc - 2.41.0 + 2.41.1-SNAPSHOT test-jar testlib com.google.api gax-grpc - 2.41.0 + 2.41.1-SNAPSHOT testlib com.google.api gax-httpjson - 2.41.0 + 2.41.1-SNAPSHOT com.google.api gax-httpjson - 2.41.0 + 2.41.1-SNAPSHOT test-jar testlib com.google.api gax-httpjson - 2.41.0 + 2.41.1-SNAPSHOT testlib diff --git a/gax-java/gax-grpc/pom.xml b/gax-java/gax-grpc/pom.xml index b26a6daffa..655bed36bd 100644 --- a/gax-java/gax-grpc/pom.xml +++ b/gax-java/gax-grpc/pom.xml @@ -3,7 +3,7 @@ 4.0.0 gax-grpc - 2.41.0 + 2.41.1-SNAPSHOT jar GAX (Google Api eXtensions) for Java (gRPC) Google Api eXtensions for Java (gRPC) @@ -11,7 +11,7 @@ com.google.api gax-parent - 2.41.0 + 2.41.1-SNAPSHOT diff --git a/gax-java/gax-httpjson/pom.xml b/gax-java/gax-httpjson/pom.xml index b37d2332b9..28ed2c25c8 100644 --- a/gax-java/gax-httpjson/pom.xml +++ b/gax-java/gax-httpjson/pom.xml @@ -3,7 +3,7 @@ 4.0.0 gax-httpjson - 2.41.0 + 2.41.1-SNAPSHOT jar GAX (Google Api eXtensions) for Java (HTTP JSON) Google Api eXtensions for Java (HTTP JSON) @@ -11,7 +11,7 @@ com.google.api gax-parent - 2.41.0 + 2.41.1-SNAPSHOT diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index f860ed0efa..6473de3e20 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -3,7 +3,7 @@ 4.0.0 gax - 2.41.0 + 2.41.1-SNAPSHOT jar GAX (Google Api eXtensions) for Java (Core) Google Api eXtensions for Java (Core) @@ -11,7 +11,7 @@ com.google.api gax-parent - 2.41.0 + 2.41.1-SNAPSHOT diff --git a/gax-java/pom.xml b/gax-java/pom.xml index f231ab56ff..fad3ac4f99 100644 --- a/gax-java/pom.xml +++ b/gax-java/pom.xml @@ -4,14 +4,14 @@ com.google.api gax-parent pom - 2.41.0 + 2.41.1-SNAPSHOT GAX (Google Api eXtensions) for Java (Parent) Google Api eXtensions for Java (Parent) com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -50,7 +50,7 @@ com.google.api api-common - 2.24.0 + 2.24.1-SNAPSHOT com.google.auth @@ -108,24 +108,24 @@ com.google.api gax - 2.41.0 + 2.41.1-SNAPSHOT com.google.api gax - 2.41.0 + 2.41.1-SNAPSHOT test-jar testlib com.google.api.grpc proto-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT com.google.api.grpc grpc-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT io.grpc diff --git a/java-common-protos/grpc-google-common-protos/pom.xml b/java-common-protos/grpc-google-common-protos/pom.xml index 05b056bb71..a139d17528 100644 --- a/java-common-protos/grpc-google-common-protos/pom.xml +++ b/java-common-protos/grpc-google-common-protos/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT grpc-google-common-protos GRPC library for grpc-google-common-protos com.google.api.grpc google-common-protos-parent - 2.32.0 + 2.32.1-SNAPSHOT diff --git a/java-common-protos/pom.xml b/java-common-protos/pom.xml index 619ef36cc3..148b528c50 100644 --- a/java-common-protos/pom.xml +++ b/java-common-protos/pom.xml @@ -4,7 +4,7 @@ com.google.api.grpc google-common-protos-parent pom - 2.32.0 + 2.32.1-SNAPSHOT Google Common Protos Parent Java idiomatic client for Google Cloud Platform services. @@ -13,7 +13,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -61,7 +61,7 @@ com.google.cloud third-party-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import @@ -75,7 +75,7 @@ com.google.api.grpc grpc-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT io.grpc @@ -87,7 +87,7 @@ com.google.api.grpc proto-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT com.google.guava diff --git a/java-common-protos/proto-google-common-protos/pom.xml b/java-common-protos/proto-google-common-protos/pom.xml index 079d2f967e..fdd4aad8ae 100644 --- a/java-common-protos/proto-google-common-protos/pom.xml +++ b/java-common-protos/proto-google-common-protos/pom.xml @@ -3,13 +3,13 @@ 4.0.0 com.google.api.grpc proto-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT proto-google-common-protos PROTO library for proto-google-common-protos com.google.api.grpc google-common-protos-parent - 2.32.0 + 2.32.1-SNAPSHOT diff --git a/java-core/google-cloud-core-bom/pom.xml b/java-core/google-cloud-core-bom/pom.xml index c8a194cf00..16591c7dac 100644 --- a/java-core/google-cloud-core-bom/pom.xml +++ b/java-core/google-cloud-core-bom/pom.xml @@ -3,13 +3,13 @@ 4.0.0 com.google.cloud google-cloud-core-bom - 2.31.0 + 2.31.1-SNAPSHOT pom com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../../gapic-generator-java-pom-parent @@ -23,17 +23,17 @@ com.google.cloud google-cloud-core - 2.31.0 + 2.31.1-SNAPSHOT com.google.cloud google-cloud-core-grpc - 2.31.0 + 2.31.1-SNAPSHOT com.google.cloud google-cloud-core-http - 2.31.0 + 2.31.1-SNAPSHOT diff --git a/java-core/google-cloud-core-grpc/pom.xml b/java-core/google-cloud-core-grpc/pom.xml index 770b7fd4f0..22c8a54e7c 100644 --- a/java-core/google-cloud-core-grpc/pom.xml +++ b/java-core/google-cloud-core-grpc/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-grpc - 2.31.0 + 2.31.1-SNAPSHOT jar Google Cloud Core gRPC @@ -12,7 +12,7 @@ com.google.cloud google-cloud-core-parent - 2.31.0 + 2.31.1-SNAPSHOT google-cloud-core-grpc diff --git a/java-core/google-cloud-core-http/pom.xml b/java-core/google-cloud-core-http/pom.xml index 208c3fd751..20ab155679 100644 --- a/java-core/google-cloud-core-http/pom.xml +++ b/java-core/google-cloud-core-http/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-http - 2.31.0 + 2.31.1-SNAPSHOT jar Google Cloud Core HTTP @@ -12,7 +12,7 @@ com.google.cloud google-cloud-core-parent - 2.31.0 + 2.31.1-SNAPSHOT google-cloud-core-http diff --git a/java-core/google-cloud-core/pom.xml b/java-core/google-cloud-core/pom.xml index 5ba16329cb..c77cd7e10a 100644 --- a/java-core/google-cloud-core/pom.xml +++ b/java-core/google-cloud-core/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core - 2.31.0 + 2.31.1-SNAPSHOT jar Google Cloud Core @@ -12,7 +12,7 @@ com.google.cloud google-cloud-core-parent - 2.31.0 + 2.31.1-SNAPSHOT google-cloud-core diff --git a/java-core/pom.xml b/java-core/pom.xml index b5fcb73fab..1a5dbc2052 100644 --- a/java-core/pom.xml +++ b/java-core/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-core-parent pom - 2.31.0 + 2.31.1-SNAPSHOT Google Cloud Core Parent Java idiomatic client for Google Cloud Platform services. @@ -13,7 +13,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -33,7 +33,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import diff --git a/java-iam/grpc-google-iam-v1/pom.xml b/java-iam/grpc-google-iam-v1/pom.xml index a025459d58..383edc5ad1 100644 --- a/java-iam/grpc-google-iam-v1/pom.xml +++ b/java-iam/grpc-google-iam-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-iam-v1 - 1.27.0 + 1.27.1-SNAPSHOT grpc-google-iam-v1 GRPC library for grpc-google-iam-v1 com.google.cloud google-iam-parent - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/java-iam/grpc-google-iam-v2/pom.xml b/java-iam/grpc-google-iam-v2/pom.xml index 93568d5461..cde0a76a03 100644 --- a/java-iam/grpc-google-iam-v2/pom.xml +++ b/java-iam/grpc-google-iam-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-iam-v2 - 1.27.0 + 1.27.1-SNAPSHOT grpc-google-iam-v2 GRPC library for proto-google-iam-v2 com.google.cloud google-iam-parent - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/java-iam/grpc-google-iam-v2beta/pom.xml b/java-iam/grpc-google-iam-v2beta/pom.xml index cb18fb506c..9a8ded0cef 100644 --- a/java-iam/grpc-google-iam-v2beta/pom.xml +++ b/java-iam/grpc-google-iam-v2beta/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-iam-v2beta - 1.27.0 + 1.27.1-SNAPSHOT grpc-google-iam-v2beta GRPC library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/java-iam/pom.xml b/java-iam/pom.xml index 6ae2d81703..c6fa565caf 100644 --- a/java-iam/pom.xml +++ b/java-iam/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-iam-parent pom - 1.27.0 + 1.27.1-SNAPSHOT Google IAM Parent Java idiomatic client for Google Cloud Platform services. @@ -13,7 +13,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -60,7 +60,7 @@ com.google.cloud third-party-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import @@ -88,44 +88,44 @@ com.google.api gax-bom - 2.41.0 + 2.41.1-SNAPSHOT pom import com.google.api.grpc proto-google-iam-v2 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc grpc-google-iam-v2 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc proto-google-common-protos - 2.32.0 + 2.32.1-SNAPSHOT com.google.api.grpc proto-google-iam-v2beta - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc grpc-google-iam-v1 - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc grpc-google-iam-v2beta - 1.27.0 + 1.27.1-SNAPSHOT com.google.api.grpc proto-google-iam-v1 - 1.27.0 + 1.27.1-SNAPSHOT javax.annotation diff --git a/java-iam/proto-google-iam-v1/pom.xml b/java-iam/proto-google-iam-v1/pom.xml index d0478f1822..c260543c85 100644 --- a/java-iam/proto-google-iam-v1/pom.xml +++ b/java-iam/proto-google-iam-v1/pom.xml @@ -3,13 +3,13 @@ 4.0.0 com.google.api.grpc proto-google-iam-v1 - 1.27.0 + 1.27.1-SNAPSHOT proto-google-iam-v1 PROTO library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/java-iam/proto-google-iam-v2/pom.xml b/java-iam/proto-google-iam-v2/pom.xml index 6833e52126..ea830bc5dd 100644 --- a/java-iam/proto-google-iam-v2/pom.xml +++ b/java-iam/proto-google-iam-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-iam-v2 - 1.27.0 + 1.27.1-SNAPSHOT proto-google-iam-v2 Proto library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/java-iam/proto-google-iam-v2beta/pom.xml b/java-iam/proto-google-iam-v2beta/pom.xml index 5e50ec088a..54475015b1 100644 --- a/java-iam/proto-google-iam-v2beta/pom.xml +++ b/java-iam/proto-google-iam-v2beta/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-iam-v2beta - 1.27.0 + 1.27.1-SNAPSHOT proto-google-iam-v2beta Proto library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.0 + 1.27.1-SNAPSHOT diff --git a/java-shared-dependencies/dependency-convergence-check/pom.xml b/java-shared-dependencies/dependency-convergence-check/pom.xml index 52597acdbd..6840b12de0 100644 --- a/java-shared-dependencies/dependency-convergence-check/pom.xml +++ b/java-shared-dependencies/dependency-convergence-check/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud shared-dependencies-dependency-convergence-test - 3.23.0 + 3.23.1-SNAPSHOT Dependency convergence test for certain artifacts in Google Cloud Shared Dependencies An dependency convergence test case for the shared dependencies BOM. A failure of this test case means diff --git a/java-shared-dependencies/first-party-dependencies/pom.xml b/java-shared-dependencies/first-party-dependencies/pom.xml index 692f3d6475..c5019e9684 100644 --- a/java-shared-dependencies/first-party-dependencies/pom.xml +++ b/java-shared-dependencies/first-party-dependencies/pom.xml @@ -6,7 +6,7 @@ com.google.cloud first-party-dependencies pom - 3.23.0 + 3.23.1-SNAPSHOT Google Cloud First-party Shared Dependencies Shared first-party dependencies for Google Cloud Java libraries. @@ -33,7 +33,7 @@ com.google.api gapic-generator-java-bom - 2.33.0 + 2.33.1-SNAPSHOT pom import @@ -45,7 +45,7 @@ com.google.cloud google-cloud-core-bom - 2.31.0 + 2.31.1-SNAPSHOT pom import @@ -69,13 +69,13 @@ com.google.cloud google-cloud-core - 2.31.0 + 2.31.1-SNAPSHOT test-jar com.google.cloud google-cloud-core - 2.31.0 + 2.31.1-SNAPSHOT tests diff --git a/java-shared-dependencies/pom.xml b/java-shared-dependencies/pom.xml index c2a16945a9..e68d6215b6 100644 --- a/java-shared-dependencies/pom.xml +++ b/java-shared-dependencies/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-shared-dependencies pom - 3.23.0 + 3.23.1-SNAPSHOT first-party-dependencies third-party-dependencies @@ -17,7 +17,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.0 + 2.33.1-SNAPSHOT ../gapic-generator-java-pom-parent @@ -31,14 +31,14 @@ com.google.cloud first-party-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import com.google.cloud third-party-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import diff --git a/java-shared-dependencies/third-party-dependencies/pom.xml b/java-shared-dependencies/third-party-dependencies/pom.xml index a0b8459860..3af13353d3 100644 --- a/java-shared-dependencies/third-party-dependencies/pom.xml +++ b/java-shared-dependencies/third-party-dependencies/pom.xml @@ -6,7 +6,7 @@ com.google.cloud third-party-dependencies pom - 3.23.0 + 3.23.1-SNAPSHOT Google Cloud Third-party Shared Dependencies Shared third-party dependencies for Google Cloud Java libraries. diff --git a/java-shared-dependencies/upper-bound-check/pom.xml b/java-shared-dependencies/upper-bound-check/pom.xml index 924257a59d..db5789cd5c 100644 --- a/java-shared-dependencies/upper-bound-check/pom.xml +++ b/java-shared-dependencies/upper-bound-check/pom.xml @@ -4,7 +4,7 @@ com.google.cloud shared-dependencies-upper-bound-test pom - 3.23.0 + 3.23.1-SNAPSHOT Upper bound test for Google Cloud Shared Dependencies An upper bound test case for the shared dependencies BOM. A failure of this test case means @@ -30,7 +30,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import diff --git a/sdk-platform-java-config/pom.xml b/sdk-platform-java-config/pom.xml index 36cecbd7e1..cc33a7d085 100644 --- a/sdk-platform-java-config/pom.xml +++ b/sdk-platform-java-config/pom.xml @@ -4,7 +4,7 @@ com.google.cloud sdk-platform-java-config pom - 3.23.0 + 3.23.1-SNAPSHOT SDK Platform For Java Configurations Shared build configuration for Google Cloud Java libraries. @@ -17,6 +17,6 @@ - 3.23.0 + 3.23.1-SNAPSHOT \ No newline at end of file diff --git a/showcase/pom.xml b/showcase/pom.xml index 92d7e20251..546296279c 100644 --- a/showcase/pom.xml +++ b/showcase/pom.xml @@ -34,7 +34,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.23.0 + 3.23.1-SNAPSHOT pom import diff --git a/versions.txt b/versions.txt index 2347e0ff49..92faa04311 100644 --- a/versions.txt +++ b/versions.txt @@ -1,19 +1,19 @@ # Format: # module:released-version:current-version -gapic-generator-java:2.33.0:2.33.0 -api-common:2.24.0:2.24.0 -gax:2.41.0:2.41.0 -gax-grpc:2.41.0:2.41.0 -gax-httpjson:0.126.0:0.126.0 -proto-google-common-protos:2.32.0:2.32.0 -grpc-google-common-protos:2.32.0:2.32.0 -proto-google-iam-v1:1.27.0:1.27.0 -grpc-google-iam-v1:1.27.0:1.27.0 -proto-google-iam-v2beta:1.27.0:1.27.0 -grpc-google-iam-v2beta:1.27.0:1.27.0 -google-iam-policy:1.27.0:1.27.0 -proto-google-iam-v2:1.27.0:1.27.0 -grpc-google-iam-v2:1.27.0:1.27.0 -google-cloud-core:2.31.0:2.31.0 -google-cloud-shared-dependencies:3.23.0:3.23.0 +gapic-generator-java:2.33.0:2.33.1-SNAPSHOT +api-common:2.24.0:2.24.1-SNAPSHOT +gax:2.41.0:2.41.1-SNAPSHOT +gax-grpc:2.41.0:2.41.1-SNAPSHOT +gax-httpjson:0.126.0:0.126.1-SNAPSHOT +proto-google-common-protos:2.32.0:2.32.1-SNAPSHOT +grpc-google-common-protos:2.32.0:2.32.1-SNAPSHOT +proto-google-iam-v1:1.27.0:1.27.1-SNAPSHOT +grpc-google-iam-v1:1.27.0:1.27.1-SNAPSHOT +proto-google-iam-v2beta:1.27.0:1.27.1-SNAPSHOT +grpc-google-iam-v2beta:1.27.0:1.27.1-SNAPSHOT +google-iam-policy:1.27.0:1.27.1-SNAPSHOT +proto-google-iam-v2:1.27.0:1.27.1-SNAPSHOT +grpc-google-iam-v2:1.27.0:1.27.1-SNAPSHOT +google-cloud-core:2.31.0:2.31.1-SNAPSHOT +google-cloud-shared-dependencies:3.23.0:3.23.1-SNAPSHOT From a34d3dd0186f6aa1a573beb46af42bf0720d8731 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Thu, 25 Jan 2024 00:39:24 +0000 Subject: [PATCH 02/11] chore: add `google/shopping/type/types.proto` when generating shopping APIs (#2414) * chore: add `google/shopping/type/types.proto` when generating shopping APIs. * do not copy types.proto --- library_generation/generate_library.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library_generation/generate_library.sh b/library_generation/generate_library.sh index e3793b020f..ed8a9008c5 100755 --- a/library_generation/generate_library.sh +++ b/library_generation/generate_library.sh @@ -182,6 +182,12 @@ case "${proto_path}" in removed_proto="google/rpc/http.proto" proto_files="${proto_files//${removed_proto}/}" ;; + "google/shopping"*) + # this proto is included in //google/shopping/css/v1:google-cloud-shopping-css-v1-java + # and //google/shopping/merchant/inventories/v1beta:google-cloud-merchant-inventories-v1beta-java + # and //google/shopping/merchant/reports/v1beta:google-cloud-merchant-reports-v1beta-java + proto_files="${proto_files} google/shopping/type/types.proto" + ;; esac # download gapic-generator-java, protobuf and grpc plugin. download_tools "${gapic_generator_version}" "${protobuf_version}" "${grpc_version}" "${os_architecture}" @@ -285,7 +291,8 @@ case "${proto_path}" in esac # copy proto files to proto-*/src/main/proto for proto_src in ${proto_files}; do - if [[ "${proto_src}" == "google/cloud/common/operation_metadata.proto" ]]; then + if [[ "${proto_src}" == "google/cloud/common/operation_metadata.proto" ]] || + [[ "${proto_src}" == "google/shopping/type/types.proto" ]]; then continue fi mkdir -p "${temp_destination_path}/proto-${folder_name}/src/main/proto" From acdde47445916dd306ce8b91489fab45c9c2ef50 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:31:32 +0000 Subject: [PATCH 03/11] feat: move new client script (#2333) * feat: add new-client.py * refactor code * clone googleapis * add build parser * parse inputs from BUILD * find versioned dir within proto_path * fix postprocessing, use google-java-format * migirate shell scripts * change depth * add generate_gapic_bom.sh * process root pom * only generating gapic-libraries-bom if it already exists * add consolidate_config.sh * add generation dir * use variable for script dir * add set_parent_pom.sh * add apply_current_versions.sh * add click option for versions.txt * change dir * change find command * add readme_update.sh * change script path * refactor * code refactor * add generator version * use GitPython to checkout googleapis * code refactor * use versions.txt in the monorepo * add comments * change year * change copyright year * remove apply_current_versions.sh * add __pychche__ to gitignore * parse generator version from WORKSPACE * remove readme_update.sh * remove update_owlbot_postprocessor_config.sh * remove delete_non_generated_samples.sh * remove set_parent_pom.sh * add destination_name * restore format plugin * remove gitignore * remove consolidate_config.sh * change output directory --------- Co-authored-by: diegomarquezp --- .gitignore | 3 + .../new_client/client_inputs.py | 157 +++++++ .../get_generator_version_from_workspace.sh | 2 + library_generation/new_client/new-client.py | 421 ++++++++++++++++++ library_generation/new_client/requirements.in | 8 + .../new_client/requirements.txt | 232 ++++++++++ library_generation/new_client/templates.py | 33 ++ .../new_client/templates/owlbot.py.j2 | 28 ++ .../templates/owlbot.yaml.monorepo.j2 | 36 ++ library_generation/owlbot/bin/entrypoint.sh | 58 +-- .../owlbot/bin/format_source.sh | 53 +++ library_generation/owlbot/src/fix-poms.py | 51 ++- .../owlbot/templates/poms/bom_pom.xml.j2 | 13 +- .../owlbot/templates/poms/cloud_pom.xml.j2 | 10 +- .../owlbot/templates/poms/grpc_pom.xml.j2 | 2 + .../owlbot/templates/poms/parent_pom.xml.j2 | 10 +- .../owlbot/templates/poms/proto_pom.xml.j2 | 2 + library_generation/postprocess_library.sh | 10 - .../generate_gapic_bom.sh | 60 +++ .../generate_root_pom.sh | 13 + 20 files changed, 1127 insertions(+), 75 deletions(-) create mode 100644 library_generation/new_client/client_inputs.py create mode 100755 library_generation/new_client/get_generator_version_from_workspace.sh create mode 100644 library_generation/new_client/new-client.py create mode 100644 library_generation/new_client/requirements.in create mode 100644 library_generation/new_client/requirements.txt create mode 100644 library_generation/new_client/templates.py create mode 100644 library_generation/new_client/templates/owlbot.py.j2 create mode 100644 library_generation/new_client/templates/owlbot.yaml.monorepo.j2 create mode 100755 library_generation/owlbot/bin/format_source.sh create mode 100755 library_generation/repo-level-postprocess/generate_gapic_bom.sh create mode 100755 library_generation/repo-level-postprocess/generate_root_pom.sh diff --git a/.gitignore b/.gitignore index f98cac050a..3da2d8a7d2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ target/ *.iml +# Python +**/__pycache__/ + # library generation output/ library_generation/output/ diff --git a/library_generation/new_client/client_inputs.py b/library_generation/new_client/client_inputs.py new file mode 100644 index 0000000000..3106fe5210 --- /dev/null +++ b/library_generation/new_client/client_inputs.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# 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. + +from pathlib import Path +import re + +proto_library_pattern = r""" +proto_library_with_info\( +(.*?) +\) +""" +gapic_pattern = r""" +java_gapic_library\( +(.*?) +\) +""" +assembly_pattern = r""" +java_gapic_assembly_gradle_pkg\( +(.*?) +\) +""" +resource_pattern = r"//google/cloud:common_resources_proto" +location_pattern = r"//google/cloud/location:location_proto" +iam_pattern = r"//google/iam/v1:iam_policy_proto" +transport_pattern = r"transport = \"(.*?)\"" +rest_pattern = r"rest_numeric_enums = True" +gapic_yaml_pattern = r"gapic_yaml = \"(.*?)\"" +service_config_pattern = r"grpc_service_config = \"(.*?)\"" +service_yaml_pattern = r"service_yaml = \"(.*?)\"" +include_samples_pattern = r"include_samples = True" + + +class ClientInput: + """ + A data class containing inputs to invoke generate_library.sh to generate + a GAPIC library. + """ + def __init__( + self, + proto_only="true", + additional_protos="google/cloud/common_resources.proto", + transport="", + rest_numeric_enum="", + gapic_yaml="", + service_config="", + service_yaml="", + include_samples="true", + ): + self.proto_only = proto_only + self.additional_protos = additional_protos + self.transport = transport + self.rest_numeric_enum = rest_numeric_enum + self.gapic_yaml = gapic_yaml + self.service_config = service_config + self.service_yaml = service_yaml + self.include_samples = include_samples + + +def parse( + build_path: Path, + versioned_path: str, +) -> ClientInput: + """ + Utility function to parse inputs of generate_library.sh from BUILD.bazel. + :param build_path: the file path of BUILD.bazel + :param versioned_path: a versioned path in googleapis repository, e.g., + google/cloud/asset/v1. + :return: an ClientInput object. + """ + with open(f"{build_path}/BUILD.bazel") as build: + content = build.read() + + proto_library_target = re.compile( + proto_library_pattern, re.DOTALL | re.VERBOSE + ).findall(content)[0] + additional_protos = __parse_additional_protos(proto_library_target) + gapic_target = re.compile(gapic_pattern, re.DOTALL | re.VERBOSE)\ + .findall(content) + assembly_target = re.compile(assembly_pattern, re.DOTALL | re.VERBOSE)\ + .findall(content) + include_samples = __parse_include_samples(assembly_target[0]) + if len(gapic_target) == 0: + return ClientInput( + include_samples=include_samples + ) + + transport = __parse_transport(gapic_target[0]) + rest_numeric_enum = __parse_rest_numeric_enums(gapic_target[0]) + gapic_yaml = __parse_gapic_yaml(gapic_target[0], versioned_path) + service_config = __parse_service_config(gapic_target[0], versioned_path) + service_yaml = __parse_service_yaml(gapic_target[0], versioned_path) + + return ClientInput( + proto_only="false", + additional_protos=additional_protos, + transport=transport, + rest_numeric_enum=rest_numeric_enum, + gapic_yaml=gapic_yaml, + service_config=service_config, + service_yaml=service_yaml, + include_samples=include_samples, + ) + + +def __parse_additional_protos(proto_library_target: str) -> str: + res = [" "] + if len(re.findall(resource_pattern, proto_library_target)) != 0: + res.append("google/cloud/common_resources.proto") + if len(re.findall(location_pattern, proto_library_target)) != 0: + res.append("google/cloud/location/locations.proto") + if len(re.findall(iam_pattern, proto_library_target)) != 0: + res.append("google/iam/v1/iam_policy.proto") + return " ".join(res) + + +def __parse_transport(gapic_target: str) -> str: + transport = re.findall(transport_pattern, gapic_target) + return transport[0] if len(transport) != 0 else "grpc" + + +def __parse_rest_numeric_enums(gapic_target: str) -> str: + rest_numeric_enums = re.findall(rest_pattern, gapic_target) + return "true" if len(rest_numeric_enums) != 0 else "false" + + +def __parse_gapic_yaml(gapic_target: str, versioned_path: str) -> str: + gapic_yaml = re.findall(gapic_yaml_pattern, gapic_target) + return f"{versioned_path}/{gapic_yaml[0]}" if len(gapic_yaml) != 0 else "" + + +def __parse_service_config(gapic_target: str, versioned_path: str) -> str: + service_config = re.findall(service_config_pattern, gapic_target) + return f"{versioned_path}/{service_config[0]}" if len(service_config) != 0 \ + else "" + + +def __parse_service_yaml(gapic_target: str, versioned_path: str) -> str: + service_yaml = re.findall(service_yaml_pattern, gapic_target) + return f"{versioned_path}/{service_yaml[0]}" if len(service_yaml) != 0 \ + else "" + + +def __parse_include_samples(assembly_target: str) -> str: + include_samples = re.findall(include_samples_pattern, assembly_target) + return "true" if len(include_samples) != 0 else "false" diff --git a/library_generation/new_client/get_generator_version_from_workspace.sh b/library_generation/new_client/get_generator_version_from_workspace.sh new file mode 100755 index 0000000000..0d8cac1f25 --- /dev/null +++ b/library_generation/new_client/get_generator_version_from_workspace.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +curl --silent 'https://raw.githubusercontent.com/googleapis/googleapis/master/WORKSPACE' | perl -nle 'print $1 if m/_gapic_generator_java_version\s+=\s+\"(.+)\"/' \ No newline at end of file diff --git a/library_generation/new_client/new-client.py b/library_generation/new_client/new-client.py new file mode 100644 index 0000000000..5b69f335c8 --- /dev/null +++ b/library_generation/new_client/new-client.py @@ -0,0 +1,421 @@ +# 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 +# +# https://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. + +import json +import os +from pathlib import Path +import re +import subprocess +import sys +import click +import templates +from git import Repo +from client_inputs import parse +import shutil + + +@click.group(invoke_without_command=False) +@click.pass_context +@click.version_option(message="%(version)s") +def main(ctx): + pass + + +@main.command() +@click.option( + "--api_shortname", + required=True, + type=str, + prompt="Service name? (e.g. automl)", + help="Name for the new directory name and (default) artifact name" +) +@click.option( + "--name-pretty", + required=True, + type=str, + prompt="Pretty name? (e.g. 'Cloud AutoML')", + help="The human-friendly name that appears in README.md" +) +@click.option( + "--product-docs", + required=True, + type=str, + prompt="Product Documentation URL", + help="Documentation URL that appears in README.md" +) +@click.option( + "--api-description", + required=True, + type=str, + prompt="Description for README. The first sentence is prefixed by the " + "pretty name", + help="Description that appears in README.md" +) +@click.option( + "--release-level", + type=click.Choice(["stable", "preview"]), + default="preview", + show_default=True, + help="A label that appears in repo-metadata.json. The first library " + "generation is always 'preview'." +) +@click.option( + "--transport", + type=click.Choice(["grpc", "http", "both"]), + default="grpc", + show_default=True, + help="A label that appears in repo-metadata.json" +) +@click.option("--language", type=str, default="java", show_default=True) +@click.option( + "--distribution-name", + type=str, + help="Maven coordinates of the generated library. By default it's " + "com.google.cloud:google-cloud-" +) +@click.option( + "--api-id", + type=str, + help="The value of the apiid parameter used in README.md It has link to " + "https://console.cloud.google.com/flows/enableapi?apiid=" +) +@click.option( + "--requires-billing", + type=bool, + default=True, + show_default=True, + help="Based on this value, README.md explains whether billing setup is " + "needed or not." +) +@click.option( + "--destination-name", + type=str, + default=None, + help="The directory name of the new library. By default it's " + "java-" +) +@click.option( + "--proto-path", + required=True, + type=str, + default=None, + help="Path to proto file from the root of the googleapis repository to the" + "directory that contains the proto files (without the version)." + "For example, to generate the library for 'google/maps/routing/v2', " + "then you specify this value as 'google/maps/routing'" +) +@click.option( + "--cloud-api", + type=bool, + default=True, + show_default=True, + help="If true, the artifact ID of the library is 'google-cloud-'; " + "otherwise 'google-'" +) +@click.option( + "--group-id", + type=str, + default="com.google.cloud", + show_default=True, + help="The group ID of the artifact when distribution name is not set" +) +@click.option( + "--library-type", + type=str, + default="GAPIC_AUTO", + show_default=True, + help="A label that appear in repo-metadata.json to tell how the library is " + "maintained or generated" +) +@click.option( + "--googleapis-url", + type=str, + default="https://github.com/googleapis/googleapis.git", + show_default=True, + help="The URL of the repository that has proto service definition" +) +@click.option( + "--rest-docs", + type=str, + help="If it exists, link to the REST Documentation for a service" +) +@click.option( + "--rpc-docs", + type=str, + help="If it exists, link to the RPC Documentation for a service" +) +@click.option( + "--split-repo", + type=bool, + default=False, + help="Whether generating a library into a split repository" +) +def generate( + api_shortname, + name_pretty, + product_docs, + api_description, + release_level, + distribution_name, + api_id, + requires_billing, + transport, + language, + destination_name, + proto_path, + cloud_api, + group_id, + library_type, + googleapis_url, + rest_docs, + rpc_docs, + split_repo, +): + cloud_prefix = "cloud-" if cloud_api else "" + + output_name = destination_name if destination_name else api_shortname + if distribution_name is None: + distribution_name = f"{group_id}:google-{cloud_prefix}{output_name}" + + distribution_name_short = re.split(r"[:\/]", distribution_name)[-1] + + if api_id is None: + api_id = f"{api_shortname}.googleapis.com" + + if not product_docs.startswith("https"): + sys.exit("product_docs must starts with 'https://'") + + client_documentation = ( + f"https://cloud.google.com/{language}/docs/reference/{distribution_name_short}/latest/overview" + ) + + if api_shortname == "": + sys.exit("api_shortname is empty") + + repo = "googleapis/google-cloud-java" + if split_repo: + repo = f"{language}-{output_name}" + + repo_metadata = { + "api_shortname": api_shortname, + "name_pretty": name_pretty, + "product_documentation": product_docs, + "api_description": api_description, + "client_documentation": client_documentation, + "release_level": release_level, + "transport": transport, + "language": language, + "repo": f"{repo}", + "repo_short": f"{language}-{output_name}", + "distribution_name": distribution_name, + "api_id": api_id, + "library_type": library_type, + } + if requires_billing: + repo_metadata["requires_billing"] = True + + if rest_docs: + repo_metadata["rest_documentation"] = rest_docs + + if rpc_docs: + repo_metadata["rpc_documentation"] = rpc_docs + # Initialize workdir + workdir = Path(f"{sys.path[0]}/../../output/java-{output_name}").resolve() + if os.path.isdir(workdir): + sys.exit( + "Couldn't create the module because " + f"the module {workdir} already exists. In Java client library " + "generation, a new API version of an existing module does not " + "require new-client.py invocation. " + "See go/yoshi-java-new-client#adding-a-new-service-version-by-owlbot." + ) + print(f"Creating a new module {workdir}") + os.makedirs(workdir, exist_ok=False) + # write .repo-metadata.json file + with open(workdir / ".repo-metadata.json", "w") as fp: + json.dump(repo_metadata, fp, indent=2) + + template_excludes = [ + ".github/*", + ".kokoro/*", + "samples/*", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "LICENSE", + "SECURITY.md", + "java.header", + "license-checks.xml", + "renovate.json", + ".gitignore" + ] + # create owlbot.py + templates.render( + template_name="owlbot.py.j2", + output_name=str(workdir / "owlbot.py"), + should_include_templates=True, + template_excludes=template_excludes, + ) + + # In monorepo, .OwlBot.yaml needs to be in the directory of the module. + owlbot_yaml_location_from_module = ".OwlBot.yaml" + # create owlbot config + templates.render( + template_name="owlbot.yaml.monorepo.j2", + output_name=str(workdir / owlbot_yaml_location_from_module), + artifact_name=distribution_name_short, + proto_path=proto_path, + module_name=f"java-{output_name}", + api_shortname=api_shortname + ) + + print(f"Pulling proto from {googleapis_url}") + output_dir = Path(f"{sys.path[0]}/../../output").resolve() + __sparse_clone( + remote_url=googleapis_url, + dest=output_dir, + ) + # Find a versioned directory within proto_path + # We only need to generate one version of the library as OwlBot + # will copy other versions from googleapis-gen. + version = __find_version( + Path(f"{sys.path[0]}/../../output/{proto_path}").resolve() + ) + versioned_proto_path = f"{proto_path}/{version}" + print(f"Generating from {versioned_proto_path}") + # parse BUILD.bazel in proto_path + client_input = parse( + build_path=Path(f"{sys.path[0]}/../../output/{versioned_proto_path}") + .resolve(), + versioned_path=versioned_proto_path, + ) + repo_root_dir = Path(f"{sys.path[0]}/../../").resolve() + generator_version = subprocess.check_output( + ["library_generation/new_client/get_generator_version_from_workspace.sh"], + cwd=repo_root_dir + ).strip() + print(f"Generator version: {generator_version}") + # run generate_library.sh + subprocess.check_call([ + "library_generation/generate_library.sh", + "-p", + versioned_proto_path, + "-d", + f"java-{output_name}", + "--gapic_generator_version", + generator_version, + "--protobuf_version", + "23.2", + "--proto_only", + client_input.proto_only, + "--gapic_additional_protos", + client_input.additional_protos, + "--transport", + client_input.transport, + "--rest_numeric_enums", + client_input.rest_numeric_enum, + "--gapic_yaml", + client_input.gapic_yaml, + "--service_config", + client_input.service_config, + "--service_yaml", + client_input.service_yaml, + "--include_samples", + client_input.include_samples, + "--versions_file", + f"{repo_root_dir}/versions.txt"], + cwd=repo_root_dir + ) + + # Move generated module to repo root. + __move_modules( + source=output_dir, + dest=repo_root_dir, + name_prefix="java-" + ) + + # Repo level post process + script_dir = "library_generation/repo-level-postprocess" + + print("Regenerating root pom.xml") + subprocess.check_call( + [ + f"{script_dir}/generate_root_pom.sh", + f"{output_dir}" + ], + cwd=repo_root_dir, + ) + + if not split_repo: + print("Regenerating the GAPIC BOM") + subprocess.check_call( + [ + f"{script_dir}/generate_gapic_bom.sh", + f"{output_dir}" + ], + cwd=repo_root_dir, + ) + + print("Deleting temp files") + subprocess.check_call( + [ + "rm", + "-rf", + f"{output_dir}" + ], + cwd=repo_root_dir + ) + + print(f"Prepared new library in {workdir}") + print(f"Please create a pull request:\n" + f" $ git checkout -b new_module_java-{output_name}\n" + f" $ git add .\n" + f" $ git commit -m 'feat: [{api_shortname}] new module for {api_shortname}'\n" + f" $ gh pr create --title 'feat: [{api_shortname}] new module for {api_shortname}'") + + +def __sparse_clone( + remote_url: str, + dest: Path, + commit_hash: str = "master", +): + local_repo = Repo.init(dest) + origin = local_repo.create_remote( + name="origin", + url=remote_url + ) + + origin.fetch() + git = local_repo.git() + git.checkout(f"origin/{commit_hash}", "--", "google", "grafeas") + + +def __find_version(proto_path: Path) -> str: + for child in proto_path.iterdir(): + if child.is_dir() and re.search(r"v[1-9]", child.name) is not None: + return child.name + return "" + + +def __move_modules( + source: Path, + dest: Path, + name_prefix: str +) -> None: + for folder in source.iterdir(): + if folder.is_dir() and folder.name.startswith(name_prefix): + shutil.move(folder, dest) + + +if __name__ == "__main__": + main() diff --git a/library_generation/new_client/requirements.in b/library_generation/new_client/requirements.in new file mode 100644 index 0000000000..2ff144604c --- /dev/null +++ b/library_generation/new_client/requirements.in @@ -0,0 +1,8 @@ +attr +attrs +black +click +jinja2 +lxml +typing +GitPython diff --git a/library_generation/new_client/requirements.txt b/library_generation/new_client/requirements.txt new file mode 100644 index 0000000000..1012323e89 --- /dev/null +++ b/library_generation/new_client/requirements.txt @@ -0,0 +1,232 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes library_generation/new_client/requirements.in +# +attr==0.3.2 \ + --hash=sha256:1ceebca768181cdcce9827611b1d728e592be5d293911539ea3d0b0bfa1146f4 \ + --hash=sha256:4f4bffeea8c27387bde446675a7ac24f3b8fea1075f12d849b5f5c5181fc8336 + # via -r library_generation/new_client/requirements.in +attrs==23.2.0 \ + --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ + --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 + # via -r library_generation/new_client/requirements.in +black==23.12.1 \ + --hash=sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50 \ + --hash=sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f \ + --hash=sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e \ + --hash=sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec \ + --hash=sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055 \ + --hash=sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3 \ + --hash=sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5 \ + --hash=sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54 \ + --hash=sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b \ + --hash=sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e \ + --hash=sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e \ + --hash=sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba \ + --hash=sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea \ + --hash=sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59 \ + --hash=sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d \ + --hash=sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0 \ + --hash=sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9 \ + --hash=sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a \ + --hash=sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e \ + --hash=sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba \ + --hash=sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2 \ + --hash=sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2 + # via -r library_generation/new_client/requirements.in +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via + # -r library_generation/new_client/requirements.in + # black +gitdb==4.0.11 \ + --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ + --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b + # via gitpython +gitpython==3.1.40 \ + --hash=sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4 \ + --hash=sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a + # via -r library_generation/new_client/requirements.in +jinja2==3.1.2 \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 + # via -r library_generation/new_client/requirements.in +lxml==5.0.0 \ + --hash=sha256:016de3b29a262655fc3d2075dc1b2611f84f4c3d97a71d579c883d45e201eee4 \ + --hash=sha256:0326e9b8176ea77269fb39e7af4010906e73e9496a9f8eaf06d253b1b1231ceb \ + --hash=sha256:03290e2f714f2e7431c8430c08b48167f657da7bc689c6248e828ff3c66d5b1b \ + --hash=sha256:049fef98d02513c34f5babd07569fc1cf1ed14c0f2fbff18fe72597f977ef3c2 \ + --hash=sha256:07a900735bad9af7be3085480bf384f68ed5580ba465b39a098e6a882c060d6b \ + --hash=sha256:0d277d4717756fe8816f0beeff229cb72f9dd02a43b70e1d3f07c8efadfb9fe1 \ + --hash=sha256:173bcead3af5d87c7bca9a030675073ddaad8e0a9f0b04be07cd9390453e7226 \ + --hash=sha256:1ef0793e1e2dd221fce7c142177008725680f7b9e4a184ab108d90d5d3ab69b7 \ + --hash=sha256:21af2c3862db6f4f486cddf73ec1157b40d5828876c47cd880edcbad8240ea1b \ + --hash=sha256:2219cbf790e701acf9a21a31ead75f983e73daf0eceb9da6990212e4d20ebefe \ + --hash=sha256:2992591e2294bb07faf7f5f6d5cb60710c046404f4bfce09fb488b85d2a8f58f \ + --hash=sha256:3663542aee845129a981889c19b366beab0b1dadcf5ca164696aabfe1aa51667 \ + --hash=sha256:3e6cbb68bf70081f036bfc018649cf4b46c4e7eaf7860a277cae92dee2a57f69 \ + --hash=sha256:3f908afd0477cace17f941d1b9cfa10b769fe1464770abe4cfb3d9f35378d0f8 \ + --hash=sha256:3ffa066db40b0347e48334bd4465de768e295a3525b9a59831228b5f4f93162d \ + --hash=sha256:405e3760f83a8ba3bdb6e622ec79595cdc20db916ce37377bbcb95b5711fa4ca \ + --hash=sha256:44fa9afd632210f1eeda51cf284ed8dbab0c7ec8b008dd39ba02818e0e114e69 \ + --hash=sha256:4786b0af7511ea614fd86407a52a7bc161aa5772d311d97df2591ed2351de768 \ + --hash=sha256:4a45a278518e4308865c1e9dbb2c42ce84fb154efb03adeb16fdae3c1687c7c9 \ + --hash=sha256:4b9d5b01900a760eb3acf6cef50aead4ef2fa79e7ddb927084244e41dfe37b65 \ + --hash=sha256:4e69c36c8618707a90ed3fb6f48a6cc9254ffcdbf7b259e439a5ae5fbf9c5206 \ + --hash=sha256:52a9ab31853d3808e7cf0183b3a5f7e8ffd622ea4aee1deb5252dbeaefd5b40d \ + --hash=sha256:52c0acc2f29b0a204efc11a5ed911a74f50a25eb7d7d5069c2b1fd3b3346ce11 \ + --hash=sha256:5382612ba2424cea5d2c89e2c29077023d8de88f8d60d5ceff5f76334516df9e \ + --hash=sha256:581a78f299a9f5448b2c3aea904bfcd17c59bf83016d221d7f93f83633bb2ab2 \ + --hash=sha256:583c0e15ae06adc81035346ae2abb2e748f0b5197e7740d8af31222db41bbf7b \ + --hash=sha256:59cea9ba1c675fbd6867ca1078fc717a113e7f5b7644943b74137b7cc55abebf \ + --hash=sha256:5b39f63edbe7e018c2ac1cf0259ee0dd2355274e8a3003d404699b040782e55e \ + --hash=sha256:5eff173f0ff408bfa578cbdafd35a7e0ca94d1a9ffe09a8a48e0572d0904d486 \ + --hash=sha256:5fb988e15378d6e905ca8f60813950a0c56da9469d0e8e5d8fe785b282684ec5 \ + --hash=sha256:6507c58431dbd95b50654b3313c5ad54f90e54e5f2cdacf733de61eae478eec5 \ + --hash=sha256:6a2de85deabf939b0af89e2e1ea46bfb1239545e2da6f8ac96522755a388025f \ + --hash=sha256:6a5501438dd521bb7e0dde5008c40c7bfcfaafaf86eccb3f9bd27509abb793da \ + --hash=sha256:6bba06d8982be0f0f6432d289a8d104417a0ab9ed04114446c4ceb6d4a40c65d \ + --hash=sha256:70ab4e02f7aa5fb4131c8b222a111ce7676f3767e36084fba3a4e7338dc82dcd \ + --hash=sha256:7188495c1bf71bfda87d78ed50601e72d252119ce11710d6e71ff36e35fea5a0 \ + --hash=sha256:71a7cee869578bc17b18050532bb2f0bc682a7b97dda77041741a1bd2febe6c7 \ + --hash=sha256:73bfab795d354aaf2f4eb7a5b0db513031734fd371047342d5803834ce19ec18 \ + --hash=sha256:766868f729f3ab84125350f1a0ea2594d8b1628a608a574542a5aff7355b9941 \ + --hash=sha256:77b73952534967a4497d9e4f26fbeebfba19950cbc66b7cc3a706214429d8106 \ + --hash=sha256:78d6d8e5b54ed89dc0f0901eaaa579c384ad8d59fa43cc7fb06e9bb89115f8f4 \ + --hash=sha256:793be9b4945c2dfd69828fb5948d7d9569b78e0599e4a2e88d92affeb0ff3aa3 \ + --hash=sha256:7ba26a7dc929a1b3487d51bbcb0099afed2fc06e891b82845c8f37a2d7d7fbbd \ + --hash=sha256:7df433d08d4587dc3932f7fcfc3194519a6824824104854e76441fd3bc000d29 \ + --hash=sha256:80209b31dd3908bc5b014f540fd192c97ea52ab179713a730456c5baf7ce80c1 \ + --hash=sha256:8134d5441d1ed6a682e3de3d7a98717a328dce619ee9c4c8b3b91f0cb0eb3e28 \ + --hash=sha256:81509dffd8aba3bdb43e90cbd218c9c068a1f4047d97bc9546b3ac9e3a4ae81d \ + --hash=sha256:88f559f8beb6b90e41a7faae4aca4c8173a4819874a9bf8e74c8d7c1d51f3162 \ + --hash=sha256:894c5f71186b410679aaab5774543fcb9cbabe8893f0b31d11cf28a0740e80be \ + --hash=sha256:8cc0a951e5616ac626f7036309c41fb9774adcd4aa7db0886463da1ce5b65edb \ + --hash=sha256:8ce8b468ab50f9e944719d1134709ec11fe0d2840891a6cae369e22141b1094c \ + --hash=sha256:904d36165848b59c4e04ae5b969072e602bd987485076fca8ec42c6cd7a7aedc \ + --hash=sha256:96095bfc0c02072fc89afa67626013a253596ea5118b8a7f4daaae049dafa096 \ + --hash=sha256:980ba47c8db4b9d870014c7040edb230825b79017a6a27aa54cdb6fcc02d8cc0 \ + --hash=sha256:992029258ed719f130d5a9c443d142c32843046f1263f2c492862b2a853be570 \ + --hash=sha256:99cad5c912f359e59e921689c04e54662cdd80835d80eeaa931e22612f515df7 \ + --hash=sha256:9b59c429e1a2246da86ae237ffc3565efcdc71c281cd38ca8b44d5fb6a3b993a \ + --hash=sha256:9ca498f8554a09fbc3a2f8fc4b23261e07bc27bef99b3df98e2570688033f6fc \ + --hash=sha256:9cd3d6c2c67d4fdcd795e4945e2ba5434909c96640b4cc09453bd0dc7e8e1bac \ + --hash=sha256:a85136d0ee18a41c91cc3e2844c683be0e72e6dda4cb58da9e15fcaef3726af7 \ + --hash=sha256:ac21aace6712472e77ea9dfc38329f53830c4259ece54c786107105ebb069053 \ + --hash=sha256:aebd8fd378e074b22e79cad329dcccd243c40ff1cafaa512d19276c5bb9554e1 \ + --hash=sha256:affdd833f82334fdb10fc9a1c7b35cdb5a86d0b672b4e14dd542e1fe7bcea894 \ + --hash=sha256:b6d4e148edee59c2ad38af15810dbcb8b5d7b13e5de3509d8cf3edfe74c0adca \ + --hash=sha256:bb58e8f4b2cfe012cd312239b8d5139995fe8f5945c7c26d5fbbbb1ddb9acd47 \ + --hash=sha256:bfdc4668ac56687a89ca3eca44231144a2e9d02ba3b877558db74ba20e2bd9fa \ + --hash=sha256:c1249aa4eaced30b59ecf8b8cae0b1ccede04583c74ca7d10b6f8bbead908b2c \ + --hash=sha256:c7cfb6af73602c8d288581df8a225989d7e9d5aab0a174be0e19fcfa800b6797 \ + --hash=sha256:c7fe19abb3d3c55a9e65d289b12ad73b3a31a3f0bda3c539a890329ae9973bd6 \ + --hash=sha256:c8954da15403db1acfc0544b3c3f963a6ef4e428283ab6555e3e298bbbff1cf6 \ + --hash=sha256:c90c593aa8dd57d5dab0ef6d7d64af894008971d98e6a41b320fdd75258fbc6e \ + --hash=sha256:cb564bbe55ff0897d9cf1225041a44576d7ae87f06fd60163544c91de2623d3f \ + --hash=sha256:cfa8a4cdc3765574b7fd0c7cfa5fbd1e2108014c9dfd299c679e5152bea9a55e \ + --hash=sha256:d1bb64646480c36a4aa1b6a44a5b6e33d0fcbeab9f53f1b39072cd3bb2c6243a \ + --hash=sha256:dac2733fe4e159b0aae0439db6813b7b1d23ff96d0b34c0107b87faf79208c4e \ + --hash=sha256:db40e85cffd22f7d65dcce30e85af565a66401a6ed22fc0c56ed342cfa4ffc43 \ + --hash=sha256:dd39ef87fd1f7bb5c4aa53454936e6135cbfe03fe3744e8218be193f9e4fef16 \ + --hash=sha256:de1a8b54170024cf1c0c2718c82412bca42cd82e390556e3d8031af9541b416f \ + --hash=sha256:e675a4b95208e74c34ac0751cc4bab9170e7728b61601fb0f4746892c2bb7e0b \ + --hash=sha256:e6bb39d91bf932e7520cb5718ae3c2f498052aca53294d5d59fdd9068fe1a7f2 \ + --hash=sha256:e8c63f5c7d87e7044880b01851ac4e863c3349e6f6b6ab456fe218d9346e816d \ + --hash=sha256:ea56825c1e23c9c8ea385a191dac75f9160477057285b88c88736d9305e6118f \ + --hash=sha256:ee60f33456ff34b2dd1d048a740a2572798356208e4c494301c931de3a0ab3a2 \ + --hash=sha256:f15844a1b93dcaa09c2b22e22a73384f3ae4502347c3881cfdd674e14ac04e21 \ + --hash=sha256:f298ac9149037d6a3d5c74991bded39ac46292520b9c7c182cb102486cc87677 \ + --hash=sha256:f30e697b6215e759d0824768b2c5b0618d2dc19abe6c67eeed2b0460f52470d1 \ + --hash=sha256:f92d73faa0b1a76d1932429d684b7ce95829e93c3eef3715ec9b98ab192c9d31 \ + --hash=sha256:fef10f27d6318d2d7c88680e113511ddecf09ee4f9559b3623b73ee89fa8f6cc + # via -r library_generation/new_client/requirements.in +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ + --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ + --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 + # via jinja2 +mypy-extensions==1.0.0 \ + --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ + --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 + # via black +packaging==23.2 \ + --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ + --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 + # via black +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +platformdirs==4.1.0 \ + --hash=sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380 \ + --hash=sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420 + # via black +smmap==5.0.1 \ + --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ + --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da + # via gitdb +typing==3.7.4.3 \ + --hash=sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9 \ + --hash=sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5 + # via -r library_generation/new_client/requirements.in diff --git a/library_generation/new_client/templates.py b/library_generation/new_client/templates.py new file mode 100644 index 0000000000..5b0282ce03 --- /dev/null +++ b/library_generation/new_client/templates.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright 2020 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. + +from jinja2 import Environment, FileSystemLoader +import os +import pathlib + +root_directory = pathlib.Path( + os.path.realpath(os.path.dirname(os.path.realpath(__file__))) +) +print(root_directory) +jinja_env = Environment(loader=FileSystemLoader(str(root_directory / "templates"))) + + +def render(template_name: str, output_name: str, **kwargs): + template = jinja_env.get_template(template_name) + t = template.stream(kwargs) + directory = os.path.dirname(output_name) + if not os.path.isdir(directory): + os.makedirs(directory) + t.dump(str(output_name)) diff --git a/library_generation/new_client/templates/owlbot.py.j2 b/library_generation/new_client/templates/owlbot.py.j2 new file mode 100644 index 0000000000..84f070f35b --- /dev/null +++ b/library_generation/new_client/templates/owlbot.py.j2 @@ -0,0 +1,28 @@ +# 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 +# +# https://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. + +import synthtool as s +{% if should_include_templates %}from synthtool.languages import java{% endif %} + + +for library in s.get_staging_dirs(): + # put any special-case replacements here + s.move(library) + +s.remove_staging_dirs() +{% if should_include_templates %}java.common_templates(monorepo=True, {% if template_excludes %}excludes=[ +{%- for exclude in template_excludes %} + "{{ exclude }}", +{%- endfor %} +]{% endif %}){% endif %} diff --git a/library_generation/new_client/templates/owlbot.yaml.monorepo.j2 b/library_generation/new_client/templates/owlbot.yaml.monorepo.j2 new file mode 100644 index 0000000000..3cfcc46aaf --- /dev/null +++ b/library_generation/new_client/templates/owlbot.yaml.monorepo.j2 @@ -0,0 +1,36 @@ +# 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. + +{% if artifact_name %} +deep-remove-regex: +- "/{{ module_name }}/grpc-google-.*/src" +- "/{{ module_name }}/proto-google-.*/src" +- "/{{ module_name }}/google-.*/src" +- "/{{ module_name }}/samples/snippets/generated" + +deep-preserve-regex: +- "/{{ module_name }}/google-.*/src/test/java/com/google/cloud/.*/v.*/it/IT.*Test.java" + +deep-copy-regex: +- source: "/{{ proto_path }}/(v.*)/.*-java/proto-google-.*/src" + dest: "/owl-bot-staging/{{ module_name }}/$1/proto-{{ artifact_name }}-$1/src" +- source: "/{{ proto_path }}/(v.*)/.*-java/grpc-google-.*/src" + dest: "/owl-bot-staging/{{ module_name }}/$1/grpc-{{ artifact_name }}-$1/src" +- source: "/{{ proto_path }}/(v.*)/.*-java/gapic-google-.*/src" + dest: "/owl-bot-staging/{{ module_name }}/$1/{{ artifact_name }}/src" +- source: "/{{ proto_path }}/(v.*)/.*-java/samples/snippets/generated" + dest: "/owl-bot-staging/{{ module_name }}/$1/samples/snippets/generated" +{% endif %} + +api-name: {{ api_shortname }} diff --git a/library_generation/owlbot/bin/entrypoint.sh b/library_generation/owlbot/bin/entrypoint.sh index f483f98cfd..65e3a5fa2a 100755 --- a/library_generation/owlbot/bin/entrypoint.sh +++ b/library_generation/owlbot/bin/entrypoint.sh @@ -65,49 +65,17 @@ function processModule() { echo "...done" } -if [ "$(ls */.OwlBot.yaml|wc -l)" -gt 1 ];then - # Monorepo (googleapis/google-cloud-java) has multiple OwlBot.yaml config - # files in the modules. - echo "Processing monorepo" - if [ -d owl-bot-staging ]; then - # The content of owl-bot-staging is controlled by Owlbot.yaml files in - # each module in the monorepo - echo "Extracting contents from owl-bot-staging" - for module in owl-bot-staging/* ; do - if [ ! -d "$module" ]; then - continue - fi - # This relocation allows us continue to use owlbot.py without modification - # after monorepo migration. - mv "owl-bot-staging/$module" "$module/owl-bot-staging" - pushd "$module" - processModule - popd - done - rm -r owl-bot-staging - else - echo "In monorepo but no owl-bot-staging." \ - "Formatting changes in the last commit" - # Find the files that were touched by the last commit. - last_commit=$(git log -1 --format=%H) - # [A]dded, [C]reated, [M]odified, and [R]enamed - changed_files=$(git show --name-only --no-renames --diff-filter=ACMR \ - "${last_commit}") - changed_modules=$(echo "$changed_files" |grep -E '.java$' |cut -d '/' -f 1 \ - |sort -u) - for module in ${changed_modules}; do - if [ ! -f "$module/.OwlBot.yaml" ]; then - # Changes irrelevant to Owlbot-generated module (such as .github) do not - # need formatting - continue - fi - pushd "$module" - processModule - popd - done - fi -else - # Split repository - echo "Processing a split repo" - processModule +# monorepo folders have an .OwlBot.yaml file in the module folder (e.g. +# java-asset/.OwlBot.yaml), whereas HW libraries have the yaml in +# `.github/.OwlBot.yaml` +if [[ -f "$(pwd)/.OwlBot.yaml" ]]; then + monorepo="true" +fi + +if [[ "${monorepo}" == "true" ]]; then + mv owl-bot-staging/* temp + rm -rd owl-bot-staging/ + mv temp owl-bot-staging fi + +processModule diff --git a/library_generation/owlbot/bin/format_source.sh b/library_generation/owlbot/bin/format_source.sh new file mode 100755 index 0000000000..849e27f74f --- /dev/null +++ b/library_generation/owlbot/bin/format_source.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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. + +set -e + +# Why OwlBot Java postprocessor does not use the formatter defined in pom.xml? +# It's because the postprocessor runs in a privileged (albeit limited) +# environment. We limit the risk of running somebody else's malicious Maven +# plugin code in the environment. + +# Find all the java files relative to the current directory and format them +# using google-java-format +list="$(find . -name '*.java' -not -path ".*/samples/snippets/generated/**/*" )" +scripts_root=$1 +tmpfile=$(mktemp) + +for file in $list; +do + if [[ $file =~ .*/samples/snippets/src/main/java/com/example/firestore/Quickstart.java ]]; + then + echo "File skipped formatting: $file" + elif [[ $file =~ .*/samples/snippets/src/.*/java/com/example/spanner/.*.java ]]; + then + echo "File skipped formatting: $file" + else + echo $file >> $tmpfile + fi +done + +# download the google-java-format tool +if [ ! -f "${scripts_root}/owlbot/google-java-format.jar" ]; then + echo 'downloading google-java-format' + java_format_version=$(cat "${scripts_root}/configuration/java-format-version") + wget -O "${scripts_root}/owlbot/google-java-format.jar" https://repo1.maven.org/maven2/com/google/googlejavaformat/google-java-format/${java_format_version}/google-java-format-${java_format_version}-all-deps.jar + +fi + +# format the source +cat $tmpfile | xargs java -jar "${scripts_root}/owlbot/google-java-format.jar" --replace + +rm $tmpfile diff --git a/library_generation/owlbot/src/fix-poms.py b/library_generation/owlbot/src/fix-poms.py index e4617a5085..87df4a46bb 100644 --- a/library_generation/owlbot/src/fix-poms.py +++ b/library_generation/owlbot/src/fix-poms.py @@ -22,6 +22,7 @@ import re from typing import List, Mapping from poms import module, templates +from pathlib import Path def load_versions(filename: str, default_group_id: str) -> Mapping[str, module.Module]: @@ -40,7 +41,7 @@ def load_versions(filename: str, default_group_id: str) -> Mapping[str, module.M group_id = ( default_group_id if artifact_id.startswith("google-") - else _proto_group_id(default_group_id) + else __proto_group_id(default_group_id) ) modules[artifact_id] = module.Module( group_id=group_id, @@ -282,7 +283,7 @@ def update_bom_pom(filename: str, modules: List[module.Module]): # https://github.com/googleapis/google-cloud-java/issues/9125 # However, some exceptions are com.google.area120 and com.google.analytics. # https://github.com/googleapis/google-cloud-java/issues/9304 -def _proto_group_id(main_artifact_group_id: str) -> str: +def __proto_group_id(main_artifact_group_id: str) -> str: prefix = "com.google" list_of_group_id = ["com.google.cloud", "com.google.area120", @@ -292,6 +293,18 @@ def _proto_group_id(main_artifact_group_id: str) -> str: return f"{prefix}.api.grpc" +def __get_monorepo_version(versions: str) -> str: + """ + Returns the current version of google-cloud-java in the given version file + :param versions: the versions.txt + :return: the current version of google-cloud-java + """ + with open(versions, "r") as f: + for line in f.readlines(): + if "google-cloud-java" in line: + return line.split(":")[-1].strip() + + def main(versions_file, monorepo): print(f"working directory: {os.getcwd()}") with open(".repo-metadata.json", "r") as fp: @@ -365,7 +378,7 @@ def main(versions_file, monorepo): for path in glob.glob("proto-google-*"): if not path in existing_modules: existing_modules[path] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=path, version=main_module.version, release_version=main_module.release_version, @@ -373,7 +386,7 @@ def main(versions_file, monorepo): if path not in excluded_dependencies_list \ and path not in main_module.artifact_id: required_dependencies[path] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=path, version=main_module.version, release_version=main_module.release_version, @@ -386,11 +399,12 @@ def main(versions_file, monorepo): module=required_dependencies[path], parent_module=parent_module, main_module=main_module, + monorepo=monorepo, ) if path not in excluded_dependencies_list \ and path not in main_module.artifact_id: required_dependencies[path] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=path, version=main_module.version, release_version=main_module.release_version, @@ -399,7 +413,7 @@ def main(versions_file, monorepo): for path in glob.glob("grpc-google-*"): if not path in existing_modules: existing_modules[path] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=path, version=main_module.version, release_version=main_module.release_version, @@ -407,7 +421,7 @@ def main(versions_file, monorepo): if path not in excluded_dependencies_list \ and path not in main_module.artifact_id: required_dependencies[path] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=path, version=main_module.version, release_version=main_module.release_version, @@ -423,11 +437,12 @@ def main(versions_file, monorepo): parent_module=parent_module, main_module=main_module, proto_module=existing_modules[proto_artifact_id], + monorepo=monorepo, ) if path not in excluded_dependencies_list \ and path not in main_module.artifact_id: required_dependencies[path] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=path, version=main_module.version, release_version=main_module.release_version, @@ -469,6 +484,7 @@ def main(versions_file, monorepo): description=repo_metadata["api_description"], proto_modules=proto_modules, grpc_modules=grpc_modules, + monorepo=monorepo, ) if os.path.isfile(f"{artifact_id}-bom/pom.xml"): @@ -477,6 +493,8 @@ def main(versions_file, monorepo): update_bom_pom(f"{artifact_id}-bom/pom.xml", modules) elif artifact_id+"-bom" not in excluded_poms_list: print("creating missing bom pom.xml") + monorepo_version = __get_monorepo_version(versions_file) \ + if monorepo else "" templates.render( template_name="bom_pom.xml.j2", output_name=f"{artifact_id}-bom/pom.xml", @@ -484,6 +502,8 @@ def main(versions_file, monorepo): name=name, modules=modules, main_module=main_module, + monorepo=monorepo, + monorepo_version=monorepo_version ) if os.path.isfile("pom.xml"): @@ -491,6 +511,8 @@ def main(versions_file, monorepo): update_parent_pom("pom.xml", modules) else: print("creating missing parent pom.xml") + monorepo_version = __get_monorepo_version(versions_file) \ + if monorepo else "" templates.render( template_name="parent_pom.xml.j2", output_name="./pom.xml", @@ -498,25 +520,24 @@ def main(versions_file, monorepo): modules=modules, main_module=main_module, name=name, + monorepo=monorepo, + monorepo_version=monorepo_version ) - # For monorepo, we use the versions.txt at the root. The "./" is needed - # for the templates.render(), which tries to create a directory. - versions_txt_file = "../versions.txt" if monorepo else "./versions.txt" - print(f"updating modules in {versions_txt_file}") + print(f"updating modules in {versions_file}") existing_modules.pop(parent_artifact_id) # add extra modules to versions.txt for dependency_module in extra_managed_modules: if dependency_module not in existing_modules: existing_modules[dependency_module] = module.Module( - group_id=_proto_group_id(group_id), + group_id=__proto_group_id(group_id), artifact_id=dependency_module, version=main_module.version, release_version=main_module.release_version, ) templates.render( - template_name="versions.txt.j2", output_name=versions_txt_file, modules=existing_modules.values(), + template_name="versions.txt.j2", output_name=versions_file, modules=existing_modules.values(), ) @@ -524,5 +545,5 @@ def main(versions_file, monorepo): versions_file = sys.argv[1] monorepo = sys.argv[2] if monorepo == 'true': - monorepo = True + monorepo = True main(versions_file, monorepo) diff --git a/library_generation/owlbot/templates/poms/bom_pom.xml.j2 b/library_generation/owlbot/templates/poms/bom_pom.xml.j2 index 45e6d25253..ddcef5226f 100644 --- a/library_generation/owlbot/templates/poms/bom_pom.xml.j2 +++ b/library_generation/owlbot/templates/poms/bom_pom.xml.j2 @@ -1,22 +1,30 @@ - + 4.0.0 {{main_module.group_id}} {{main_module.artifact_id}}-bom {{main_module.version}} pom + {% if monorepo -%} + + com.google.cloud + google-cloud-pom-parent + {{ monorepo_version }} + ../../google-cloud-pom-parent/pom.xml + + {%- else -%} com.google.cloud google-cloud-shared-config 1.5.3 + {%- endif %} Google {{name}} BOM BOM for {{name}} - true @@ -30,5 +38,4 @@ {% endfor %} - diff --git a/library_generation/owlbot/templates/poms/cloud_pom.xml.j2 b/library_generation/owlbot/templates/poms/cloud_pom.xml.j2 index 6f999f4897..ed1f6fbfea 100644 --- a/library_generation/owlbot/templates/poms/cloud_pom.xml.j2 +++ b/library_generation/owlbot/templates/poms/cloud_pom.xml.j2 @@ -1,4 +1,4 @@ - + 4.0.0 {{module.group_id}} @@ -6,7 +6,9 @@ {{module.version}} jar Google {{name}} + {%- if not monorepo %} https://github.com/{{repo}} + {%- endif %} {{name}} {{description}} {{parent_module.group_id}} @@ -62,6 +64,10 @@ com.google.api gax-httpjson + + com.google.api.grpc + grpc-google-common-protos + com.google.api.grpc proto-google-iam-v1 @@ -108,6 +114,7 @@ + {%- if not monorepo %} java9 @@ -131,4 +138,5 @@ + {%- endif %} diff --git a/library_generation/owlbot/templates/poms/grpc_pom.xml.j2 b/library_generation/owlbot/templates/poms/grpc_pom.xml.j2 index ad2b39c223..514861e7a7 100644 --- a/library_generation/owlbot/templates/poms/grpc_pom.xml.j2 +++ b/library_generation/owlbot/templates/poms/grpc_pom.xml.j2 @@ -43,6 +43,7 @@ + {%- if not monorepo %} java9 @@ -66,4 +67,5 @@ + {%- endif %} diff --git a/library_generation/owlbot/templates/poms/parent_pom.xml.j2 b/library_generation/owlbot/templates/poms/parent_pom.xml.j2 index cbe3f10913..dcf922340e 100644 --- a/library_generation/owlbot/templates/poms/parent_pom.xml.j2 +++ b/library_generation/owlbot/templates/poms/parent_pom.xml.j2 @@ -10,11 +10,20 @@ Java idiomatic client for Google Cloud Platform services. + {% if monorepo -%} + + com.google.cloud + google-cloud-jar-parent + {{ monorepo_version }} + ../google-cloud-jar-parent/pom.xml + + {%- else -%} com.google.cloud google-cloud-shared-config 1.5.3 + {%- endif %} UTF-8 @@ -34,7 +43,6 @@ - {% for module in modules %} {{module.artifact_id}} {% endfor %} {{main_module.artifact_id}}-bom diff --git a/library_generation/owlbot/templates/poms/proto_pom.xml.j2 b/library_generation/owlbot/templates/poms/proto_pom.xml.j2 index 9c383533c7..886cd02663 100644 --- a/library_generation/owlbot/templates/poms/proto_pom.xml.j2 +++ b/library_generation/owlbot/templates/poms/proto_pom.xml.j2 @@ -35,6 +35,7 @@ + {%- if not monorepo %} @@ -43,4 +44,5 @@ + {%- endif %} diff --git a/library_generation/postprocess_library.sh b/library_generation/postprocess_library.sh index bb8415dda2..bf07127427 100755 --- a/library_generation/postprocess_library.sh +++ b/library_generation/postprocess_library.sh @@ -88,16 +88,6 @@ docker run --rm \ --source-repo=/pre-processed-libraries \ --config-file=.OwlBot.yaml -# if the postprocessing_target is a library of google-cloud-java, we have to "unpack" the -# owl-bot-staging folder so it's properly processed by java owlbot -if [[ $(basename $(dirname "${postprocessing_target}")) == "google-cloud-java" ]]; then - pushd "${postprocessing_target}" - mv owl-bot-staging/* temp - rm -rd owl-bot-staging/ - mv temp owl-bot-staging - popd # postprocessing_target -fi - # we clone the synthtool library and manually build it mkdir -p /tmp/synthtool pushd /tmp/synthtool diff --git a/library_generation/repo-level-postprocess/generate_gapic_bom.sh b/library_generation/repo-level-postprocess/generate_gapic_bom.sh new file mode 100755 index 0000000000..ad37553d58 --- /dev/null +++ b/library_generation/repo-level-postprocess/generate_gapic_bom.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +set -e + +# Generate BOM of the artifacts in this repository +GENERATION_DIR=$1 +bom_lines="" +# For modules that produce BOMs +for bom_directory in $(find . -maxdepth 3 -name 'google-*-bom' | sort --dictionary-order); do + if [[ "${bom_directory}" = *gapic-libraries-bom ]] || [[ "${bom_directory}" = *google-cloud-core* ]]; then + continue + fi + pom_file="${bom_directory}/pom.xml" + groupId_line=$(grep --max-count=1 'groupId' "${pom_file}") + artifactId_line=$(grep --max-count=1 'artifactId' "${pom_file}") + version_line=$(grep --max-count=1 'x-version-update' "${pom_file}") + + if [[ "$groupId_line" == *"com.google.cloud"* + || "$groupId_line" == *"com.google.analytic"* + || "$groupId_line" == *"com.google.area120"* + || "$groupId_line" == *"io.grafeas"* ]]; then + # The gapic bom mainly includes cloud libraries and ones that have been included already. + # Let's avoid adding com.google.maps and com.google.shopping for now. We may decide to + # add them later. It's more difficult to remove them later without impacting users. + bom_lines+=" \n\ + ${groupId_line}\n\ + ${artifactId_line}\n\ + ${version_line}\n\ + pom\n\ + import\n\ + \n" + fi +done + +# For originally-handwritten modules that do not produce a BOM +for module in $(find . -mindepth 2 -maxdepth 2 -name pom.xml |sort --dictionary-order | xargs dirname); do + if ls "${module}"/*-bom 1> /dev/null 2>&1; then + continue + fi + if ! test -f "${module}/.repo-metadata.json"; then + continue + fi + + pom_file="${module}/pom.xml" + groupId_line=$(grep --max-count=1 'groupId' "${pom_file}") + artifactId_line=$(grep --max-count=1 'artifactId' "${pom_file}") + version_line=$(grep --max-count=1 'x-version-update' "${pom_file}") + bom_lines+=" \n\ + ${groupId_line}\n\ + ${artifactId_line}\n\ + ${version_line}\n\ + \n" +done + +mkdir -p gapic-libraries-bom + +perl -0pe 's/.*<\/dependencies>/\nBOM_ARTIFACT_LIST\n <\/dependencies>/s' "${GENERATION_DIR}/../gapic-libraries-bom/pom.xml" > "${GENERATION_DIR}/bom.pom.xml" +awk -v "dependencyManagements=${bom_lines}" '{gsub(/BOM_ARTIFACT_LIST/,dependencyManagements)}1' \ + "${GENERATION_DIR}/bom.pom.xml" > gapic-libraries-bom/pom.xml +rm "${GENERATION_DIR}/bom.pom.xml" \ No newline at end of file diff --git a/library_generation/repo-level-postprocess/generate_root_pom.sh b/library_generation/repo-level-postprocess/generate_root_pom.sh new file mode 100755 index 0000000000..2cac682c94 --- /dev/null +++ b/library_generation/repo-level-postprocess/generate_root_pom.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +GENERATION_DIR=$1; + +# Find all Maven modules (a directory that contains pom.xml) +find . -mindepth 2 -maxdepth 2 -name pom.xml |sort --dictionary-order | xargs dirname \ + |sed -e 's|./||' | xargs -I '{}' echo " {}" > /tmp/repo-modules.txt + +perl -0pe 's/.*<\/modules>/\n <\/modules>/s' ${GENERATION_DIR}/../pom.xml > ${GENERATION_DIR}/parent.pom.xml +awk -v MODULES="`awk -v ORS='\\\\n' '1' /tmp/repo-modules.txt`" '1;//{print MODULES}' ${GENERATION_DIR}/parent.pom.xml > pom.xml +rm ${GENERATION_DIR}/parent.pom.xml \ No newline at end of file From 0d7303ccf4badf5e2581043fe92ab145e13fd0cb Mon Sep 17 00:00:00 2001 From: Tomo Suzuki Date: Sat, 27 Jan 2024 20:52:28 -0500 Subject: [PATCH 04/11] ci: validate unmanaged dependency check in a few downstream repositories (#2426) --- ...downstream_unmanaged_dependency_check.yaml | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/downstream_unmanaged_dependency_check.yaml diff --git a/.github/workflows/downstream_unmanaged_dependency_check.yaml b/.github/workflows/downstream_unmanaged_dependency_check.yaml new file mode 100644 index 0000000000..a3886cc81c --- /dev/null +++ b/.github/workflows/downstream_unmanaged_dependency_check.yaml @@ -0,0 +1,80 @@ +on: + push: + branches: + - main + pull_request: + paths: + - .github/workflows/downstream_unmanaged_dependency_check.yaml + - java-shared-dependencies/** + +name: Downstream Unmanaged Dependency Check +jobs: + validate: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + repo: + - java-bigtable + - java-firestore + - java-logging + steps: + - name: Checkout sdk-platform-java + uses: actions/checkout@v3 + with: + path: sdk-platform-java + - name: Checkout the downstream repo + uses: actions/checkout@v4 + with: + repository: googleapis/${{ matrix.repo }} + path: ${{ matrix.repo }} + - name: Check the environment + shell: bash + run: | + set -euxo pipefail + pwd + ls -alt + - uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: temurin + cache: maven + - name: Install the modules of sdk-platform-java + shell: bash + working-directory: sdk-platform-java + run: | + set -euo pipefail + # gapic-generator-java is irrelevant + mvn -q -B -ntp install --projects '!gapic-generator-java' \ + -Dcheckstyle.skip -Dfmt.skip -DskipTests -T 1C + - name: Build unmanaged dependency check + shell: bash + working-directory: sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check + run: | + set -euo pipefail + echo "Install Unmanaged Dependency Check in $(pwd)" + mvn clean install -V --batch-mode --no-transfer-progress -DskipTests + - name: Install the modules of the downstream repository + shell: bash + working-directory: ${{ matrix.repo }} + run: | + # No argument to build.sh installs the modules in local Maven repository + .kokoro/build.sh + - name: Run unmanaged dependency check + shell: bash + run: | + set -euo pipefail + set -x + # java-bigtable has "-deps-bom" that declares its dependencies. It's not a good + # BOM to list the artifacts generated by that repository. + bom_dir=$(find ${{ matrix.repo }} -type d -name 'google-*-bom' ! -name '*-deps-bom') + bom_absolute_path=$(realpath "${bom_dir}/pom.xml") + cd sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check + echo "Running Unmanaged Dependency Check against ${bom_absolute_path}" + unmanaged_dependencies=$(mvn exec:java -Dexec.args="../pom.xml ${bom_absolute_path}" -q) + if [[ "${unmanaged_dependencies}" != "[]" ]]; then + echo "With this change, the unmanaged dependencies check installed in ${{ matrix.repo }} will start to" + echo "fail due to ${unmanaged_dependencies}, among the artifacts listed in ${bom_absolute_path}." + exit 1 + fi + echo "Unmanaged dependency check passed" From a34d897767e1b8a8853ba8a211f30f0f6f09ac54 Mon Sep 17 00:00:00 2001 From: Tomo Suzuki Date: Sat, 27 Jan 2024 20:54:58 -0500 Subject: [PATCH 05/11] ci: suppress download progress in unmanaged dependency check (#2424) --- .../src/test/resources/local-install.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java-shared-dependencies/unmanaged-dependency-check/src/test/resources/local-install.sh b/java-shared-dependencies/unmanaged-dependency-check/src/test/resources/local-install.sh index 1e1c0aab36..118ddb2490 100755 --- a/java-shared-dependencies/unmanaged-dependency-check/src/test/resources/local-install.sh +++ b/java-shared-dependencies/unmanaged-dependency-check/src/test/resources/local-install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -mvn install -f shared-dependency-pom.xml -mvn install -f gax-example-pom.xml -mvn install -f nested-dependency-pom.xml -mvn install -f transitive-dependency-pom.xml -mvn install -f google-internal-artifact-test-case-pom.xml +mvn install -B -ntp -f shared-dependency-pom.xml +mvn install -B -ntp -f gax-example-pom.xml +mvn install -B -ntp -f nested-dependency-pom.xml +mvn install -B -ntp -f transitive-dependency-pom.xml +mvn install -B -ntp -f google-internal-artifact-test-case-pom.xml From 363e35e46e41c88b810e4b0672906f73cb7c38b6 Mon Sep 17 00:00:00 2001 From: Diego Marquez Date: Mon, 29 Jan 2024 13:09:38 -0500 Subject: [PATCH 06/11] feat: enable generation with postprocessing of multiple service versions (#2342) * use local synthtool and owlbot * remove unused files * remove more unused files * remove cache files in owlbot * use java 11 for it * remove kokoro files * use glob in owlbot entrypoint * remove unused files * do not do post-process IT on mac * concise entrypoint logic * cleanup i * cleanup ii * cleanup iii * cleanup iv * remove templates * remove protos folder * remove synthtool * connect image to owlbot entrypoint * simplify synthtool docker run command * install synthtool locally * install synthtool only once * use virtualenvs to run python scripts * install pyenv in action * remove jar from history * download google-java-format * fix pyenv init * attempt to fix pyenv installation in gh action * fix manual pyenv installation * install pyenv in profile * install pyenv in bashrc as well * use bash shell explicitly in gh action * install pyenv in same step as IT * do not restart shell * set pyenv path manually * install pyenv in its own step * propagate environment variables to other steps * fix global env var setup * remove wrong env settings * explain usage of pyenv in README * simplify pyenv setup * add comment to owlbot entrypoint * rename destination_path to preprocessed_libraries_path * infer scripts_root in postprocess_library.sh * use temporary folder for preprocess step * use owlbot files from workspace * get rid of output_folder argument * use common temp dir to clone synthtool into * lock synthtool to a specific commitish * fix file transfer * fix owl-bot-staging unpacking * remove unnecessary workspace variable * rename workspace to postprocessing_target * remove owlbot sha logic * remove repository_root variable * cleanup * correct pyenv comment * clean temp sources folder on each run * safety checks for get_proto_path_from_preprocessed_sources * fix integration test * disable compute and asset/v1p2beta1 temporarily they have changes in googleapis that have not been reflected yet in google-cloud-java * fix unit tests * correct comment * do not install docker for macos * fix owlbot files check * fix license headers * remove unnecessary owlbot_sha * add explanation on why are there no macos + postprocess ITs * use `fmt:format` instead of google formatter * clean templates * remove more unnecessary elements * add README entry explaining owlbot maintenance * remove unnecessary java format version * wip: create generate_composed_library.sh * initial implementation of generate_composed_library.sh * partial implementation of scripts * wip: fixes after main changes * partial implementation of scripts ii * correct arg parsing * fixes in python utils * fix generate_library call * correct argument preparation * add gapic generatior version arg check * call generate_library successfully * fix postprocessing step in generate_composed * IT working for all libraries * add unit tests * fix comments in generate_composed_lib * remove commented code * prepare tests without postprocessing * restore test functions * fix rename utility for building owlbot folder * correct linter problems * install realpath on macos * install utils also in unit tests * include utilities.sh in showcase scripts * comment py_util * Update library_generation/generate_composed_library.sh Co-authored-by: Tomo Suzuki * add comment explaining usage of coreutils in macos workflow * explain that entrypoint.sh can be used for HW libraries * use real world example for generate_composed_library.sh * improve versions.txt explanation in generate_composed_library * add return type in python utils * fix versions file inference * use ggj 2.29 to fix ITs temporarily * disable asset due to licence year mismatch * improve commment in generate_composed_library * restore latest generator * remove wrong WORKSPACE comment * improve composed_library input comment * Update library_generation/utilities.py Co-authored-by: Blake Li * remove postprocessing logic in generate_library * add generated_composed_library.py * use python script in IT * iterative fixes * ignore python cache * iterative fixes ii * Update library_generation/generate_composed_library.py Co-authored-by: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> * Update library_generation/utilities.py Co-authored-by: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> * Update library_generation/generate_composed_library.py Co-authored-by: Tomo Suzuki * use underscores in configuration yaml * initial model for gapic config yaml * add requirements file * introduce yaml parsing logic * move parse logic to GenerationConfig * adapt composed_library script * fixes i * set IT to dummy mode * pass BUILD parse utils to production utils * fixes ii - constructor calls and composed_library arguments * fix destination path for partial generations * adapt IT to process individual libraries * use proper versions in configuration yaml * add rest of the libraries to integration test * add library_type to config yaml * reference to parent directory in compare_poms * handle script failures * use library-specific googleapis_commitish * fix protobuf version * install python reqs in Github Action * fix python version path logic * add python unit tests * remove obsolete py_util tests * add python unit tests in workflow * correct type hinting for Library * fix comments * set enable_postprocessing input to main.py to boolean * add explanation on library_type * remove old proto_path_list * fix comments in IT * remove WORKSPACE file usage * add IT configuration yaml for java-bigtable * fix config yaml for bigtable * finish tests for HW bigtable * install python in gh action, lint fix * update ggj to 2.32.0 * fix showcase test * remove commented line * use owlbot cli sha from config yaml * use python version from config yaml * use synthtool commitish from config yaml * add repository_path option * make destination_path required * add typing * use python version from config yaml * correct workflow indentation * fix workflow syntax * use repository_path when postprocessing * correct runs-on in workflow * use full path to repo in workflow * add debug output in workflow * decompose steps to compute python version * checout code in workflow * fix function name in workflow * use full path to obtain python version from yaml * use correct path in python version workflow * use set-output to share python version * fix set-output call * fix output setting in workflow * correct python version letter case * remove pyenv setup * fix repository_path * fix speech proto_path * do not wipe out google-cloud-java in IT * ensure correct version of python compares the poms * fix diff check * add return type for GenerationCOnfig.from_yaml * add missing python unit tests * use default values for enable_postprocessing * use is_monorepo var, constant for google-coud-java * fixes for local run * compute monorepo variable heuristically * update generation configs * remove python version * rename Library to LibraryConfig * rename GAPIC to GapicConfig * remove quotes from grpc version * move ClientInputs to model folder * parse BUILD file using ClientInputs * add unit tests for ClientInputs * fix CLientInputs typo * fix synthtool version * disable compute test * fix unit test * fix compute test * fix unit tests * remove BUILD parsing shell utilities * update monorepo special treatment comment * fix typo in shebang --------- Co-authored-by: Tomo Suzuki Co-authored-by: Blake Li Co-authored-by: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> --- .../workflows/verify_library_generation.yaml | 43 ++- library_generation/.gitignore | 1 + .../generate_composed_library.py | 141 ++++++++++ library_generation/generate_library.sh | 50 +--- library_generation/main.py | 77 ++++++ .../ClientInputs.py} | 15 +- library_generation/model/GapicConfig.py | 9 + library_generation/model/GenerationConfig.py | 91 ++++++ library_generation/model/Library.py | 46 ++++ library_generation/model/LibraryConfig.py | 46 ++++ library_generation/new_client/new-client.py | 5 +- library_generation/owlbot/bin/entrypoint.sh | 2 + library_generation/postprocess_library.sh | 63 +++-- library_generation/requirements.in | 17 ++ library_generation/test/__init__.py | 0 library_generation/test/compare_poms.py | 13 +- .../test/generate_library_integration_test.sh | 259 +++++++----------- .../test/generate_library_unit_tests.sh | 111 -------- .../google-cloud-java/generation_config.yaml | 70 +++-- .../java-bigtable/generation_config.yaml | 14 + .../resources/misc/BUILD_gapic_yaml.bazel | 3 + .../resources/misc/BUILD_no_gapic_yaml.bazel | 3 + .../misc/BUILD_no_service_config.bazel | 3 + .../misc/BUILD_no_service_yaml.bazel | 3 + .../resources/misc/BUILD_service_config.bazel | 3 + .../resources/misc/BUILD_service_yaml.bazel | 3 + .../test/resources/proto_path_list.txt | 25 -- library_generation/test/test_utilities.sh | 213 +------------- library_generation/test/unit_tests.py | 190 +++++++++++++ library_generation/utilities.py | 125 +++++++++ library_generation/utilities.sh | 79 +++++- showcase/scripts/generate_showcase.sh | 2 +- 32 files changed, 1107 insertions(+), 618 deletions(-) create mode 100644 library_generation/.gitignore create mode 100755 library_generation/generate_composed_library.py create mode 100644 library_generation/main.py rename library_generation/{new_client/client_inputs.py => model/ClientInputs.py} (91%) create mode 100644 library_generation/model/GapicConfig.py create mode 100644 library_generation/model/GenerationConfig.py create mode 100644 library_generation/model/Library.py create mode 100644 library_generation/model/LibraryConfig.py create mode 100644 library_generation/requirements.in create mode 100644 library_generation/test/__init__.py create mode 100644 library_generation/test/resources/integration/java-bigtable/generation_config.yaml create mode 100644 library_generation/test/resources/misc/BUILD_gapic_yaml.bazel create mode 100644 library_generation/test/resources/misc/BUILD_no_gapic_yaml.bazel create mode 100644 library_generation/test/resources/misc/BUILD_no_service_config.bazel create mode 100644 library_generation/test/resources/misc/BUILD_no_service_yaml.bazel create mode 100644 library_generation/test/resources/misc/BUILD_service_config.bazel create mode 100644 library_generation/test/resources/misc/BUILD_service_yaml.bazel delete mode 100755 library_generation/test/resources/proto_path_list.txt create mode 100644 library_generation/test/unit_tests.py create mode 100755 library_generation/utilities.py diff --git a/.github/workflows/verify_library_generation.yaml b/.github/workflows/verify_library_generation.yaml index cdfde24b98..0b4ae1b8ed 100644 --- a/.github/workflows/verify_library_generation.yaml +++ b/.github/workflows/verify_library_generation.yaml @@ -25,7 +25,7 @@ jobs: cache: maven - uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: 3.11 - name: install pyenv shell: bash run: | @@ -36,10 +36,23 @@ jobs: export PATH="$PYENV_ROOT/bin:$PATH" echo "PYENV_ROOT=${PYENV_ROOT}" >> $GITHUB_ENV echo "PATH=${PATH}" >> $GITHUB_ENV - # init pyenv - eval "$(pyenv init --path)" - eval "$(pyenv init -)" + set +ex + - name: install python dependencies + shell: bash + run: | + set -ex + pushd library_generation + pip install -r requirements.in + popd + + - name: install utils (macos) + if: matrix.os == 'macos-12' + shell: bash + run: | + brew update --preinstall + # we need the `realpath` command to be available + brew install coreutils - name: install docker (ubuntu) if: matrix.os == 'ubuntu-22.04' shell: bash @@ -69,10 +82,30 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - - name: Run unit tests + - name: install utils (macos) + if: matrix.os == 'macos-12' + shell: bash + run: | + brew update --preinstall + brew install coreutils + - uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: install python dependencies + shell: bash + run: | + set -ex + pushd library_generation + pip install -r requirements.in + popd + - name: Run shell unit tests run: | set -x library_generation/test/generate_library_unit_tests.sh + - name: Run python unit tests + run: | + set -x + python -m unittest library_generation/test/unit_tests.py lint: runs-on: ubuntu-22.04 steps: diff --git a/library_generation/.gitignore b/library_generation/.gitignore new file mode 100644 index 0000000000..c18dd8d83c --- /dev/null +++ b/library_generation/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/library_generation/generate_composed_library.py b/library_generation/generate_composed_library.py new file mode 100755 index 0000000000..d5beec733b --- /dev/null +++ b/library_generation/generate_composed_library.py @@ -0,0 +1,141 @@ +""" +This script allows generation of libraries that are composed of more than one +service version. It is achieved by calling `generate_library.sh` without +postprocessing for all service versions and then calling +postprocess_library.sh at the end, once all libraries are ready. + +Prerequisites +- Needs a folder named `output` in current working directory. This folder +is automatically detected by `generate_library.sh` and this script ensures it +contains the necessary folders and files, specifically: + - A "google" folder found in the googleapis/googleapis repository + - A "grafeas" folder found in the googleapis/googleapis repository +Note: googleapis repo is found in https://github.com/googleapis/googleapis. +""" + +import click +import utilities as util +import os +import sys +import subprocess +import json +from model.GenerationConfig import GenerationConfig +from model.LibraryConfig import LibraryConfig +from model.ClientInputs import parse as parse_build_file + +script_dir = os.path.dirname(os.path.realpath(__file__)) + +""" +Main function in charge of generating libraries composed of more than one +service or service version. +Arguments + - config: a GenerationConfig object representing a parsed configuration + yaml + - library: a LibraryConfig object contained inside config, passed here for + convenience and to prevent all libraries to be processed + - enable_postprocessing: true if postprocessing should be done on the generated + libraries + - repository_path: path to the repository where the generated files will be + sent. If not specified, it will default to the one defined in the configuration yaml + and will be downloaded. The versions file will be inferred from this folder +""" +def generate_composed_library( + config: GenerationConfig, + library: LibraryConfig, + repository_path: str, + enable_postprocessing: bool = True, +) -> None: + output_folder = util.sh_util('get_output_folder') + + print(f'output_folder: {output_folder}') + print('library: ', library) + os.makedirs(output_folder, exist_ok=True) + + googleapis_commitish = config.googleapis_commitish + if library.googleapis_commitish is not None: + googleapis_commitish = library.googleapis_commitish + print('using library-specific googleapis commitish: ' + googleapis_commitish) + else: + print('using common googleapis_commitish') + + print('removing old googleapis folders and files') + util.delete_if_exists(f'{output_folder}/google') + util.delete_if_exists(f'{output_folder}/grafeas') + + print('downloading googleapis') + util.sh_util(f'download_googleapis_files_and_folders "{output_folder}" "{googleapis_commitish}"') + + is_monorepo = len(config.libraries) > 1 + + base_arguments = [] + base_arguments += util.create_argument('gapic_generator_version', config) + base_arguments += util.create_argument('grpc_version', config) + base_arguments += util.create_argument('protobuf_version', config) + + library_name = f'java-{library.api_shortname}' + library_path = None + + versions_file = '' + if is_monorepo: + print('this is a monorepo library') + destination_path = config.destination_path + '/' + library_name + library_folder = destination_path.split('/')[-1] + if repository_path is None: + print(f'sparse_cloning monorepo with {library_name}') + repository_path = f'{output_folder}/{config.destination_path}' + clone_out = util.sh_util(f'sparse_clone "https://github.com/googleapis/{MONOREPO_NAME}.git" "{library_folder} google-cloud-pom-parent google-cloud-jar-parent versions.txt .github"', cwd=output_folder) + print(clone_out) + library_path = f'{repository_path}/{library_name}' + versions_file = f'{repository_path}/versions.txt' + else: + print('this is a HW library') + destination_path = library_name + if repository_path is None: + repository_path = f'{output_folder}/{destination_path}' + util.delete_if_exists(f'{output_folder}/{destination_path}') + clone_out = util.sh_util(f'git clone "https://github.com/googleapis/{destination_path}.git"', cwd=output_folder) + print(clone_out) + library_path = f'{repository_path}' + versions_file = f'{repository_path}/versions.txt' + + owlbot_cli_source_folder = util.sh_util('mktemp -d') + for gapic in library.gapic_configs: + + effective_arguments = list(base_arguments) + effective_arguments += util.create_argument('proto_path', gapic) + + build_file_folder = f'{output_folder}/{gapic.proto_path}' + print(f'build_file_folder: {build_file_folder}') + client_inputs = parse_build_file(build_file_folder, gapic.proto_path) + effective_arguments += [ + '--proto_only', client_inputs.proto_only, + '--gapic_additional_protos', client_inputs.additional_protos, + '--transport', client_inputs.transport, + '--rest_numeric_enums', client_inputs.rest_numeric_enum, + '--gapic_yaml', client_inputs.gapic_yaml, + '--service_config', client_inputs.service_config, + '--service_yaml', client_inputs.service_yaml, + '--include_samples', client_inputs.include_samples, + ] + service_version = gapic.proto_path.split('/')[-1] + temp_destination_path = f'java-{library.api_shortname}-{service_version}' + effective_arguments += [ '--destination_path', temp_destination_path ] + print('arguments: ') + print(effective_arguments) + print(f'Generating library from {gapic.proto_path} to {destination_path}...') + util.run_process_and_print_output(['bash', '-x', f'{script_dir}/generate_library.sh', + *effective_arguments], 'Library generation') + + + if enable_postprocessing: + util.sh_util(f'build_owlbot_cli_source_folder "{library_path}"' + + f' "{owlbot_cli_source_folder}" "{output_folder}/{temp_destination_path}"' + + f' "{gapic.proto_path}"', + cwd=output_folder) + + if enable_postprocessing: + # call postprocess library + util.run_process_and_print_output([f'{script_dir}/postprocess_library.sh', + f'{library_path}', '', versions_file, owlbot_cli_source_folder, + config.owlbot_cli_image, config.synthtool_commitish, str(is_monorepo).lower()], 'Library postprocessing') + diff --git a/library_generation/generate_library.sh b/library_generation/generate_library.sh index ed8a9008c5..9691f063e6 100755 --- a/library_generation/generate_library.sh +++ b/library_generation/generate_library.sh @@ -60,18 +60,10 @@ case $key in include_samples="$2" shift ;; - --enable_postprocessing) - enable_postprocessing="$2" - shift - ;; --os_architecture) os_architecture="$2" shift ;; - --versions_file) - versions_file="$2" - shift - ;; *) echo "Invalid option: [$1]" exit 1 @@ -85,6 +77,11 @@ script_dir=$(dirname "$(readlink -f "$0")") source "${script_dir}"/utilities.sh output_folder="$(get_output_folder)" +if [ -z "${gapic_generator_version}" ]; then + echo 'missing required argument --gapic_generator_version' + exit 1 +fi + if [ -z "${protobuf_version}" ]; then protobuf_version=$(get_protobuf_version "${gapic_generator_version}") fi @@ -125,10 +122,6 @@ if [ -z "${include_samples}" ]; then include_samples="true" fi -if [ -z "$enable_postprocessing" ]; then - enable_postprocessing="true" -fi - if [ -z "${os_architecture}" ]; then os_architecture=$(detect_os_architecture) fi @@ -305,34 +298,7 @@ popd # output_folder pushd "${temp_destination_path}" rm -rf java_gapic_srcjar java_gapic_srcjar_raw.srcjar.zip java_grpc.jar java_proto.jar temp-codegen.srcjar popd # destination path -##################### Section 5 ##################### -# post-processing -##################################################### -if [ "${enable_postprocessing}" != "true" ]; -then - echo "post processing is disabled" - cp -r ${temp_destination_path}/* "${output_folder}/${destination_path}" - rm -rdf "${temp_destination_path}" - exit 0 -fi -if [ -z "${versions_file}" ];then - echo "no versions.txt argument provided. Please provide one in order to enable post-processing" - exit 1 -fi -workspace="${output_folder}/workspace" -if [ -d "${workspace}" ]; then - rm -rdf "${workspace}" -fi - -mkdir -p "${workspace}" - -# if destination_path is not empty, it will be used as a starting workspace for -# postprocessing -if [[ $(find "${output_folder}/${destination_path}" -mindepth 1 -maxdepth 1 -type d,f | wc -l) -gt 0 ]];then - workspace="${output_folder}/${destination_path}" -fi - -bash -x "${script_dir}/postprocess_library.sh" "${workspace}" \ - "${temp_destination_path}" \ - "${versions_file}" +cp -r ${temp_destination_path}/* "${output_folder}/${destination_path}" +rm -rdf "${temp_destination_path}" +exit 0 diff --git a/library_generation/main.py b/library_generation/main.py new file mode 100644 index 0000000000..282e0283fd --- /dev/null +++ b/library_generation/main.py @@ -0,0 +1,77 @@ +""" +Parses a config yaml and generates libraries via generate_composed_library.py +""" + +import click +from generate_composed_library import generate_composed_library +from typing import Dict +from model.GenerationConfig import GenerationConfig +from collections.abc import Sequence +from absl import app + +@click.group(invoke_without_command=False) +@click.pass_context +@click.version_option(message="%(version)s") +def main(ctx): + pass + +@main.command() +@click.option( + "--generation-config-yaml", + required=True, + type=str, + help=""" + Path to generation_config.yaml that contains the metadata about library generation + """ +) +@click.option( + "--enable-postprocessing", + required=False, + default=True, + type=bool, + help=""" + Path to repository where generated files will be merged into, via owlbot copy-code. + Specifying this option enables postprocessing + """ +) +@click.option( + "--target-library-api-shortname", + required=False, + type=str, + help=""" + If specified, only the `library` with api_shortname = target-library-api-shortname will + be generated. If not specified, all libraries in the configuration yaml will be generated + """ +) +@click.option( + "--repository-path", + required=False, + type=str, + help=""" + If specified, the generated files will be sent to this location. If not specified, the + repository will be pulled into output_folder and move the generated files there + """ +) +def generate_from_yaml( + generation_config_yaml: str, + enable_postprocessing: bool, + target_library_api_shortname: str, + repository_path: str +) -> None: + config = GenerationConfig.from_yaml(generation_config_yaml) + target_libraries = config.libraries + if target_library_api_shortname is not None: + target_libraries = [library for library in config.libraries + if library.api_shortname == target_library_api_shortname] + for library in target_libraries: + print(f'generating library {library.api_shortname}') + generate_composed_library( + config, library, repository_path, enable_postprocessing + ) + + + + + +if __name__ == "__main__": + main() diff --git a/library_generation/new_client/client_inputs.py b/library_generation/model/ClientInputs.py similarity index 91% rename from library_generation/new_client/client_inputs.py rename to library_generation/model/ClientInputs.py index 3106fe5210..38acdb316f 100644 --- a/library_generation/new_client/client_inputs.py +++ b/library_generation/model/ClientInputs.py @@ -71,6 +71,7 @@ def __init__( def parse( build_path: Path, versioned_path: str, + build_file_name: str = 'BUILD.bazel' ) -> ClientInput: """ Utility function to parse inputs of generate_library.sh from BUILD.bazel. @@ -79,18 +80,22 @@ def parse( google/cloud/asset/v1. :return: an ClientInput object. """ - with open(f"{build_path}/BUILD.bazel") as build: + with open(f"{build_path}/{build_file_name}") as build: content = build.read() proto_library_target = re.compile( proto_library_pattern, re.DOTALL | re.VERBOSE - ).findall(content)[0] - additional_protos = __parse_additional_protos(proto_library_target) + ).findall(content) + additional_protos = '' + if len(proto_library_target) > 0: + additional_protos = __parse_additional_protos(proto_library_target[0]) gapic_target = re.compile(gapic_pattern, re.DOTALL | re.VERBOSE)\ .findall(content) assembly_target = re.compile(assembly_pattern, re.DOTALL | re.VERBOSE)\ .findall(content) - include_samples = __parse_include_samples(assembly_target[0]) + include_samples = 'false' + if len(assembly_target) > 0: + include_samples = __parse_include_samples(assembly_target[0]) if len(gapic_target) == 0: return ClientInput( include_samples=include_samples @@ -142,7 +147,7 @@ def __parse_gapic_yaml(gapic_target: str, versioned_path: str) -> str: def __parse_service_config(gapic_target: str, versioned_path: str) -> str: service_config = re.findall(service_config_pattern, gapic_target) - return f"{versioned_path}/{service_config[0]}" if len(service_config) != 0 \ + return f"{versioned_path}/{service_config[0]}".replace(':','') if len(service_config) != 0 \ else "" diff --git a/library_generation/model/GapicConfig.py b/library_generation/model/GapicConfig.py new file mode 100644 index 0000000000..be99b0a35f --- /dev/null +++ b/library_generation/model/GapicConfig.py @@ -0,0 +1,9 @@ +""" +Class that represents a GAPICs single entry, inside a `LibraryConfig` in a generation_config.yaml +""" +class GapicConfig: + def __init__( + self, + proto_path: str, + ): + self.proto_path = proto_path diff --git a/library_generation/model/GenerationConfig.py b/library_generation/model/GenerationConfig.py new file mode 100644 index 0000000000..77273b10eb --- /dev/null +++ b/library_generation/model/GenerationConfig.py @@ -0,0 +1,91 @@ +""" +Class that represents the root of a generation_config.yaml +""" +import yaml +from typing import List, Optional, Dict +from .LibraryConfig import LibraryConfig +from .GapicConfig import GapicConfig + + +class GenerationConfig: + def __init__( + self, + gapic_generator_version: str, + grpc_version: Optional[str], + protobuf_version: Optional[str], + googleapis_commitish: str, + owlbot_cli_image: str, + synthtool_commitish: str, + destination_path: Optional[str], + libraries: List[LibraryConfig], + ): + self.gapic_generator_version = gapic_generator_version + self.grpc_version = grpc_version + self.protobuf_version = protobuf_version + self.googleapis_commitish = googleapis_commitish + self.owlbot_cli_image = owlbot_cli_image + self.synthtool_commitish = synthtool_commitish + self.destination_path = destination_path + self.libraries = libraries + + """ + Parses a yaml located in path_to_yaml. Returns the parsed configuration represented + by the "model" classes + """ + @staticmethod + def from_yaml(path_to_yaml: str): + config = None + with open(path_to_yaml, 'r') as file_stream: + config = yaml.load(file_stream, yaml.Loader) + + libraries = _required(config, 'libraries') + + parsed_libraries = list() + for library in libraries: + gapics = _required(library, 'GAPICs') + + parsed_gapics = list() + for gapic in gapics: + proto_path = _required(gapic, 'proto_path') + new_gapic = GapicConfig(proto_path) + parsed_gapics.append(new_gapic) + + new_library = LibraryConfig( + _required(library, 'api_shortname'), + _optional(library, 'name_pretty', None), + _required(library, 'library_type'), + _optional(library, 'artifact_id', None), + _optional(library, 'api_description', None), + _optional(library, 'product_documentation', None), + _optional(library, 'client_documentation', None), + _optional(library, 'rest_documentation', None), + _optional(library, 'rpc_documentation', None), + parsed_gapics, + _optional(library, 'googleapis_commitish', None), + _optional(library, 'group_id', 'com.google.cloud'), + _optional(library, 'requires_billing', None), + ) + parsed_libraries.append(new_library) + + parsed_config = GenerationConfig( + _required(config, 'gapic_generator_version'), + _optional(config, 'grpc_version', None), + _optional(config, 'protobuf_version', None), + _required(config, 'googleapis_commitish'), + _required(config, 'owlbot_cli_image'), + _required(config, 'synthtool_commitish'), + _optional(config, 'destination_path', None), + parsed_libraries + ) + + return parsed_config + +def _required(config: Dict, key: str): + if key not in config: + raise ValueError(f'required key {key} not found in yaml') + return config[key] + +def _optional(config: Dict, key: str, default: any): + if key not in config: + return default + return config[key] diff --git a/library_generation/model/Library.py b/library_generation/model/Library.py new file mode 100644 index 0000000000..e1449443ba --- /dev/null +++ b/library_generation/model/Library.py @@ -0,0 +1,46 @@ +""" +Class that represents a library in a generation_config.yaml file +""" +from typing import Dict, List, Optional +from enum import Enum +from .GapicConfig import GapicConfig + +""" +Two possible library types: + - GAPIC_AUTO: pure generated library + - GAPIC_COMBO: generated library with a handwritten layer +""" +class _LibraryType(Enum): + GAPIC_AUTO = 1 + GAPIC_COMBO = 2 + +class LibraryConfig: + def __init__( + self, + api_shortname: str, + name_pretty: Optional[str], + library_type: _LibraryType, + artifact_id: Optional[str], + api_description: Optional[str], + product_documentation: Optional[str], + client_documentation: Optional[str], + rest_documentation: Optional[str], + rpc_documentation: Optional[str], + gapicConfigs: List[GapicConfig], + googleapis_commitish: Optional[str], + group_id: Optional[str] = 'com.google.cloud', + requires_billing: Optional[bool] = True, + ): + self.api_shortname = api_shortname + self.name_pretty = name_pretty + self.library_type = library_type + self.artifact_id = artifact_id + self.requires_billing = requires_billing + self.api_description = api_description + self.product_documentation = product_documentation + self.client_documentation = client_documentation + self.rest_documentation = rest_documentation + self.rpc_documentation = rpc_documentation + self.group_id = group_id + self.gapicConfigs = gapicConfigs + self.googleapis_commitish = googleapis_commitish diff --git a/library_generation/model/LibraryConfig.py b/library_generation/model/LibraryConfig.py new file mode 100644 index 0000000000..a0d09351ed --- /dev/null +++ b/library_generation/model/LibraryConfig.py @@ -0,0 +1,46 @@ +""" +Class that represents a library in a generation_config.yaml file +""" +from typing import Dict, List, Optional +from enum import Enum +from .GapicConfig import GapicConfig + +""" +Two possible library types: + - GAPIC_AUTO: pure generated library + - GAPIC_COMBO: generated library with a handwritten layer +""" +class _LibraryType(Enum): + GAPIC_AUTO = 1 + GAPIC_COMBO = 2 + +class LibraryConfig: + def __init__( + self, + api_shortname: str, + name_pretty: Optional[str], + library_type: _LibraryType, + artifact_id: Optional[str], + api_description: Optional[str], + product_documentation: Optional[str], + client_documentation: Optional[str], + rest_documentation: Optional[str], + rpc_documentation: Optional[str], + gapic_configs: List[GapicConfig], + googleapis_commitish: Optional[str], + group_id: Optional[str] = 'com.google.cloud', + requires_billing: Optional[bool] = True, + ): + self.api_shortname = api_shortname + self.name_pretty = name_pretty + self.library_type = library_type + self.artifact_id = artifact_id + self.requires_billing = requires_billing + self.api_description = api_description + self.product_documentation = product_documentation + self.client_documentation = client_documentation + self.rest_documentation = rest_documentation + self.rpc_documentation = rpc_documentation + self.group_id = group_id + self.gapic_configs = gapic_configs + self.googleapis_commitish = googleapis_commitish diff --git a/library_generation/new_client/new-client.py b/library_generation/new_client/new-client.py index 5b69f335c8..26d0afb7f3 100644 --- a/library_generation/new_client/new-client.py +++ b/library_generation/new_client/new-client.py @@ -21,8 +21,11 @@ import click import templates from git import Repo -from client_inputs import parse import shutil +current_dir = os.path.dirname(os.path.realpath(__file__)) +parent_dir = os.path.dirname(current_dir) +sys.path.append(parent_dir) +from model.ClientInputs import parse @click.group(invoke_without_command=False) diff --git a/library_generation/owlbot/bin/entrypoint.sh b/library_generation/owlbot/bin/entrypoint.sh index 65e3a5fa2a..26ed707591 100755 --- a/library_generation/owlbot/bin/entrypoint.sh +++ b/library_generation/owlbot/bin/entrypoint.sh @@ -65,6 +65,8 @@ function processModule() { echo "...done" } +# This script can be used to process HW libraries and monorepo +# (google-cloud-java) libraries, which require a slightly different treatment # monorepo folders have an .OwlBot.yaml file in the module folder (e.g. # java-asset/.OwlBot.yaml), whereas HW libraries have the yaml in # `.github/.OwlBot.yaml` diff --git a/library_generation/postprocess_library.sh b/library_generation/postprocess_library.sh index bf07127427..f7035ec6c8 100755 --- a/library_generation/postprocess_library.sh +++ b/library_generation/postprocess_library.sh @@ -13,25 +13,43 @@ # 2 - preprocessed_sources_path: used to transfer the raw grpc, proto and gapic # libraries into the postprocessing_target via copy-code # 3 - versions_file: path to file containing versions to be applied to the poms +# 4 - owlbot_cli_source_folder: alternative folder with a structure exactly like +# googleapis-gen. It will be used instead of preprocessed_sources_path if +# 5 - owlbot_cli_image_sha: SHA of the image containing the OwlBot CLI +# 6 - synthtool_commitish: Commit SHA of the synthtool repo +# provided +# 7 - is_monorepo: whether this library is a monorepo, which implies slightly +# different logic set -xeo pipefail scripts_root=$(dirname "$(readlink -f "$0")") postprocessing_target=$1 preprocessed_sources_path=$2 versions_file=$3 +owlbot_cli_source_folder=$4 +owlbot_cli_image_sha=$5 +synthtool_commitish=$6 +is_monorepo=$7 source "${scripts_root}"/utilities.sh +declare -a required_inputs=("postprocessing_target" "versions_file" "owlbot_cli_image_sha" "synthtool_commitish" "is_monorepo") +for required_input in "${required_inputs[@]}"; do + if [[ -z "${!required_input}" ]]; then + echo "missing required ${required_input} argument, please specify one" + exit 1 + fi +done + for owlbot_file in ".repo-metadata.json" "owlbot.py" ".OwlBot.yaml" do if [[ $(find "${postprocessing_target}" -name "${owlbot_file}" | wc -l) -eq 0 ]]; then echo "necessary file for postprocessing '${owlbot_file}' was not found in postprocessing_target" - echo "please provide a postprocessing_target folder that is java owlbot compatible" + echo "please provide a postprocessing_target folder that is compatible with the OwlBot Java postprocessor" exit 1 fi done -proto_path=$(get_proto_path_from_preprocessed_sources "${preprocessed_sources_path}") # ensure pyenv scripts are available eval "$(pyenv init --path)" @@ -48,45 +66,31 @@ if [ $(pyenv virtualenvs | grep "${python_version}" | grep "postprocessing" | wc fi pyenv activate "postprocessing" -# call owl-bot-copy -owlbot_staging_folder="${postprocessing_target}/owl-bot-staging" -mkdir -p "${owlbot_staging_folder}" -echo 'Running owl-bot-copy' -pre_processed_libs_folder=$(mktemp -d) -# By default (thanks to generation templates), .OwlBot.yaml `deep-copy` section -# references a wildcard pattern matching a folder -# ending with `-java` at the leaf of proto_path. We then use a generated-java -# folder that will be picked up by copy-code -mkdir -p "${pre_processed_libs_folder}/${proto_path}/generated-java" -copy_directory_if_exists "${preprocessed_sources_path}" "proto" \ - "${pre_processed_libs_folder}/${proto_path}/generated-java/proto-google-cloud-library" -copy_directory_if_exists "${preprocessed_sources_path}" "grpc" \ - "${pre_processed_libs_folder}/${proto_path}/generated-java/grpc-google-cloud-library" -copy_directory_if_exists "${preprocessed_sources_path}" "gapic" \ - "${pre_processed_libs_folder}/${proto_path}/generated-java/gapic-google-cloud-library" -copy_directory_if_exists "${preprocessed_sources_path}" "samples" \ - "${pre_processed_libs_folder}/${proto_path}/generated-java/samples" -pushd "${pre_processed_libs_folder}" -# create an empty commit so owl-bot-copy can process this as a repo -# (it cannot process non-git-repositories) -git init -git commit --allow-empty -m 'empty commit' -popd # pre_processed_libs_folder +if [[ -z "${owlbot_cli_source_folder}" ]]; then + owlbot_cli_source_folder=$(mktemp -d) + build_owlbot_cli_source_folder "${postprocessing_target}" "${owlbot_cli_source_folder}" "${preprocessed_sources_path}" +fi -owlbot_cli_image_sha=$(cat "${scripts_root}/configuration/owlbot-cli-sha" | grep "sha256") +# we determine the location of the .OwlBot.yaml file by checking if the target +# folder is a monorepo folder or not +if [[ "${postprocessing_target}" == *google-cloud-java* ]]; then + owlbot_yaml_relative_path=".OwlBot.yaml" +else + owlbot_yaml_relative_path=".github/.OwlBot.yaml" +fi docker run --rm \ --user $(id -u):$(id -g) \ -v "${postprocessing_target}:/repo" \ - -v "${pre_processed_libs_folder}:/pre-processed-libraries" \ + -v "${owlbot_cli_source_folder}:/pre-processed-libraries" \ -w /repo \ --env HOME=/tmp \ gcr.io/cloud-devrel-public-resources/owlbot-cli@"${owlbot_cli_image_sha}" \ copy-code \ --source-repo-commit-hash=none \ --source-repo=/pre-processed-libraries \ - --config-file=.OwlBot.yaml + --config-file="${owlbot_yaml_relative_path}" # we clone the synthtool library and manually build it mkdir -p /tmp/synthtool @@ -95,7 +99,6 @@ if [ ! -d "synthtool" ]; then git clone https://github.com/googleapis/synthtool.git fi pushd "synthtool" -synthtool_commitish=$(cat "${scripts_root}/configuration/synthtool-commitish") git reset --hard "${synthtool_commitish}" python3 -m pip install -e . python3 -m pip install -r requirements.in diff --git a/library_generation/requirements.in b/library_generation/requirements.in new file mode 100644 index 0000000000..2bd5a0b0a8 --- /dev/null +++ b/library_generation/requirements.in @@ -0,0 +1,17 @@ +absl-py==2.0.0 +attr==0.3.2 +attrs==23.2.0 +black==23.12.1 +click==8.1.7 +gitdb==4.0.11 +GitPython==3.1.40 +Jinja2==3.1.2 +lxml==5.0.0 +MarkupSafe==2.1.3 +mypy-extensions==1.0.0 +packaging==23.2 +pathspec==0.12.1 +platformdirs==4.1.0 +PyYAML==6.0.1 +smmap==5.0.1 +typing==3.7.4.3 diff --git a/library_generation/test/__init__.py b/library_generation/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/library_generation/test/compare_poms.py b/library_generation/test/compare_poms.py index c2abd8da13..94c94ae128 100644 --- a/library_generation/test/compare_poms.py +++ b/library_generation/test/compare_poms.py @@ -4,16 +4,15 @@ The only comparison points are: element path (e.g. project/dependencies) and element text There is a special case for `dependency`, where the maven coordinates are prepared as well """ - -import sys import xml.etree.ElementTree as ET from collections import Counter +import sys +import os +current = os.path.dirname(os.path.realpath(__file__)) +parent = os.path.dirname(current) +sys.path.append(parent) +from utilities import eprint -""" -prints to stderr -""" -def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) """ Convenience method to access a node's child elements via path and get its text diff --git a/library_generation/test/generate_library_integration_test.sh b/library_generation/test/generate_library_integration_test.sh index f6084cf241..9b46304da3 100755 --- a/library_generation/test/generate_library_integration_test.sh +++ b/library_generation/test/generate_library_integration_test.sh @@ -5,14 +5,11 @@ set -xeo pipefail # This script is used to test the result of `generate_library.sh` against generated # source code in the specified repository. # Specifically, this script will do -# 1. checkout the master branch of googleapis/google and WORKSPACE -# 2. parse version of gapic-generator-java, protobuf and grpc from WORKSPACE -# 3. generate a library with proto_path and destination_path in a proto_path -# list by invoking `generate_library.sh`. GAPIC options to generate a library -# will be parsed from proto_path/BUILD.bazel. -# 4. depending on whether postprocessing is enabled, -# 4.1 checkout the master branch of googleapis-gen repository and compare the result, or -# 4.2 checkout the master branch of google-cloud-java or HW library repository and compare the result +# 1. take a configuration yaml describing the structure of the libraries to +# generate +# 2. For each api_shortname, call generate_composed_library.py to generate the groups of libraries +# 3. After the generation is done, compare the resulting library with the +# corresponding cloned repository # defaults googleapis_gen_url="git@github.com:googleapis/googleapis-gen.git" @@ -25,6 +22,7 @@ source "${script_dir}/test_utilities.sh" source "${script_dir}/../utilities.sh" output_folder="$(pwd)/output" + while [[ $# -gt 0 ]]; do key="$1" case $key in @@ -40,10 +38,6 @@ case $key in googleapis_gen_url="$2" shift ;; - -v|--versions_file) - versions_file="$2" - shift - ;; *) echo "Invalid option: [$1]" exit 1 @@ -53,166 +47,105 @@ shift # past argument or value done mkdir -p "${output_folder}" -pushd "${output_folder}" -# checkout the master branch of googleapis/google (proto files) and WORKSPACE -echo "Checking out googlapis repository..." -# sparse_clone will remove folder contents first, so we have to checkout googleapis -# only once. -sparse_clone https://github.com/googleapis/googleapis.git "google grafeas WORKSPACE" -pushd googleapis -cp -r google "${output_folder}" -cp -r grafeas "${output_folder}" -# parse version of gapic-generator-java, protobuf and grpc from WORKSPACE -gapic_generator_version=$(get_version_from_WORKSPACE "_gapic_generator_java_version" WORKSPACE "=") -echo "The version of gapic-generator-java is ${gapic_generator_version}." -protobuf_version=$(get_version_from_WORKSPACE "protobuf-" WORKSPACE "-") -echo "The version of protobuf is ${protobuf_version}" -popd # googleapis -popd # output_folder + if [ -f "${output_folder}/generation_times" ];then rm "${output_folder}/generation_times" fi -if [ -z "${versions_file}" ]; then - # google-cloud-java will be downloaded before each call of - # `generate_library.sh` - versions_file="${output_folder}/google-cloud-java/versions.txt" -fi -grep -v '^ *#' < "${proto_path_list}" | while IFS= read -r line; do - proto_path=$(echo "$line" | cut -d " " -f 1) - repository_path=$(echo "$line" | cut -d " " -f 2) - skip_postprocessing=$(echo "$line" | cut -d " " -f 3) - # parse destination_path - pushd "${output_folder}" - echo "Checking out googleapis-gen repository..." - sparse_clone "${googleapis_gen_url}" "${proto_path}" - destination_path=$(compute_destination_path "${proto_path}" "${output_folder}") - # parse GAPIC options from proto_path/BUILD.bazel - proto_build_file_path="${proto_path}/BUILD.bazel" - proto_only=$(get_proto_only_from_BUILD "${proto_build_file_path}") - gapic_additional_protos=$(get_gapic_additional_protos_from_BUILD "${proto_build_file_path}") - transport=$(get_transport_from_BUILD "${proto_build_file_path}") - rest_numeric_enums=$(get_rest_numeric_enums_from_BUILD "${proto_build_file_path}") - gapic_yaml=$(get_gapic_yaml_from_BUILD "${proto_build_file_path}") - service_config=$(get_service_config_from_BUILD "${proto_build_file_path}") - service_yaml=$(get_service_yaml_from_BUILD "${proto_build_file_path}") - include_samples=$(get_include_samples_from_BUILD "${proto_build_file_path}") - popd # output_folder - echo "GAPIC options are - transport=${transport}, - rest_numeric_enums=${rest_numeric_enums}, - gapic_yaml=${gapic_yaml}, - service_config=${service_config}, - service_yaml=${service_yaml}, - include_samples=${include_samples}." - pushd "${output_folder}" - if [ "${skip_postprocessing}" == "true" ]; then - echo 'this library is not intended for postprocessing test' - popd # output folder - continue - else - echo 'this is a monorepo library' - sparse_clone "https://github.com/googleapis/google-cloud-java.git" "${repository_path} google-cloud-pom-parent google-cloud-jar-parent versions.txt .github" +declare -a configuration_yamls=( + "${script_dir}/resources/integration/java-bigtable/generation_config.yaml" + "${script_dir}/resources/integration/google-cloud-java/generation_config.yaml" +) - # compute path from output_folder to source of truth library location - # (e.g. google-cloud-java/java-compute) - repository_path="google-cloud-java/${repository_path}" - target_folder="${output_folder}/${repository_path}" - popd # output_folder - fi - # generate GAPIC client library - echo "Generating library from ${proto_path}, to ${destination_path}..." - generation_start=$(date "+%s") - if [ "${enable_postprocessing}" == "true" ]; then - if [[ "${repository_path}" == "null" ]]; then - # we need a repository to compare the generated results with. Skip this - # library - continue - fi - "${library_generation_dir}"/generate_library.sh \ - -p "${proto_path}" \ - -d "${repository_path}" \ - --gapic_generator_version "${gapic_generator_version}" \ - --protobuf_version "${protobuf_version}" \ - --proto_only "${proto_only}" \ - --gapic_additional_protos "${gapic_additional_protos}" \ - --transport "${transport}" \ - --rest_numeric_enums "${rest_numeric_enums}" \ - --gapic_yaml "${gapic_yaml}" \ - --service_config "${service_config}" \ - --service_yaml "${service_yaml}" \ - --include_samples "${include_samples}" \ - --enable_postprocessing "true" \ - --versions_file "${output_folder}/google-cloud-java/versions.txt" + +for configuration_yaml in "${configuration_yamls[@]}"; do + library_api_shortnames=$(py_util "get_configuration_yaml_library_api_shortnames" "${configuration_yaml}") + destination_path=$(py_util "get_configuration_yaml_destination_path" "${configuration_yaml}") + pushd "${output_folder}" + if [[ "${destination_path}" == *google-cloud-java* ]]; then + git clone "https://github.com/googleapis/google-cloud-java" + repository_path="${output_folder}/google-cloud-java" else - "${library_generation_dir}"/generate_library.sh \ - -p "${proto_path}" \ - -d "${destination_path}" \ - --gapic_generator_version "${gapic_generator_version}" \ - --protobuf_version "${protobuf_version}" \ - --proto_only "${proto_only}" \ - --gapic_additional_protos "${gapic_additional_protos}" \ - --transport "${transport}" \ - --rest_numeric_enums "${rest_numeric_enums}" \ - --gapic_yaml "${gapic_yaml}" \ - --service_config "${service_config}" \ - --service_yaml "${service_yaml}" \ - --include_samples "${include_samples}" \ - --enable_postprocessing "false" + git clone "https://github.com/googleapis/${destination_path}" + repository_path="${output_folder}/${destination_path}" fi - generation_end=$(date "+%s") - # some generations are less than 1 second (0 produces exit code 1 in `expr`) - generation_duration_seconds=$(expr "${generation_end}" - "${generation_start}" || true) - echo "Generation time for ${repository_path} was ${generation_duration_seconds} seconds." - pushd "${output_folder}" - echo "${proto_path} ${generation_duration_seconds}" >> generation_times - - echo "Generate library finished." - echo "Compare generation result..." - if [ $enable_postprocessing == "true" ]; then - echo "Checking out repository..." - pushd "${target_folder}" - source_diff_result=0 - git diff \ - --ignore-space-at-eol \ - -r \ - --exit-code \ - -- \ - ':!*pom.xml' \ - ':!*README.md' \ - ':!*package-info.java' \ - || source_diff_result=$? - - pom_diff_result=$(compare_poms "${target_folder}") - popd # target_folder - if [[ ${source_diff_result} == 0 ]] && [[ ${pom_diff_result} == 0 ]] ; then - echo "SUCCESS: Comparison finished, no difference is found." - # Delete google-cloud-java to allow a sparse clone of the next library - rm -rdf google-cloud-java - elif [ ${source_diff_result} != 0 ]; then - echo "FAILURE: Differences found in proto path: ${proto_path}." - exit "${source_diff_result}" - elif [ ${pom_diff_result} != 0 ]; then - echo "FAILURE: Differences found in generated poms" - exit "${pom_diff_result}" - fi - elif [ "${enable_postprocessing}" == "false" ]; then - # include gapic_metadata.json and package-info.java after - # resolving https://github.com/googleapis/sdk-platform-java/issues/1986 - source_diff_result=0 - diff --strip-trailing-cr -r "googleapis-gen/${proto_path}/${destination_path}" "${output_folder}/${destination_path}" \ - -x "*gradle*" \ - -x "gapic_metadata.json" \ - -x "package-info.java" || source_diff_result=$? - if [ ${source_diff_result} == 0 ] ; then - echo "SUCCESS: Comparison finished, no difference is found." - else - echo "FAILURE: Differences found in proto path: ${proto_path}." - exit "${source_diff_result}" + popd + + for api_shortname in ${library_api_shortnames}; do + pushd "${output_folder}" + + echo "Generating library ${api_shortname}..." + generation_start=$(date "+%s") + python3 "${library_generation_dir}"/main.py generate-from-yaml \ + --generation-config-yaml "${configuration_yaml}" \ + --enable-postprocessing "${enable_postprocessing}" \ + --target-library-api-shortname "${api_shortname}" \ + --repository-path "${repository_path}" + generation_end=$(date "+%s") + + # some generations are less than 1 second (0 produces exit code 1 in `expr`) + generation_duration_seconds=$(expr "${generation_end}" - "${generation_start}" || true) + echo "Generation time for ${api_shortname} was ${generation_duration_seconds} seconds." + pushd "${output_folder}" + echo "${proto_path} ${generation_duration_seconds}" >> generation_times + + echo "Generate library finished." + echo "Compare generation result..." + if [ ${enable_postprocessing} == "true" ]; then + echo "Checking out repository..." + if [[ "${destination_path}" == *google-cloud-java* ]]; then + target_folder="${output_folder}/google-cloud-java/java-${api_shortname}" + else + target_folder="${output_folder}/java-${api_shortname}" + fi + + pushd "${target_folder}" + source_diff_result=0 + git diff \ + --ignore-space-at-eol \ + -r \ + --exit-code \ + -- \ + . \ + ':!*pom.xml' \ + ':!*README.md' \ + ':!*gapic_metadata.json' \ + ':!*reflect-config.json' \ + ':!*package-info.java' \ + || source_diff_result=$? + + pom_diff_result=$(compare_poms "${target_folder}") + popd # target_folder + if [[ ${source_diff_result} == 0 ]] && [[ ${pom_diff_result} == 0 ]] ; then + echo "SUCCESS: Comparison finished, no difference is found." + elif [ ${source_diff_result} != 0 ]; then + echo "FAILURE: Differences found in proto path: java-${api_shortname}." + exit "${source_diff_result}" + elif [ ${pom_diff_result} != 0 ]; then + echo "FAILURE: Differences found in generated java-${api_shortname}'s poms" + exit "${pom_diff_result}" + fi + elif [ "${enable_postprocessing}" == "false" ]; then + for proto_path in "${proto_paths[@]}"; do + destination_path=$(compute_destination_path "${proto_path}" "${output_folder}") + # include gapic_metadata.json and package-info.java after + # resolving https://github.com/googleapis/sdk-platform-java/issues/1986 + source_diff_result=0 + diff --strip-trailing-cr -r "googleapis-gen/${proto_path}/${destination_path}" "${output_folder}/${destination_path}" \ + -x "*gradle*" \ + -x "gapic_metadata.json" \ + -x "package-info.java" || source_diff_result=$? + if [ ${source_diff_result} == 0 ] ; then + echo "SUCCESS: Comparison finished, no difference is found." + else + echo "FAILURE: Differences found in proto path: ${proto_path}." + exit "${source_diff_result}" + fi + done fi - fi - popd # output_folder + popd # output_folder + done done echo "ALL TESTS SUCCEEDED" echo "generation times in seconds (does not consider repo checkout):" diff --git a/library_generation/test/generate_library_unit_tests.sh b/library_generation/test/generate_library_unit_tests.sh index 6fde314788..e9f4954298 100755 --- a/library_generation/test/generate_library_unit_tests.sh +++ b/library_generation/test/generate_library_unit_tests.sh @@ -208,103 +208,6 @@ generate_library_failed_with_invalid_grpc_version() { cleanup "${destination}" } -get_gapic_additional_protos_from_BUILD_common_resources_test() { - local proto_path="${script_dir}/resources/search_additional_protos/BUILD_common_resources.bazel" - local addition_protos - addition_protos=$(get_gapic_additional_protos_from_BUILD "${proto_path}") - assertEquals "google/cloud/common_resources.proto" "${addition_protos}" -} - -get_gapic_additional_protos_from_BUILD_iam_policy_test() { - local proto_path="${script_dir}/resources/search_additional_protos/BUILD_iam_policy.bazel" - local addition_protos - addition_protos=$(get_gapic_additional_protos_from_BUILD "${proto_path}") - assertEquals "google/cloud/common_resources.proto google/iam/v1/iam_policy.proto" "${addition_protos}" -} - -get_gapic_additional_protos_from_BUILD_locations_test() { - local proto_path="${script_dir}/resources/search_additional_protos/BUILD_locations.bazel" - local addition_protos - addition_protos=$(get_gapic_additional_protos_from_BUILD "${proto_path}") - assertEquals "google/cloud/common_resources.proto google/cloud/location/locations.proto" "${addition_protos}" -} - -get_gapic_additional_protos_from_BUILD_iam_locations_test() { - local proto_path="${script_dir}/resources/search_additional_protos/BUILD_iam_locations.bazel" - local addition_protos - addition_protos=$(get_gapic_additional_protos_from_BUILD "${proto_path}") - assertEquals "google/cloud/common_resources.proto google/iam/v1/iam_policy.proto google/cloud/location/locations.proto" "${addition_protos}" -} - -get_transport_from_BUILD_grpc_rest_test() { - local build_file="${script_dir}/resources/misc/BUILD_grpc_rest.bazel" - local transport - transport=$(get_transport_from_BUILD "${build_file}") - assertEquals "grpc+rest" "${transport}" -} - -get_transport_from_BUILD_grpc_test() { - local build_file="${script_dir}/resources/misc/BUILD_grpc.bazel" - local transport - transport=$(get_transport_from_BUILD "${build_file}") - assertEquals "grpc" "${transport}" -} - -get_transport_from_BUILD_rest_test() { - local build_file="${script_dir}/resources/misc/BUILD_rest.bazel" - local transport - transport=$(get_transport_from_BUILD "${build_file}") - assertEquals "rest" "${transport}" -} - -get_rest_numeric_enums_from_BUILD_true_test() { - local build_file="${script_dir}/resources/misc/BUILD_rest_numeric_enums_true.bazel" - local rest_numeric_enums - rest_numeric_enums=$(get_rest_numeric_enums_from_BUILD "${build_file}") - assertEquals "true" "${rest_numeric_enums}" -} - -get_rest_numeric_enums_from_BUILD_false_test() { - local build_file="${script_dir}/resources/misc/BUILD_rest_numeric_enums_false.bazel" - local rest_numeric_enums - rest_numeric_enums=$(get_rest_numeric_enums_from_BUILD "${build_file}") - assertEquals "false" "${rest_numeric_enums}" -} - -get_rest_numeric_enums_from_BUILD_empty_test() { - local build_file="${script_dir}/resources/misc/BUILD_rest_numeric_enums_empty.bazel" - local rest_numeric_enums - rest_numeric_enums=$(get_rest_numeric_enums_from_BUILD "${build_file}") - assertEquals "false" "${rest_numeric_enums}" -} - -get_include_samples_from_BUILD_true_test() { - local build_file="${script_dir}/resources/misc/BUILD_include_samples_true.bazel" - local include_samples - include_samples=$(get_include_samples_from_BUILD "${build_file}") - assertEquals "true" "${include_samples}" -} - -get_include_samples_from_BUILD_false_test() { - local build_file="${script_dir}/resources/misc/BUILD_include_samples_false.bazel" - local include_samples - include_samples=$(get_include_samples_from_BUILD "${build_file}") - assertEquals "false" "${include_samples}" -} - -get_include_samples_from_BUILD_empty_test() { - local build_file="${script_dir}/resources/misc/BUILD_include_samples_empty.bazel" - local include_samples - include_samples=$(get_include_samples_from_BUILD "${build_file}") - assertEquals "false" "${include_samples}" -} - -get_version_from_valid_WORKSPACE_test() { - workspace_file="${script_dir}/resources/misc/TESTWORKSPACE" - obtained_ggj_version=$(get_version_from_WORKSPACE "_gapic_generator_java_version" "${workspace_file}") - assertEquals '2.25.1-SNAPSHOT' "${obtained_ggj_version}" -} - copy_directory_if_exists_valid_folder_succeeds() { local source_folder="${script_dir}/resources" local destination="${script_dir}/test_destination_folder" @@ -372,20 +275,6 @@ test_list=( generate_library_failed_with_invalid_generator_version generate_library_failed_with_invalid_protobuf_version generate_library_failed_with_invalid_grpc_version - get_gapic_additional_protos_from_BUILD_common_resources_test - get_gapic_additional_protos_from_BUILD_iam_policy_test - get_gapic_additional_protos_from_BUILD_locations_test - get_gapic_additional_protos_from_BUILD_iam_locations_test - get_transport_from_BUILD_grpc_rest_test - get_transport_from_BUILD_grpc_test - get_transport_from_BUILD_rest_test - get_rest_numeric_enums_from_BUILD_true_test - get_rest_numeric_enums_from_BUILD_false_test - get_rest_numeric_enums_from_BUILD_empty_test - get_include_samples_from_BUILD_true_test - get_include_samples_from_BUILD_false_test - get_include_samples_from_BUILD_empty_test - get_version_from_valid_WORKSPACE_test copy_directory_if_exists_valid_folder_succeeds copy_directory_if_exists_invalid_folder_does_not_copy get_proto_path_from_preprocessed_sources_valid_library_succeeds diff --git a/library_generation/test/resources/integration/google-cloud-java/generation_config.yaml b/library_generation/test/resources/integration/google-cloud-java/generation_config.yaml index f8c2808739..7b73f329d0 100644 --- a/library_generation/test/resources/integration/google-cloud-java/generation_config.yaml +++ b/library_generation/test/resources/integration/google-cloud-java/generation_config.yaml @@ -1,25 +1,25 @@ #Required. -gapic_generator_version: 2.30.0 +gapic_generator_version: 2.32.0 #Optional. -grpc_version: 1.59.1 -#Optional. -protobuf_version: 3.25.1 -#Required. -googleapis-commitish: 4512234113a18c1fda1fb0d0ceac8f4b4efe9801 -#Required. -owlbot-cli-image: sha256:623647ee79ac605858d09e60c1382a716c125fb776f69301b72de1cd35d49409 +# grpc_version: 1.60.0 +#Optional. The protobuf version in googleapis (not sdk-platform-java) is the actual source of truth for generated protos in google-cloud-java +protobuf_version: 23.2 #Required. -synthtool-commitish: 59fe44fde9866a26e7ee4e4450fd79f67f8cf599 +googleapis_commitish: 4512234113a18c1fda1fb0d0ceac8f4b4efe9801 #Required. -python-version: 3.11.2 -#Optional. The root folder name of generated client libraries. If empty, modules will be created under current folder, useful for single module -destination-path: google-cloud-java +owlbot_cli_image: sha256:623647ee79ac605858d09e60c1382a716c125fb776f69301b72de1cd35d49409 #Required. +synthtool_commitish: fac8444edd5f5526e804c306b766a271772a3e2f +#Required. The root folder name of generated client libraries. +destination_path: google-cloud-java +#Required. If the number of libraries is greater than 1, the scripts will treat the target repository as a monorepo, with a slightly different workflow mainly in the postprocessing stage libraries: #Required. Can be used for populating the folder name java-{api_shortName}. This is also the destination-name in new-client.py. - api_shortname: asset + #Optional. Overrides the root-level commit hash + googleapis_commitish: 4512234113a18c1fda1fb0d0ceac8f4b4efe9801 #Optional. The default value is the title of service yaml - name-pretty: Cloud Asset + name_pretty: Cloud Asset #Required. library_type: GAPIC_AUTO #Optional. The default value is com.google.cloud @@ -27,7 +27,7 @@ libraries: #Optional. The default value is google.cloud.{api_shortname} artifact_id: google.cloud.asset #Optional. The default value is true. - requires-billing: true + requires_billing: true #Optional. The default value is documentation.summary from service yaml api_description: #Optional. @@ -48,5 +48,43 @@ libraries: - proto_path: google/cloud/asset/v1p7beta1 - api_shortname: speech library_type: GAPIC_AUTO - services: - - proto_path: google/cloud/asset/v1 + GAPICs: + - proto_path: google/cloud/speech/v1 + - proto_path: google/cloud/speech/v1p1beta1 + - proto_path: google/cloud/speech/v2 + - api_shortname: apigee-connect + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/cloud/apigeeconnect/v1 + - api_shortname: dialogflow + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/cloud/dialogflow/v2beta1 + - proto_path: google/cloud/dialogflow/v2 + - api_shortname: compute + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/cloud/compute/v1 + - api_shortname: kms + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/cloud/kms/v1 + - api_shortname: redis + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/cloud/redis/v1 + - proto_path: google/cloud/redis/v1beta1 + - api_shortname: containeranalysis + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/devtools/containeranalysis/v1 + - api_shortname: iam + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/iam/v1 + - proto_path: google/iam/v2 + - api_shortname: iamcredentials + library_type: GAPIC_AUTO + GAPICs: + - proto_path: google/iam/credentials/v1 + diff --git a/library_generation/test/resources/integration/java-bigtable/generation_config.yaml b/library_generation/test/resources/integration/java-bigtable/generation_config.yaml new file mode 100644 index 0000000000..4a82a3e2c4 --- /dev/null +++ b/library_generation/test/resources/integration/java-bigtable/generation_config.yaml @@ -0,0 +1,14 @@ +gapic_generator_version: 2.32.0 +grpc_version: 1.61.0 +protobuf_version: 23.2 +googleapis_commitish: 4512234113a18c1fda1fb0d0ceac8f4b4efe9801 +owlbot_cli_image: sha256:623647ee79ac605858d09e60c1382a716c125fb776f69301b72de1cd35d49409 +synthtool_commitish: 6612ab8f3afcd5e292aecd647f0fa68812c9f5b5 +destination_path: java-bigtable +libraries: + - api_shortname: bigtable + name_pretty: Cloud Bigtable + library_type: GAPIC_COMBO + GAPICs: + - proto_path: google/bigtable/admin/v2 + - proto_path: google/bigtable/v2 diff --git a/library_generation/test/resources/misc/BUILD_gapic_yaml.bazel b/library_generation/test/resources/misc/BUILD_gapic_yaml.bazel new file mode 100644 index 0000000000..b55f4550d8 --- /dev/null +++ b/library_generation/test/resources/misc/BUILD_gapic_yaml.bazel @@ -0,0 +1,3 @@ +java_gapic_library( + gapic_yaml = "test_gapic_yaml.yaml", +) diff --git a/library_generation/test/resources/misc/BUILD_no_gapic_yaml.bazel b/library_generation/test/resources/misc/BUILD_no_gapic_yaml.bazel new file mode 100644 index 0000000000..1e9462aa30 --- /dev/null +++ b/library_generation/test/resources/misc/BUILD_no_gapic_yaml.bazel @@ -0,0 +1,3 @@ +java_gapic_library( + gapic_yaml = None +) diff --git a/library_generation/test/resources/misc/BUILD_no_service_config.bazel b/library_generation/test/resources/misc/BUILD_no_service_config.bazel new file mode 100644 index 0000000000..dbde6de05c --- /dev/null +++ b/library_generation/test/resources/misc/BUILD_no_service_config.bazel @@ -0,0 +1,3 @@ +java_gapic_library( + grpc_service_config = None +) diff --git a/library_generation/test/resources/misc/BUILD_no_service_yaml.bazel b/library_generation/test/resources/misc/BUILD_no_service_yaml.bazel new file mode 100644 index 0000000000..05bae16d5d --- /dev/null +++ b/library_generation/test/resources/misc/BUILD_no_service_yaml.bazel @@ -0,0 +1,3 @@ +java_gapic_library( + service_yaml = None +) diff --git a/library_generation/test/resources/misc/BUILD_service_config.bazel b/library_generation/test/resources/misc/BUILD_service_config.bazel new file mode 100644 index 0000000000..097d1bb6bd --- /dev/null +++ b/library_generation/test/resources/misc/BUILD_service_config.bazel @@ -0,0 +1,3 @@ +java_gapic_library( + grpc_service_config = "test_service_config.json" +) diff --git a/library_generation/test/resources/misc/BUILD_service_yaml.bazel b/library_generation/test/resources/misc/BUILD_service_yaml.bazel new file mode 100644 index 0000000000..f7e4c91f4e --- /dev/null +++ b/library_generation/test/resources/misc/BUILD_service_yaml.bazel @@ -0,0 +1,3 @@ +java_gapic_library( + service_yaml = "test_service_yaml.yaml" +) diff --git a/library_generation/test/resources/proto_path_list.txt b/library_generation/test/resources/proto_path_list.txt deleted file mode 100755 index 5f82059e52..0000000000 --- a/library_generation/test/resources/proto_path_list.txt +++ /dev/null @@ -1,25 +0,0 @@ -# This file is used in integration test against `generate_library.sh`. -# Format: -# proto_path repository_path skip_postprocessing_test -# google/bigtable/admin/v2 java-bigtable true -# google/bigtable/v2 java-bigtable true -google/cloud/apigeeconnect/v1 java-apigee-connect false -google/cloud/asset/v1p5beta1 java-asset false -# google/cloud/asset/v1p2beta1 java-asset false -google/cloud/asset/v1p1beta1 java-asset false -google/cloud/asset/v1p7beta1 java-asset false -google/cloud/asset/v1 java-asset false -# google/cloud/dialogflow/v2beta1 java-dialogflow false -# google/cloud/dialogflow/v2 java-dialogflow false -# google/cloud/compute/v1 java-compute false -google/cloud/kms/v1 java-kms false -google/cloud/redis/v1 java-redis false -google/cloud/redis/v1beta1 java-redis false -# google/example/library/v1 google-cloud-example-library-v1-java null false -google/devtools/containeranalysis/v1 java-containeranalysis false -google/iam/v1 java-iam true -google/iam/v2 java-iam false -google/iam/credentials/v1 java-iamcredentials false -google/logging/v2 java-logging true -google/pubsub/v1 java-pubsub true -google/storage/v2 java-storage true diff --git a/library_generation/test/test_utilities.sh b/library_generation/test/test_utilities.sh index 3da3bd0392..007dc8e6d9 100755 --- a/library_generation/test/test_utilities.sh +++ b/library_generation/test/test_utilities.sh @@ -26,90 +26,6 @@ __test_failed() { failed_tests="${failed_tests} ${failed_test}" } -# Used to obtain configuration values from a bazel BUILD file -# -# inspects a $build_file for a certain $rule (e.g. java_gapic_library). If the -# first 15 lines after the declaration of the rule contain $pattern, then -# it will return $if_match if $pattern is found, otherwise $default -__get_config_from_BUILD() { - build_file=$1 - rule=$2 - pattern=$3 - default=$4 - if_match=$5 - - result="${default}" - if grep -A 20 "${rule}" "${build_file}" | grep -q "${pattern}"; then - result="${if_match}" - fi - echo "${result}" -} - -__get_gapic_option_from_BUILD() { - local build_file=$1 - local pattern=$2 - local gapic_option - local file_path - gapic_option=$(grep "${pattern}" "${build_file}" |\ - head -1 |\ - sed 's/.*\"\([^]]*\)\".*/\1/g' |\ - sed 's/^[[:space:]]*//;s/[[:space:]]*$//' - ) - if [ -z "${gapic_option}" ] || [[ "${gapic_option}" == *"None"* ]]; then - echo "" - return - fi - - if [[ "${gapic_option}" == ":"* ]] || [[ "${gapic_option}" == "*"* ]]; then - # if gapic_option starts with : or *, remove the first character. - gapic_option="${gapic_option:1}" - elif [[ "${gapic_option}" == "//"* ]]; then - # gapic option is a bazel target, use the file path and name directly. - # remove the leading "//". - gapic_option="${gapic_option:2}" - # replace ":" with "/" - gapic_option="${gapic_option//://}" - echo "${gapic_option}" - return - fi - - file_path="${build_file%/*}" - # Make sure gapic option (*.yaml or *.json) exists in proto_path; otherwise - # reset gapic option to empty string. - if [ -f "${file_path}/${gapic_option}" ]; then - gapic_option="${file_path}/${gapic_option}" - else - echo "WARNING: file ${file_path}/${gapic_option} does not exist, reset gapic option to empty string." >&2 - gapic_option="" - fi - echo "${gapic_option}" -} - -__get_iam_policy_from_BUILD() { - local build_file=$1 - local contains_iam_policy - contains_iam_policy=$(__get_config_from_BUILD \ - "${build_file}" \ - "proto_library_with_info(" \ - "//google/iam/v1:iam_policy_proto" \ - "false" \ - "true" - ) - echo "${contains_iam_policy}" -} - -__get_locations_from_BUILD() { - local build_file=$1 - local contains_locations - contains_locations=$(__get_config_from_BUILD \ - "${build_file}" \ - "proto_library_with_info(" \ - "//google/cloud/location:location_proto" \ - "false" \ - "true" - ) - echo "${contains_locations}" -} ############# Functions used in test execution ############# @@ -167,136 +83,11 @@ execute_tests() { } ############# Utility functions used in `generate_library_integration_tests.sh` ############# -get_proto_only_from_BUILD() { - local build_file=$1 - local proto_only - proto_only=$(__get_config_from_BUILD \ - "${build_file}" \ - "java_gapic_library(" \ - "java_gapic_library" \ - "true" \ - "false" - ) - echo "${proto_only}" -} - -# Apart from proto files in proto_path, additional protos are needed in order -# to generate GAPIC client libraries. -# In most cases, these protos should be within google/ directory, which is -# pulled from googleapis as a prerequisite. -# Get additional protos in BUILD.bazel. -get_gapic_additional_protos_from_BUILD() { - local build_file=$1 - local gapic_additional_protos="google/cloud/common_resources.proto" - if [[ $(__get_iam_policy_from_BUILD "${build_file}") == "true" ]]; then - gapic_additional_protos="${gapic_additional_protos} google/iam/v1/iam_policy.proto" - fi - if [[ $(__get_locations_from_BUILD "${build_file}") == "true" ]]; then - gapic_additional_protos="${gapic_additional_protos} google/cloud/location/locations.proto" - fi - echo "${gapic_additional_protos}" -} - -get_transport_from_BUILD() { - local build_file=$1 - local transport - transport=$(__get_config_from_BUILD \ - "${build_file}" \ - "java_gapic_library(" \ - "grpc+rest" \ - "grpc" \ - "grpc+rest" - ) - # search again because the transport maybe `rest`. - transport=$(__get_config_from_BUILD \ - "${build_file}" \ - "java_gapic_library(" \ - "transport = \"rest\"" \ - "${transport}" \ - "rest" - ) - echo "${transport}" -} - -get_rest_numeric_enums_from_BUILD() { - local build_file=$1 - local rest_numeric_enums - rest_numeric_enums=$(__get_config_from_BUILD \ - "${build_file}" \ - "java_gapic_library(" \ - "rest_numeric_enums = True" \ - "false" \ - "true" - ) - echo "${rest_numeric_enums}" -} - -get_gapic_yaml_from_BUILD() { - local build_file=$1 - local gapic_yaml - gapic_yaml=$(__get_gapic_option_from_BUILD "${build_file}" "gapic_yaml = ") - echo "${gapic_yaml}" -} - -get_service_config_from_BUILD() { - local build_file=$1 - local service_config - service_config=$(__get_gapic_option_from_BUILD "${build_file}" "grpc_service_config = ") - echo "${service_config}" -} - -get_service_yaml_from_BUILD() { - local build_file=$1 - local service_yaml - service_yaml=$(__get_gapic_option_from_BUILD "${build_file}" "service_yaml") - echo "${service_yaml}" -} - -get_include_samples_from_BUILD() { - local build_file=$1 - local include_samples - include_samples=$(__get_config_from_BUILD \ - "${build_file}" \ - "java_gapic_assembly_gradle_pkg(" \ - "include_samples = True" \ - "false" \ - "true" - ) - echo "${include_samples}" -} # Obtains a version from a bazel WORKSPACE file # # versions look like "_ggj_version="1.2.3" # It will return 1.2.3 for such example -get_version_from_WORKSPACE() { - version_key_word=$1 - workspace=$2 - version=$(\ - grep "${version_key_word}" "${workspace}" |\ - head -n 1 |\ - sed 's/\(.*\) = "\(.*\)"\(.*\)/\2/' |\ - sed 's/[a-zA-Z-]*//' - ) - echo "${version}" -} - -# Convenience function to clone only the necessary folders from a git repository -sparse_clone() { - repo_url=$1 - paths=$2 - commitish=$3 - clone_dir=$(basename "${repo_url%.*}") - rm -rf "${clone_dir}" - git clone -n --depth=1 --no-single-branch --filter=tree:0 "${repo_url}" - pushd "${clone_dir}" - if [ -n "${commitish}" ]; then - git checkout "${commitish}" - fi - git sparse-checkout set --no-cone ${paths} - git checkout - popd -} # performs a deep structural comparison between the current pom in a git # folder and the one at HEAD. @@ -313,9 +104,9 @@ compare_poms() { set -e result=0 if [ "${os_architecture}" == "linux-x86_64" ]; then - find . -name 'pom.xml' -print0 | xargs -i -0 python "${test_utilities_script_dir}/compare_poms.py" {} {}.new false || result=$? + find . -name 'pom.xml' -print0 | xargs -i -0 python3 "${test_utilities_script_dir}/compare_poms.py" {} {}.new false || result=$? else - find . -name 'pom.xml' -print0 | xargs -I{} -0 python "${test_utilities_script_dir}/compare_poms.py" {} {}.new false || result=$? + find . -name 'pom.xml' -print0 | xargs -I{} -0 python3 "${test_utilities_script_dir}/compare_poms.py" {} {}.new false || result=$? fi popd &> /dev/null # target_dir echo ${result} diff --git a/library_generation/test/unit_tests.py b/library_generation/test/unit_tests.py new file mode 100644 index 0000000000..13d2eaacf9 --- /dev/null +++ b/library_generation/test/unit_tests.py @@ -0,0 +1,190 @@ +""" +Unit tests for utilities.py +""" + +import unittest +import os +import io +import sys +import contextlib +import subprocess +current = os.path.dirname(os.path.realpath(__file__)) +parent = os.path.dirname(current) +sys.path.append(parent) +import utilities as util +from model.GapicConfig import GapicConfig +from model.GenerationConfig import GenerationConfig +from model.ClientInputs import parse as parse_build_file + +script_dir = os.path.dirname(os.path.realpath(__file__)) +resources_dir = os.path.join(script_dir, 'resources') + +class UtilitiesTest(unittest.TestCase): + + CONFIGURATION_YAML_PATH = os.path.join(current, 'resources', 'integration', + 'google-cloud-java', 'generation_config.yaml') + + def test_create_argument_valid_container_succeeds(self): + container_value = 'google/test/v1' + container = GapicConfig(container_value) + argument_key = 'proto_path' + result = util.create_argument(argument_key, container) + self.assertEqual([ f'--{argument_key}', container_value], result) + + def test_create_argument_empty_container_returns_empty_list(self): + container = dict() + argument_key = 'proto_path' + result = util.create_argument(argument_key, container) + self.assertEqual([], result) + + def test_create_argument_none_container_fails(self): + container = None + argument_key = 'proto_path' + result = util.create_argument(argument_key, container) + self.assertEqual([], result) + + def test_get_configuration_yaml_library_api_shortnames_valid_input_returns_valid_list(self): + result = util.get_configuration_yaml_library_api_shortnames(self.CONFIGURATION_YAML_PATH) + self.assertEqual('asset speech apigee-connect dialogflow compute kms ' + + 'redis containeranalysis iam iamcredentials', result) + + def test_get_configuration_yaml_destination_path_returns_valid_destination_path(self): + result = util.get_configuration_yaml_destination_path(self.CONFIGURATION_YAML_PATH) + self.assertEqual('google-cloud-java', result) + + def test_sh_util_existent_function_succeeds(self): + result = util.sh_util('extract_folder_name path/to/folder_name') + self.assertEqual('folder_name', result) + + def test_sh_util_nonexistent_function_fails(self): + with self.assertRaises(RuntimeError): + result = util.sh_util('nonexistent_function') + + def test_eprint_valid_input_succeeds(self): + test_input='This is some test input' + # create a stdio capture object + stderr_capture = io.StringIO() + # run eprint() with the capture object + with contextlib.redirect_stderr(stderr_capture): + util.eprint(test_input) + result = stderr_capture.getvalue() + # print() appends a `\n` each time it's called + self.assertEqual(test_input + '\n', result) + + def test_delete_if_exists_preexisting_temp_files_succeeds(self): + # create temporary directory + # also remove last character (\n) + temp_dir = subprocess.check_output(['mktemp', '-d']).decode()[:-1] + + # add a file and a folder to the temp dir + file = os.path.join(temp_dir, 'temp_file') + with open(file, 'a'): + os.utime(file, None) + folder = os.path.join(temp_dir, 'temp_child_dir') + os.mkdir(folder) + self.assertEqual(2, len(os.listdir(temp_dir))) + + # remove file and folder + util.delete_if_exists(file) + util.delete_if_exists(folder) + self.assertEqual(0, len(os.listdir(temp_dir))) + + def test_client_inputs_parse_grpc_only_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_grpc.bazel') + self.assertEqual('grpc', parsed.transport) + + def test_client_inputs_parse_grpc_only_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_grpc.bazel') + self.assertEqual('grpc', parsed.transport) + + def test_client_inputs_parse_grpc_rest_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_grpc_rest.bazel') + self.assertEqual('grpc+rest', parsed.transport) + + def test_client_inputs_parse_rest_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_rest.bazel') + self.assertEqual('rest', parsed.transport) + + def test_client_inputs_parse_empty_include_samples_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_include_samples_empty.bazel') + self.assertEqual('false', parsed.include_samples) + + def test_client_inputs_parse_include_samples_false_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_include_samples_false.bazel') + self.assertEqual('false', parsed.include_samples) + + def test_client_inputs_parse_include_samples_true_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_include_samples_true.bazel') + self.assertEqual('true', parsed.include_samples) + + def test_client_inputs_parse_empty_rest_numeric_enums_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_rest_numeric_enums_empty.bazel') + self.assertEqual('false', parsed.rest_numeric_enum) + + def test_client_inputs_parse_include_samples_false_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_rest_numeric_enums_false.bazel') + self.assertEqual('false', parsed.rest_numeric_enum) + + def test_client_inputs_parse_include_samples_true_suceeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, '', 'BUILD_rest_numeric_enums_true.bazel') + self.assertEqual('true', parsed.rest_numeric_enum) + + def test_client_inputs_parse_no_gapic_library_returns_proto_only_true(self): + build_file = os.path.join(resources_dir, 'misc') + # include_samples_empty only has a gradle assembly rule + parsed = parse_build_file(build_file, '', 'BUILD_include_samples_empty.bazel') + self.assertEqual('true', parsed.proto_only) + + def test_client_inputs_parse_with_gapic_library_returns_proto_only_false(self): + build_file = os.path.join(resources_dir, 'misc') + # rest.bazel has a java_gapic_library rule + parsed = parse_build_file(build_file, '', 'BUILD_rest.bazel') + self.assertEqual('false', parsed.proto_only) + + def test_client_inputs_parse_gapic_yaml_succeeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, 'test/versioned/path', 'BUILD_gapic_yaml.bazel') + self.assertEqual('test/versioned/path/test_gapic_yaml.yaml', parsed.gapic_yaml) + + def test_client_inputs_parse_no_gapic_yaml_returns_empty_string(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, 'test/versioned/path', 'BUILD_no_gapic_yaml.bazel') + self.assertEqual('', parsed.gapic_yaml) + + def test_client_inputs_parse_service_config_succeeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, 'test/versioned/path', 'BUILD_service_config.bazel') + self.assertEqual('test/versioned/path/test_service_config.json', parsed.service_config) + + def test_client_inputs_parse_no_service_config_returns_empty_string(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, 'test/versioned/path', 'BUILD_no_service_config.bazel') + self.assertEqual('', parsed.service_config) + + def test_client_inputs_parse_service_yaml_succeeds(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, 'test/versioned/path', 'BUILD_service_yaml.bazel') + self.assertEqual('test/versioned/path/test_service_yaml.yaml', parsed.service_yaml) + + def test_client_inputs_parse_no_service_yaml_returns_empty_string(self): + build_file = os.path.join(resources_dir, 'misc') + parsed = parse_build_file(build_file, 'test/versioned/path', 'BUILD_no_service_yaml.bazel') + self.assertEqual('', parsed.service_yaml) + + + + + + +if __name__ == "__main__": + unittest.main() diff --git a/library_generation/utilities.py b/library_generation/utilities.py new file mode 100755 index 0000000000..0772e8b260 --- /dev/null +++ b/library_generation/utilities.py @@ -0,0 +1,125 @@ + +import sys +import subprocess +import os +import shutil +from collections.abc import Sequence +from model.GenerationConfig import GenerationConfig +from typing import List + +script_dir = os.path.dirname(os.path.realpath(__file__)) + + +""" +Generates a list of two elements [argument, value], or returns +an empty array if arg_val is None +""" +def create_argument(arg_key: str, arg_container: object) -> List[str]: + arg_val = getattr(arg_container, arg_key, None) + if arg_val is not None: + return [f'--{arg_key}', f'{arg_val}'] + return [] + +""" +For a given configuration yaml path, it returns a space-separated list of +the api_shortnames contained in such configuration_yaml +""" +def get_configuration_yaml_library_api_shortnames(generation_config_yaml: str) -> List[str]: + config = GenerationConfig.from_yaml(generation_config_yaml) + result = '' + for library in config.libraries: + result += f'{library.api_shortname} ' + return result[:-1] + +""" +For a given configuration yaml path, it returns the destination_path +entry at the root of the yaml +""" +def get_configuration_yaml_destination_path(generation_config_yaml: str) -> str: + config = GenerationConfig.from_yaml(generation_config_yaml) + return config.destination_path or '' + +""" +Runs a process with the given "arguments" list and prints its output. If the process +fails, then the whole program exits +""" +def run_process_and_print_output(arguments: List[str], job_name: str = 'Job'): + # check_output() raises an exception if it exited with a nonzero code + try: + output = subprocess.check_output(arguments, stderr=subprocess.STDOUT) + print(output.decode(), end='', flush=True) + print(f'{job_name} finished successfully') + except subprocess.CalledProcessError as ex: + print(ex.output.decode(), end='', flush=True) + print(f'{job_name} failed') + sys.exit(1) + + +""" +Calls a function defined in library_generation/utilities.sh +""" +def sh_util(statement: str, **kwargs) -> str: + if 'stdout' not in kwargs: + kwargs['stdout'] = subprocess.PIPE + if 'stderr' not in kwargs: + kwargs['stderr'] = subprocess.PIPE + output = '' + with subprocess.Popen( + ['bash', '-exc', f'source {script_dir}/utilities.sh && {statement}'], + **kwargs, + ) as proc: + print('command stderr:') + for line in proc.stderr: + print(line.decode(), end='', flush=True) + print('command stdout:') + for line in proc.stdout: + print(line.decode(), end='', flush=True) + output += line.decode() + proc.wait() + if proc.returncode != 0: + raise RuntimeError(f'function {statement} failed with exit code {proc.returncode}') + # captured stdout may contain a newline at the end, we remove it + if len(output) > 0 and output[-1] == '\n': + output = output[:-1] + return output + +""" +prints to stderr +""" +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +"""Deletes a file or folder if it exists. + + Args: + path: The path to the file or folder. +""" +def delete_if_exists(path: str): + if os.path.isfile(path): # Check if it's a file + os.remove(path) + print(f"File deleted: {path}") + elif os.path.isdir(path): # Check if it's a directory + shutil.rmtree(path) + print(f"Folder deleted: {path}") + else: + print(f"Path does not exist: {path}") + +def main(argv: Sequence[str]) -> None: + if len(argv) < 1: + raise ValueError('Usage: python generate_composed_library_args.py function_name arg1...argN') + + function_name = argv[1] + arguments = argv[2:] + try: + function = getattr(sys.modules[__name__], function_name) + print(function(*arguments)) + except AttributeError: + print(f'function name "{function_name}" not found in utilities.py') + sys.exit(1) + + + + +if __name__ == "__main__": + main(sys.argv) diff --git a/library_generation/utilities.sh b/library_generation/utilities.sh index 87feb3838c..965ed1fa0a 100755 --- a/library_generation/utilities.sh +++ b/library_generation/utilities.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -xeo pipefail +utilities_script_dir=$(dirname "$(realpath "${BASH_SOURCE[0]}")") # Utility functions used in `generate_library.sh` and showcase generation. extract_folder_name() { @@ -209,7 +210,11 @@ download_fail() { # gets the output folder where all sources and dependencies will be located. get_output_folder() { - echo "$(pwd)/output" + if [[ $(basename $(pwd)) != "output" ]]; then + echo "$(pwd)/output" + else + echo $(pwd) + fi } detect_os_architecture() { @@ -268,3 +273,75 @@ get_proto_path_from_preprocessed_sources() { popd > /dev/null # sources echo "${result}" } + +# for a pre-processed library stored in $preprocessed_sources_path, a folder +# tree is built on $target_folder so it looks like a googleapis-gen folder and +# is therefore consumable by an .OwlBot.yaml file +build_owlbot_cli_source_folder() { + local postprocessing_target=$1 + local target_folder=$2 + local preprocessed_sources_path=$3 + local proto_path=$4 + if [[ -z "${proto_path}" ]]; then + proto_path=$(get_proto_path_from_preprocessed_sources "${preprocessed_sources_path}") + fi + owlbot_staging_folder="${postprocessing_target}/owl-bot-staging" + mkdir -p "${owlbot_staging_folder}" + + # By default (thanks to generation templates), .OwlBot.yaml `deep-copy` section + # references a wildcard pattern matching a folder + # ending with `-java` at the leaf of proto_path. We then use a generated-java + # folder that will be picked up by copy-code + mkdir -p "${target_folder}/${proto_path}/generated-java" + copy_directory_if_exists "${preprocessed_sources_path}" "proto" \ + "${target_folder}/${proto_path}/generated-java/proto-google-cloud-library" + copy_directory_if_exists "${preprocessed_sources_path}" "grpc" \ + "${target_folder}/${proto_path}/generated-java/grpc-google-cloud-library" + copy_directory_if_exists "${preprocessed_sources_path}" "gapic" \ + "${target_folder}/${proto_path}/generated-java/gapic-google-cloud-library" + copy_directory_if_exists "${preprocessed_sources_path}" "samples" \ + "${target_folder}/${proto_path}/generated-java/samples" + pushd "${target_folder}" + # create an empty commit so owl-bot-copy can process this as a repo + # (it cannot process non-git-repositories) + git init + git commit --allow-empty -m 'empty commit' + popd # target_folder +} + +# Convenience function to clone only the necessary folders from a git repository +sparse_clone() { + repo_url=$1 + paths=$2 + commitish=$3 + clone_dir=$(basename "${repo_url%.*}") + rm -rf "${clone_dir}" + git clone -n --depth=1 --no-single-branch --filter=tree:0 "${repo_url}" + pushd "${clone_dir}" + if [ -n "${commitish}" ]; then + git checkout "${commitish}" + fi + git sparse-checkout set --no-cone ${paths} + git checkout + popd +} + +# calls a function in utilities.py. THe first argument is the function name, the +# rest of the arguments are the positional arguments to such function +py_util() { + python3 "${utilities_script_dir}/utilities.py" "$@" +} + +download_googleapis_files_and_folders() { + local output_folder=$1 + local googleapis_commitish=$2 + # checkout the master branch of googleapis/google (proto files) and WORKSPACE + echo "Checking out googlapis repository..." + # sparse_clone will remove folder contents first, so we have to checkout googleapis + # only once. + sparse_clone https://github.com/googleapis/googleapis.git "google grafeas" "${googleapis_commitish}" + pushd googleapis + cp -r google "${output_folder}" + cp -r grafeas "${output_folder}" +} + diff --git a/showcase/scripts/generate_showcase.sh b/showcase/scripts/generate_showcase.sh index ef9e2bf850..1c1b1f58de 100755 --- a/showcase/scripts/generate_showcase.sh +++ b/showcase/scripts/generate_showcase.sh @@ -8,6 +8,7 @@ set -ex readonly SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) lib_gen_scripts_dir="${SCRIPT_DIR}/../../library_generation/" source "${lib_gen_scripts_dir}/test/test_utilities.sh" +source "${lib_gen_scripts_dir}/utilities.sh" readonly perform_cleanup=$1 cd "${SCRIPT_DIR}" @@ -66,7 +67,6 @@ bash "${SCRIPT_DIR}/../../library_generation/generate_library.sh" \ --service_config "${service_config}" \ --service_yaml "${service_yaml}" \ --include_samples "${include_samples}" \ - --enable_postprocessing "false" \ --transport "${transport}" exit_code=$? From 46b0a857eaa4484c5f1ebe1170338fc90a994375 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 29 Jan 2024 18:47:31 +0000 Subject: [PATCH 07/11] fix: Endpoint resolution uses user set endpoint from ClientSettings (#2429) * fix: Endpoint resolution uses the user set endpoint * chore: Rename to userSetEndpoint() * chore: Empty commit * chore: Fix showcase tests --- .../java/com/google/api/gax/rpc/ClientContext.java | 2 +- .../java/com/google/api/gax/rpc/StubSettings.java | 11 +++++++++++ .../showcase/v1beta1/it/ITEndpointContext.java | 13 +++++++++++-- .../v1beta1/it/util/TestClientInitializer.java | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 03a0cf86d9..409dbbada1 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -165,7 +165,7 @@ public static ClientContext create(StubSettings settings) throws IOException { EndpointContext.newBuilder() .setServiceName(settings.getServiceName()) .setUniverseDomain(settings.getUniverseDomain()) - .setClientSettingsEndpoint(settings.getEndpoint()) + .setClientSettingsEndpoint(settings.getUserSetEndpoint()) .setTransportChannelProviderEndpoint( settings.getTransportChannelProvider().getEndpoint()) .setMtlsEndpoint(settings.getMtlsEndpoint()) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java index a2d4e10603..962443e5d2 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java @@ -157,6 +157,17 @@ public String getEndpoint() { return endpoint; } + /** + * This is an internal api meant to either return the user set endpoint or null. The difference + * between this method and {@link #getEndpoint()}} is that {@link #getEndpoint()} is reimplemented + * by the child class and will return the default service endpoint if the user did not set an + * endpoint (does not return null). + */ + @InternalApi + String getUserSetEndpoint() { + return endpoint; + } + public final String getMtlsEndpoint() { return mtlsEndpoint; } diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITEndpointContext.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITEndpointContext.java index 11f56fa5c8..c7589f8e0d 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITEndpointContext.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITEndpointContext.java @@ -20,14 +20,23 @@ public class ITEndpointContext { public static final String SHOWCASE_DEFAULT_ENDPOINT = "localhost:7469"; // Default (no configuration) + // This test is very similar to `endpointResolution_userConfiguration`. This test is kept + // as future enhancements could allow this test to not have to explicitly set the endpoint. @Test public void endpointResolution_default() throws InterruptedException, IOException { EchoClient echoClient = null; try { - // The default usage is EchoClient.create(), but for showcase tests run in CI, the + // This is not how a client is created by default: + // 1. The default usage is EchoClient.create(), but for showcase tests run in CI, the // client must be supplied with Credentials. + // 2. The default configuration does not set an endpoint. Showcase clients do not have + // a serviceName (and this cannot be configured by the user). Set the endpoint + // to simulate the endpointContext creating it with a proper serviceName. EchoSettings echoSettings = - EchoSettings.newBuilder().setCredentialsProvider(NoCredentialsProvider.create()).build(); + EchoSettings.newBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setEndpoint(SHOWCASE_DEFAULT_ENDPOINT) + .build(); echoClient = EchoClient.create(echoSettings); Truth.assertThat(echoClient.getSettings().getEndpoint()).isEqualTo(SHOWCASE_DEFAULT_ENDPOINT); } finally { diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java index 9fbedbc107..d2606402d8 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java @@ -213,6 +213,7 @@ public static ComplianceClient createGrpcComplianceClient(List interceptorList) .build()) + .setEndpoint("localhost:7469") .build(); return ComplianceClient.create(grpcComplianceSettings); } From 5c291e8786b8e976979ec2e26b13f0327333bb02 Mon Sep 17 00:00:00 2001 From: Deepankar Dixit <90280028+ddixit14@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:43:02 +0000 Subject: [PATCH 08/11] feat: MetricsTracer implementation (#2421) * feat: Opentelemetry implementation --- .../google/api/gax/tracing/MetricsTracer.java | 175 +++++++++++- .../gax/tracing/MetricsTracerFactoryTest.java | 81 ++++++ .../api/gax/tracing/MetricsTracerTest.java | 261 ++++++++++++++++++ 3 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java diff --git a/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java b/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java index bf5dbdd046..45a8558599 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/tracing/MetricsTracer.java @@ -32,6 +32,16 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.StatusCode; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import org.threeten.bp.Duration; /** * This class computes generic metrics that can be observed in the lifecycle of an RPC operation. @@ -42,12 +52,173 @@ @InternalApi public class MetricsTracer implements ApiTracer { - public MetricsTracer(MethodName methodName, MetricsRecorder metricsRecorder) {} + private static final String STATUS_ATTRIBUTE = "status"; + + private Stopwatch attemptTimer; + + private final Stopwatch operationTimer = Stopwatch.createStarted(); + + private final Map attributes = new HashMap<>(); + + private MetricsRecorder metricsRecorder; + + public MetricsTracer(MethodName methodName, MetricsRecorder metricsRecorder) { + this.attributes.put("method_name", methodName.toString()); + this.metricsRecorder = metricsRecorder; + } + + /** + * Signals that the overall operation has finished successfully. The tracer is now considered + * closed and should no longer be used. Successful operation adds "OK" value to the status + * attribute key. + */ + @Override + public void operationSucceeded() { + attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); + metricsRecorder.recordOperationLatency( + operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordOperationCount(1, attributes); + } + + /** + * Signals that the operation was cancelled by the user. The tracer is now considered closed and + * should no longer be used. Cancelled operation adds "CANCELLED" value to the status attribute + * key. + */ + @Override + public void operationCancelled() { + attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString()); + metricsRecorder.recordOperationLatency( + operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordOperationCount(1, attributes); + } + + /** + * Signals that the operation was cancelled by the user. The tracer is now considered closed and + * should no longer be used. Failed operation extracts the error from the throwable and adds it to + * the status attribute key. + */ + @Override + public void operationFailed(Throwable error) { + attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + metricsRecorder.recordOperationLatency( + operationTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordOperationCount(1, attributes); + } + + /** + * Adds an annotation that an attempt is about to start with additional information from the + * request. In general this should occur at the very start of the operation. The attemptNumber is + * zero based. So the initial attempt will be 0. When the attempt starts, the attemptTimer starts + * the stopwatch. + * + * @param attemptNumber the zero based sequential attempt number. + * @param request request of this attempt. + */ + @Override + public void attemptStarted(Object request, int attemptNumber) { + attemptTimer = Stopwatch.createStarted(); + } + + /** + * Adds an annotation that the attempt succeeded. Successful attempt add "OK" value to the status + * attribute key. + */ + @Override + public void attemptSucceeded() { + + attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.OK.toString()); + metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordAttemptCount(1, attributes); + } + + /** + * Add an annotation that the attempt was cancelled by the user. Cancelled attempt add "CANCELLED" + * to the status attribute key. + */ + @Override + public void attemptCancelled() { + + attributes.put(STATUS_ATTRIBUTE, StatusCode.Code.CANCELLED.toString()); + metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordAttemptCount(1, attributes); + } + + /** + * Adds an annotation that the attempt failed, but another attempt will be made after the delay. + * + * @param error the error that caused the attempt to fail. + * @param delay the amount of time to wait before the next attempt will start. + *

Failed attempt extracts the error from the throwable and adds it to the status attribute + * key. + */ + @Override + public void attemptFailed(Throwable error, Duration delay) { + + attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordAttemptCount(1, attributes); + } + + /** + * Adds an annotation that the attempt failed and that no further attempts will be made because + * retry limits have been reached. This extracts the error from the throwable and adds it to the + * status attribute key. + * + * @param error the last error received before retries were exhausted. + */ + @Override + public void attemptFailedRetriesExhausted(Throwable error) { + + attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordAttemptCount(1, attributes); + } + + /** + * Adds an annotation that the attempt failed and that no further attempts will be made because + * the last error was not retryable. This extracts the error from the throwable and adds it to the + * status attribute key. + * + * @param error the error that caused the final attempt to fail. + */ + @Override + public void attemptPermanentFailure(Throwable error) { + + attributes.put(STATUS_ATTRIBUTE, extractStatus(error)); + metricsRecorder.recordAttemptLatency(attemptTimer.elapsed(TimeUnit.MILLISECONDS), attributes); + metricsRecorder.recordAttemptCount(1, attributes); + } + + /** Function to extract the status of the error as a string */ + @VisibleForTesting + static String extractStatus(@Nullable Throwable error) { + final String statusString; + + if (error == null) { + return StatusCode.Code.OK.toString(); + } else if (error instanceof CancellationException) { + statusString = StatusCode.Code.CANCELLED.toString(); + } else if (error instanceof ApiException) { + statusString = ((ApiException) error).getStatusCode().getCode().toString(); + } else { + statusString = StatusCode.Code.UNKNOWN.toString(); + } + + return statusString; + } /** * Add attributes that will be attached to all metrics. This is expected to be called by * handwritten client teams to add additional attributes that are not supposed be collected by * Gax. */ - public void addAttributes(String key, String value) {}; + public void addAttributes(String key, String value) { + attributes.put(key, value); + }; + + @VisibleForTesting + Map getAttributes() { + return attributes; + } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java new file mode 100644 index 0000000000..2c6a014658 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerFactoryTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.tracing; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.gax.tracing.ApiTracerFactory.OperationType; +import com.google.common.truth.Truth; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class MetricsTracerFactoryTest { + @Mock private MetricsRecorder metricsRecorder; + @Mock private ApiTracer parent; + private SpanName spanName; + private MetricsTracerFactory metricsTracerFactory; + + @Before + public void setUp() { + // Create an instance of MetricsTracerFactory with the mocked MetricsRecorder + metricsTracerFactory = new MetricsTracerFactory(metricsRecorder); + + spanName = mock(SpanName.class); + when(spanName.getClientName()).thenReturn("testService"); + when(spanName.getMethodName()).thenReturn("testMethod"); + } + + @Test + public void testNewTracer_notNull() { + // Call the newTracer method + ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary); + + // Assert that the apiTracer created has expected type and not null + Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class); + Truth.assertThat(apiTracer).isNotNull(); + } + + @Test + public void testNewTracer_HasCorrectParameters() { + + // Call the newTracer method + ApiTracer apiTracer = metricsTracerFactory.newTracer(parent, spanName, OperationType.Unary); + + // Assert that the apiTracer created has expected type and not null + Truth.assertThat(apiTracer).isInstanceOf(MetricsTracer.class); + Truth.assertThat(apiTracer).isNotNull(); + + MetricsTracer metricsTracer = (MetricsTracer) apiTracer; + Truth.assertThat(metricsTracer.getAttributes().get("method_name")) + .isEqualTo("testService.testMethod"); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java new file mode 100644 index 0000000000..7b6b76f181 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/tracing/MetricsTracerTest.java @@ -0,0 +1,261 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.tracing; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.DeadlineExceededException; +import com.google.api.gax.rpc.NotFoundException; +import com.google.api.gax.rpc.StatusCode.Code; +import com.google.api.gax.rpc.testing.FakeStatusCode; +import com.google.common.collect.ImmutableMap; +import com.google.common.truth.Truth; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.quality.Strictness; +import org.threeten.bp.Duration; + +@RunWith(JUnit4.class) +public class MetricsTracerTest { + // stricter way of testing for early detection of unused stubs and argument mismatches + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + + private MetricsTracer metricsTracer; + @Mock private MetricsRecorder metricsRecorder; + + @Before + public void setUp() { + metricsTracer = + new MetricsTracer(MethodName.of("fake_service", "fake_method"), metricsRecorder); + } + + @Test + public void testOperationSucceeded_recordsAttributes() { + + metricsTracer.operationSucceeded(); + + Map attributes = + ImmutableMap.of( + "status", "OK", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordOperationCount(1, attributes); + verify(metricsRecorder).recordOperationLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testOperationFailed_recordsAttributes() { + + ApiException error0 = + new NotFoundException( + "invalid argument", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false); + metricsTracer.operationFailed(error0); + + Map attributes = + ImmutableMap.of( + "status", "INVALID_ARGUMENT", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordOperationCount(1, attributes); + verify(metricsRecorder).recordOperationLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testOperationCancelled_recordsAttributes() { + + metricsTracer.operationCancelled(); + + Map attributes = + ImmutableMap.of( + "status", "CANCELLED", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordOperationCount(1, attributes); + verify(metricsRecorder).recordOperationLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testAttemptSucceeded_recordsAttributes() { + // initialize mock-request + Object mockSuccessfulRequest = new Object(); + + // Attempt #1 + metricsTracer.attemptStarted(mockSuccessfulRequest, 0); + metricsTracer.attemptSucceeded(); + + Map attributes = + ImmutableMap.of( + "status", "OK", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordAttemptCount(1, attributes); + verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testAttemptFailed_recordsAttributes() { + // initialize mock-request + Object mockFailedRequest = new Object(); + + // Attempt #1 + metricsTracer.attemptStarted(mockFailedRequest, 0); + ApiException error0 = + new NotFoundException( + "invalid argument", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false); + metricsTracer.attemptFailed(error0, Duration.ofMillis(2)); + + Map attributes = + ImmutableMap.of( + "status", "INVALID_ARGUMENT", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordAttemptCount(1, attributes); + verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testAttemptCancelled_recordsAttributes() { + // initialize mock-request + Object mockCancelledRequest = new Object(); + // Attempt #1 + metricsTracer.attemptStarted(mockCancelledRequest, 0); + metricsTracer.attemptCancelled(); + + Map attributes = + ImmutableMap.of( + "status", "CANCELLED", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordAttemptCount(1, attributes); + verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testAttemptFailedRetriesExhausted_recordsAttributes() { + // initialize mock-request + Object mockRequest = new Object(); + // Attempt #1 + metricsTracer.attemptStarted(mockRequest, 0); + ApiException error0 = + new DeadlineExceededException( + "deadline exceeded", null, new FakeStatusCode(Code.DEADLINE_EXCEEDED), false); + metricsTracer.attemptFailedRetriesExhausted(error0); + + Map attributes = + ImmutableMap.of( + "status", "DEADLINE_EXCEEDED", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordAttemptCount(1, attributes); + verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testAttemptPermanentFailure_recordsAttributes() { + + // initialize mock-request + Object mockRequest = new Object(); + // Attempt #1 + metricsTracer.attemptStarted(mockRequest, 0); + ApiException error0 = + new NotFoundException("not found", null, new FakeStatusCode(Code.NOT_FOUND), false); + metricsTracer.attemptFailedRetriesExhausted(error0); + + Map attributes = + ImmutableMap.of( + "status", "NOT_FOUND", + "method_name", "fake_service.fake_method"); + + verify(metricsRecorder).recordAttemptCount(1, attributes); + verify(metricsRecorder).recordAttemptLatency(anyDouble(), eq(attributes)); + + verifyNoMoreInteractions(metricsRecorder); + } + + @Test + public void testAddAttributes_recordsAttributes() { + + metricsTracer.addAttributes("FakeTableId", "12345"); + Truth.assertThat(metricsTracer.getAttributes().get("FakeTableId").equals("12345")); + } + + @Test + public void testExtractStatus_errorConversion_apiExceptions() { + + ApiException error = + new ApiException("fake_error", null, new FakeStatusCode(Code.INVALID_ARGUMENT), false); + String errorCode = metricsTracer.extractStatus(error); + assertThat(errorCode).isEqualTo("INVALID_ARGUMENT"); + } + + @Test + public void testExtractStatus_errorConversion_noError() { + + // test "OK", which corresponds to a "null" error. + String successCode = metricsTracer.extractStatus(null); + assertThat(successCode).isEqualTo("OK"); + } + + @Test + public void testExtractStatus_errorConversion_unknownException() { + + // test "UNKNOWN" + Throwable unknownException = new RuntimeException(); + String errorCode2 = metricsTracer.extractStatus(unknownException); + assertThat(errorCode2).isEqualTo("UNKNOWN"); + } +} From 99165403902ff91ecb0b14b858333855e7a10c60 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Wed, 31 Jan 2024 00:10:37 -0500 Subject: [PATCH 09/11] fix: Move direct path misconfiguration log to before creating the first channel (#2430) Fixes #2423 The root cause of the issue is that `logDirectPathMisconfig()` is called in the builder of `InstantiatingGrpcChannelProvider`, which could be called multiple times before it is fully instantiated. We should only call `logDirectPathMisconfig()` right before `createChannel()` which creates the first channel. We can not move it to before `createSingleChannel()` because it is used for resizing channel regularly after a client is initialized, and we only want to log direct path misconfiguration once. --- .../InstantiatingGrpcChannelProvider.java | 5 ++- .../InstantiatingGrpcChannelProviderTest.java | 39 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index 280f9cf78e..bf8ffc81da 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -145,7 +145,6 @@ private InstantiatingGrpcChannelProvider(Builder builder) { builder.directPathServiceConfig == null ? getDefaultDirectPathServiceConfig() : builder.directPathServiceConfig; - logDirectPathMisconfig(); } /** @@ -234,6 +233,7 @@ public TransportChannel getTransportChannel() throws IOException { } else if (needsEndpoint()) { throw new IllegalStateException("getTransportChannel() called when needsEndpoint() is true"); } else { + logDirectPathMisconfig(); return createChannel(); } } @@ -272,6 +272,9 @@ boolean isDirectPathXdsEnabled() { return false; } + // This method should be called once per client initialization, hence can not be called in the + // builder or createSingleChannel, only in getTransportChannel which creates the first channel + // for a client. private void logDirectPathMisconfig() { if (isDirectPathXdsEnabled()) { // Case 1: does not enable DirectPath diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java index f6fb9d00ac..a58d10ffc6 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java @@ -539,11 +539,19 @@ protected Object getMtlsObjectFromTransportChannel(MtlsProvider provider) } @Test - public void testLogDirectPathMisconfigAttrempDirectPathNotSet() { + public void testLogDirectPathMisconfigAttrempDirectPathNotSet() throws Exception { FakeLogHandler logHandler = new FakeLogHandler(); InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); InstantiatingGrpcChannelProvider provider = - InstantiatingGrpcChannelProvider.newBuilder().setAttemptDirectPathXds().build(); + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPathXds() + .setHeaderProvider(Mockito.mock(HeaderProvider.class)) + .setExecutor(Mockito.mock(Executor.class)) + .setEndpoint("localhost:8080") + .build(); + + provider.getTransportChannel(); + assertThat(logHandler.getAllMessages()) .contains( "DirectPath is misconfigured. Please set the attemptDirectPath option along with the" @@ -552,15 +560,33 @@ public void testLogDirectPathMisconfigAttrempDirectPathNotSet() { } @Test - public void testLogDirectPathMisconfigWrongCredential() { + public void testLogDirectPathMisconfig_shouldNotLogInTheBuilder() { + FakeLogHandler logHandler = new FakeLogHandler(); + InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPathXds() + .setAttemptDirectPath(true) + .build(); + + assertThat(logHandler.getAllMessages()).isEmpty(); + InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler); + } + + @Test + public void testLogDirectPathMisconfigWrongCredential() throws Exception { FakeLogHandler logHandler = new FakeLogHandler(); InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); InstantiatingGrpcChannelProvider provider = InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPathXds() .setAttemptDirectPath(true) + .setHeaderProvider(Mockito.mock(HeaderProvider.class)) + .setExecutor(Mockito.mock(Executor.class)) .setEndpoint("test.googleapis.com:443") .build(); + + provider.getTransportChannel(); + assertThat(logHandler.getAllMessages()) .contains( "DirectPath is misconfigured. Please make sure the credential is an instance of" @@ -569,7 +595,7 @@ public void testLogDirectPathMisconfigWrongCredential() { } @Test - public void testLogDirectPathMisconfigNotOnGCE() { + public void testLogDirectPathMisconfigNotOnGCE() throws Exception { FakeLogHandler logHandler = new FakeLogHandler(); InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); InstantiatingGrpcChannelProvider provider = @@ -577,8 +603,13 @@ public void testLogDirectPathMisconfigNotOnGCE() { .setAttemptDirectPathXds() .setAttemptDirectPath(true) .setAllowNonDefaultServiceAccount(true) + .setHeaderProvider(Mockito.mock(HeaderProvider.class)) + .setExecutor(Mockito.mock(Executor.class)) .setEndpoint("test.googleapis.com:443") .build(); + + provider.getTransportChannel(); + if (!InstantiatingGrpcChannelProvider.isOnComputeEngine()) { assertThat(logHandler.getAllMessages()) .contains( From b28235ab20fd174deddafc0426b8d20352af6e85 Mon Sep 17 00:00:00 2001 From: Alice <65933803+alicejli@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:19:38 -0500 Subject: [PATCH 10/11] feat: autopopulate fields in the request (#2353) * feat: autopopulate fields in the request * revert showcase golden changes * add AbstractTransportServiceStubClassComposerTest * add REST implementation * add GrpcDirectCallableTest * remove unnecessary null check * add field info proto registry and more unit test cases * add HttpJsonDirectCallableTest * refactor AbstractTransportServiceSTubClassComposer * add more unit tests for Field * feat: refactor request mutator expression * fix goldens * add showcase test for autopopulation * fix lint * change assertion in showcase test * refactor for sonarcloud * sonarcloud fixes * sonarcloud * sonarcloud fix * fix sonarcloud * slight refactoring * revert changes to directCallable and replace with retryable Callable * overload retrying Callables method * change license header format * fix license header * fix showcase lint * add comment * add showcase comment * add CallableTest and httpjson Retrying test * fix lint * add RetryingCallable test and some refactoring * refactor GrpcCallableFactory * remove extraneous from HttpJsonDirectCallableTest * remove FakeHttpJsonChannel * revert changes to tests for extra param * refactoring * Update gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java Co-authored-by: Blake Li * Update gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java Co-authored-by: Blake Li --------- Co-authored-by: Blake Li --- .../google/api/generator/ProtoRegistry.java | 2 + ...ractTransportServiceStubClassComposer.java | 218 +++++++- .../api/generator/gapic/model/Field.java | 11 + .../generator/gapic/protoparser/Parser.java | 11 +- ...TransportServiceStubClassComposerTest.java | 529 ++++++++++++++++++ .../DefaultValueComposerTest.java | 5 + .../composer/grpc/goldens/EchoClient.golden | 35 ++ .../grpc/goldens/EchoClientTest.golden | 50 ++ .../composer/grpc/goldens/GrpcEchoStub.golden | 13 + .../samples/echoclient/AsyncChat.golden | 5 + .../samples/echoclient/AsyncChatAgain.golden | 5 + .../samples/echoclient/AsyncCollect.golden | 5 + .../echoclient/AsyncCollideName.golden | 5 + .../samples/echoclient/AsyncEcho.golden | 5 + .../samples/echoclient/SyncCollideName.golden | 5 + .../samples/echoclient/SyncEcho.golden | 5 + .../rest/goldens/HttpJsonEchoStub.golden | 13 + ...lientCallableMethodSampleComposerTest.java | 15 + ...ServiceClientHeaderSampleComposerTest.java | 5 + ...ServiceClientMethodSampleComposerTest.java | 10 + .../api/generator/gapic/model/FieldTest.java | 76 +++ .../gapic/protoparser/ParserTest.java | 31 +- .../test/protoloader/TestProtoLoader.java | 9 +- .../src/test/proto/echo.proto | 28 + .../src/test/resources/echo_v1beta1.yaml | 7 +- .../google/api/gax/grpc/GrpcCallSettings.java | 15 + .../api/gax/grpc/GrpcCallableFactory.java | 8 +- .../api/gax/grpc/GrpcDirectCallable.java | 2 + .../gax/httpjson/HttpJsonCallSettings.java | 15 + .../gax/httpjson/HttpJsonCallableFactory.java | 29 +- .../google/api/gax/httpjson/RetryingTest.java | 154 +++-- .../com/google/api/gax/rpc/Callables.java | 45 +- .../google/api/gax/rpc/RequestMutator.java | 52 ++ .../google/api/gax/rpc/RetryingCallable.java | 17 +- .../com/google/api/gax/rpc/CallableTest.java | 38 +- .../api/gax/rpc/RetryingCallableTest.java | 74 +++ .../showcase/v1beta1/stub/GrpcEchoStub.java | 10 + .../v1beta1/stub/HttpJsonEchoStub.java | 10 + .../v1beta1/it/ITAutoPopulatedFields.java | 373 ++++++++++++ .../it/util/TestClientInitializer.java | 51 ++ 40 files changed, 1928 insertions(+), 68 deletions(-) create mode 100644 gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java create mode 100644 gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java create mode 100644 showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITAutoPopulatedFields.java diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java b/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java index 6f08ccfa3b..ae9c8cc76e 100644 --- a/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java +++ b/gapic-generator-java/src/main/java/com/google/api/generator/ProtoRegistry.java @@ -17,6 +17,7 @@ import com.google.api.AnnotationsProto; import com.google.api.ClientProto; import com.google.api.FieldBehaviorProto; +import com.google.api.FieldInfoProto; import com.google.api.ResourceProto; import com.google.api.RoutingProto; import com.google.cloud.ExtendedOperationsProto; @@ -31,6 +32,7 @@ public static void registerAllExtensions(ExtensionRegistry extensionRegistry) { ClientProto.registerAllExtensions(extensionRegistry); ResourceProto.registerAllExtensions(extensionRegistry); FieldBehaviorProto.registerAllExtensions(extensionRegistry); + FieldInfoProto.registerAllExtensions(extensionRegistry); ExtendedOperationsProto.registerAllExtensions(extensionRegistry); RoutingProto.registerAllExtensions(extensionRegistry); } diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java index 191e834a40..2647647443 100644 --- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java +++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java @@ -42,6 +42,7 @@ import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; import com.google.api.generator.engine.ast.NewObjectExpr; +import com.google.api.generator.engine.ast.Reference; import com.google.api.generator.engine.ast.ReferenceConstructorExpr; import com.google.api.generator.engine.ast.RelationalOperationExpr; import com.google.api.generator.engine.ast.ScopeNode; @@ -58,6 +59,7 @@ import com.google.api.generator.gapic.composer.comment.StubCommentComposer; import com.google.api.generator.gapic.composer.store.TypeStore; import com.google.api.generator.gapic.composer.utils.PackageChecker; +import com.google.api.generator.gapic.model.Field; import com.google.api.generator.gapic.model.GapicClass; import com.google.api.generator.gapic.model.GapicClass.Kind; import com.google.api.generator.gapic.model.GapicContext; @@ -70,8 +72,10 @@ import com.google.api.generator.gapic.model.Transport; import com.google.api.generator.gapic.utils.JavaStyle; import com.google.api.pathtemplate.PathTemplate; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; @@ -84,9 +88,11 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.Generated; import javax.annotation.Nullable; @@ -136,6 +142,7 @@ private static TypeStore createStaticTypes() { OperationCallable.class, OperationSnapshot.class, RequestParamsExtractor.class, + UUID.class, ServerStreamingCallable.class, TimeUnit.class, TypeRegistry.class, @@ -277,7 +284,8 @@ protected Expr createTransportSettingsInitExpr( Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr, - List classStatements) { + List classStatements, + ImmutableMap messageTypes) { MethodInvocationExpr callSettingsBuilderExpr = MethodInvocationExpr.builder() .setStaticReferenceType(getTransportContext().transportCallSettingsType()) @@ -318,6 +326,15 @@ protected Expr createTransportSettingsInitExpr( .build(); } + if (method.hasAutoPopulatedFields() && shouldGenerateRequestMutator(method, messageTypes)) { + callSettingsBuilderExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(callSettingsBuilderExpr) + .setMethodName("setRequestMutator") + .setArguments(createRequestMutatorClassInstance(method, messageTypes)) + .build(); + } + callSettingsBuilderExpr = MethodInvocationExpr.builder() .setExprReferenceExpr(callSettingsBuilderExpr) @@ -760,7 +777,8 @@ protected List createConstructorMethods( javaStyleMethodNameToTransportSettingsVarExprs.get( JavaStyle.toLowerCamelCase(m.name())), protoMethodNameToDescriptorVarExprs.get(m.name()), - classStatements)) + classStatements, + context.messages())) .collect(Collectors.toList())); secondCtorStatements.addAll( secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())); @@ -1233,12 +1251,197 @@ protected TypeNode getTransportOperationsStubType(Service service) { return transportOpeationsStubType; } + protected static LambdaExpr createRequestMutatorClassInstance( + Method method, ImmutableMap messageTypes) { + List bodyStatements = new ArrayList<>(); + VariableExpr requestVarExpr = createRequestVarExpr(method); + + Reference requestBuilderRef = + VaporReference.builder() + .setEnclosingClassNames(method.inputType().reference().name()) + .setName("Builder") + .setPakkage(method.inputType().reference().pakkage()) + .build(); + + TypeNode requestBuilderType = TypeNode.withReference(requestBuilderRef); + + VariableExpr requestBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(true) + .build(); + + MethodInvocationExpr setRequestBuilderInvocationExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestVarExpr) + .setMethodName("toBuilder") + .setReturnType(requestBuilderType) + .build(); + + Expr requestBuilderExpr = + AssignmentExpr.builder() + .setVariableExpr(requestBuilderVarExpr) + .setValueExpr(setRequestBuilderInvocationExpr) + .build(); + + bodyStatements.add(ExprStatement.withExpr(requestBuilderExpr)); + + VariableExpr returnBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(false) + .build(); + + MethodInvocationExpr.Builder returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(returnBuilderVarExpr) + .setMethodName("build"); + + createRequestMutatorBody(method, messageTypes, bodyStatements, returnBuilderVarExpr); + + return LambdaExpr.builder() + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody(bodyStatements) + .setReturnExpr(returnExpr.build()) + .build(); + } + + @VisibleForTesting + static List createRequestMutatorBody( + Method method, + ImmutableMap messageTypes, + List bodyStatements, + VariableExpr returnBuilderVarExpr) { + + Message methodRequestMessage = messageTypes.get(method.inputType().reference().fullName()); + method.autoPopulatedFields().stream() + // Map each field name to its corresponding Field object, if present + .map( + fieldName -> + methodRequestMessage.fields().stream() + .filter(field -> field.name().equals(fieldName)) + .findFirst()) + .filter(Optional::isPresent) // Keep only the existing Fields + .map(Optional::get) // Extract the Field from the Optional + .filter(Field::canBeAutoPopulated) // Filter fields that can be autopopulated + .forEach( + matchedField -> { + // Create statements for each autopopulated Field + bodyStatements.add( + createAutoPopulatedRequestStatement( + method, matchedField.name(), returnBuilderVarExpr)); + }); + return bodyStatements; + } + + @VisibleForTesting + static Statement createAutoPopulatedRequestStatement( + Method method, String fieldName, VariableExpr returnBuilderVarExpr) { + + VariableExpr requestVarExpr = createRequestVarExpr(method); + + // Expected expression: request.getRequestId() + MethodInvocationExpr getAutoPopulatedFieldInvocationExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestVarExpr) + .setMethodName(String.format("get%s", JavaStyle.toUpperCamelCase(fieldName))) + .setReturnType(TypeNode.STRING) + .build(); + + VariableExpr stringsVar = + VariableExpr.withVariable( + Variable.builder() + .setType(TypeNode.withReference(ConcreteReference.withClazz(Strings.class))) + .setName("Strings") + .build()); + + // Expected expression: Strings.isNullOrEmpty(request.getRequestId()) + MethodInvocationExpr isNullOrEmptyFieldInvocationExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(stringsVar) + .setMethodName("isNullOrEmpty") + .setReturnType(TypeNode.BOOLEAN) + .setArguments(getAutoPopulatedFieldInvocationExpr) + .build(); + + // Note: Currently, autopopulation is only for UUID. + VariableExpr uuidVarExpr = + VariableExpr.withVariable( + Variable.builder() + .setType( + TypeNode.withReference( + ConcreteReference.builder().setClazz(UUID.class).build())) + .setName("UUID") + .build()); + + // Expected expression: UUID.randomUUID() + MethodInvocationExpr autoPopulatedFieldsArgsHelper = + MethodInvocationExpr.builder() + .setExprReferenceExpr(uuidVarExpr) + .setMethodName("randomUUID") + .setReturnType( + TypeNode.withReference(ConcreteReference.builder().setClazz(UUID.class).build())) + .build(); + + // Expected expression: UUID.randomUUID().toString() + MethodInvocationExpr autoPopulatedFieldsArgsToString = + MethodInvocationExpr.builder() + .setExprReferenceExpr(autoPopulatedFieldsArgsHelper) + .setMethodName("toString") + .setReturnType(TypeNode.STRING) + .build(); + + // Expected expression: requestBuilder().setField(UUID.randomUUID().toString()) + MethodInvocationExpr setAutoPopulatedFieldInvocationExpr = + MethodInvocationExpr.builder() + .setArguments(autoPopulatedFieldsArgsToString) + .setExprReferenceExpr(returnBuilderVarExpr) + .setMethodName(String.format("set%s", JavaStyle.toUpperCamelCase(fieldName))) + .setReturnType(method.inputType()) + .build(); + + return IfStatement.builder() + .setConditionExpr(isNullOrEmptyFieldInvocationExpr) + .setBody(Arrays.asList(ExprStatement.withExpr(setAutoPopulatedFieldInvocationExpr))) + .build(); + } + + /** + * The Request Mutator should only be generated if the field exists in the Message and is properly + * configured in the Message(see {@link Field#canBeAutoPopulated()}) + */ + @VisibleForTesting + static Boolean shouldGenerateRequestMutator( + Method method, ImmutableMap messageTypes) { + if (method.inputType().reference() == null + || method.inputType().reference().fullName() == null) { + return false; + } + String methodRequestName = method.inputType().reference().fullName(); + + Message methodRequestMessage = messageTypes.get(methodRequestName); + if (methodRequestMessage == null || methodRequestMessage.fields() == null) { + return false; + } + return method.autoPopulatedFields().stream().anyMatch(shouldAutoPopulate(methodRequestMessage)); + } + + /** + * The field has to exist in the Message and properly configured in the Message(see {@link + * Field#canBeAutoPopulated()}) + */ + private static Predicate shouldAutoPopulate(Message methodRequestMessage) { + return fieldName -> + methodRequestMessage.fields().stream() + .anyMatch(field -> field.name().equals(fieldName) && field.canBeAutoPopulated()); + } + protected LambdaExpr createRequestParamsExtractorClassInstance( Method method, List classStatements) { List bodyStatements = new ArrayList<>(); - VariableExpr requestVarExpr = - VariableExpr.withVariable( - Variable.builder().setType(method.inputType()).setName("request").build()); + VariableExpr requestVarExpr = createRequestVarExpr(method); TypeNode returnType = TypeNode.withReference( ConcreteReference.builder() @@ -1499,4 +1702,9 @@ private MethodInvocationExpr createRequestFieldGetterExpr( } return requestFieldGetterExprBuilder.build(); } + + private static VariableExpr createRequestVarExpr(Method method) { + return VariableExpr.withVariable( + Variable.builder().setType(method.inputType()).setName("request").build()); + } } diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java index 997ea54e5a..39213909de 100644 --- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java +++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/Field.java @@ -72,6 +72,17 @@ public boolean hasResourceReference() { return type().equals(TypeNode.STRING) && resourceReference() != null; } + // Check that the field format is of UUID, it is not annotated as required, and is of type String. + // Unless + // those three conditions are met, do not autopopulate the field. + // In the future, if additional formats are supported for autopopulation, this will need to be + // refactored to support those formats. + public boolean canBeAutoPopulated() { + return Format.UUID4.equals(fieldInfoFormat()) + && !isRequired() + && TypeNode.STRING.equals(type()); + } + @Override public boolean equals(Object o) { if (!(o instanceof Field)) { diff --git a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java index 9f1b395940..e960ad3f6d 100644 --- a/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java +++ b/gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java @@ -1025,10 +1025,13 @@ private static Field parseField( if (fieldOptions.hasExtension(FieldInfoProto.fieldInfo)) { fieldInfoFormat = fieldOptions.getExtension(FieldInfoProto.fieldInfo).getFormat(); } - if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0) { - if (fieldOptions - .getExtension(FieldBehaviorProto.fieldBehavior) - .contains(FieldBehavior.REQUIRED)) ; + + // Cannot directly check fieldOptions.hasExtension(FieldBehaviorProto.fieldBehavior) because the + // default is null + if (fieldOptions.getExtensionCount(FieldBehaviorProto.fieldBehavior) > 0 + && fieldOptions + .getExtension(FieldBehaviorProto.fieldBehavior) + .contains(FieldBehavior.REQUIRED)) { isRequired = true; } diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java new file mode 100644 index 0000000000..77205f0122 --- /dev/null +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposerTest.java @@ -0,0 +1,529 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.generator.gapic.composer.common; + +import static com.google.api.generator.gapic.composer.common.AbstractTransportServiceStubClassComposer.shouldGenerateRequestMutator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.api.FieldInfo.Format; +import com.google.api.generator.engine.ast.LambdaExpr; +import com.google.api.generator.engine.ast.Reference; +import com.google.api.generator.engine.ast.Statement; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.VaporReference; +import com.google.api.generator.engine.ast.Variable; +import com.google.api.generator.engine.ast.VariableExpr; +import com.google.api.generator.engine.writer.JavaWriterVisitor; +import com.google.api.generator.gapic.model.Field; +import com.google.api.generator.gapic.model.Message; +import com.google.api.generator.gapic.model.Method; +import com.google.api.generator.test.utils.LineFormatter; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; + +public class AbstractTransportServiceStubClassComposerTest { + private JavaWriterVisitor writerVisitor; + + @Before + public void setUp() { + writerVisitor = new JavaWriterVisitor(); + } + + @Test + public void shouldGenerateRequestMutator_fieldConfiguredCorrectly() { + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestField") + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.STRING) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + assertTrue(shouldGenerateRequestMutator(METHOD, messageTypes)); + } + + @Test + public void shouldNotGenerateRequestMutator_fieldConfiguredIncorrectly() { + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestField") + .setFieldInfoFormat(Format.IPV6) + .setType(TypeNode.STRING) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + assertFalse(shouldGenerateRequestMutator(METHOD, messageTypes)); + } + + // TODO: add unit tests where the field is not found in the messageTypes map + @Test + public void createAutoPopulatedRequestStatement_sampleField() { + Reference RequestBuilderRef = + VaporReference.builder() + .setName("EchoRequest") + .setPakkage("com.google.example.examples.library.v1") + .build(); + + TypeNode testType = TypeNode.withReference(RequestBuilderRef); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType(testType) + .setOutputType(TypeNode.STRING) + .build(); + + Reference RequestVarBuilderRef = + VaporReference.builder() + .setEnclosingClassNames(METHOD.inputType().reference().name()) + .setName("Builder") + .setPakkage(METHOD.inputType().reference().pakkage()) + .build(); + + TypeNode requestBuilderType = TypeNode.withReference(RequestVarBuilderRef); + + VariableExpr requestBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(false) + .build(); + + Statement autoPopulatedFieldStatement = + AbstractTransportServiceStubClassComposer.createAutoPopulatedRequestStatement( + METHOD, "sampleField", requestBuilderVarExpr); + + autoPopulatedFieldStatement.accept(writerVisitor); + String expected = + LineFormatter.lines( + "if (Strings.isNullOrEmpty(request.getSampleField())) {\n", + "requestBuilder.setSampleField(UUID.randomUUID().toString());\n", + "}\n"); + assertEquals(expected, writerVisitor.write()); + } + + @Test + public void createRequestMutatorBody_TestField() { + List bodyStatements = new ArrayList<>(); + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestField") + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.STRING) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + Reference RequestBuilderRef = + VaporReference.builder() + .setEnclosingClassNames(METHOD.inputType().reference().name()) + .setName("Builder") + .setPakkage(METHOD.inputType().reference().pakkage()) + .build(); + + TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef); + + VariableExpr requestBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(false) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + List listOfAutoPopulatedStatements = + AbstractTransportServiceStubClassComposer.createRequestMutatorBody( + METHOD, messageTypes, bodyStatements, requestBuilderVarExpr); + + for (Statement statement : listOfAutoPopulatedStatements) { + statement.accept(writerVisitor); + } + + String expected = + LineFormatter.lines( + "if (Strings.isNullOrEmpty(request.getTestField())) {\n", + "requestBuilder.setTestField(UUID.randomUUID().toString());\n", + "}\n"); + assertEquals(expected, writerVisitor.write()); + } + + @Test + public void createRequestMutatorBody_TestFieldNotString_shouldReturnNull() { + List bodyStatements = new ArrayList<>(); + + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestField") + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.BOOLEAN) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + Reference RequestBuilderRef = + VaporReference.builder() + .setEnclosingClassNames(METHOD.inputType().reference().name()) + .setName("Builder") + .setPakkage(METHOD.inputType().reference().pakkage()) + .build(); + + TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef); + + VariableExpr requestBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(false) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + List listOfAutoPopulatedStatements = + AbstractTransportServiceStubClassComposer.createRequestMutatorBody( + METHOD, messageTypes, bodyStatements, requestBuilderVarExpr); + + for (Statement statement : listOfAutoPopulatedStatements) { + statement.accept(writerVisitor); + } + + String expected = LineFormatter.lines(""); + assertEquals(expected, writerVisitor.write()); + } + + @Test + public void createRequestMutatorBody_TestFieldFormatNotUUID_shouldReturnNull() { + List bodyStatements = new ArrayList<>(); + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestField") + .setFieldInfoFormat(Format.IPV4_OR_IPV6) + .setType(TypeNode.STRING) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + Reference RequestBuilderRef = + VaporReference.builder() + .setEnclosingClassNames(METHOD.inputType().reference().name()) + .setName("Builder") + .setPakkage(METHOD.inputType().reference().pakkage()) + .build(); + + TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef); + + VariableExpr requestBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(false) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + List listOfAutoPopulatedStatements = + AbstractTransportServiceStubClassComposer.createRequestMutatorBody( + METHOD, messageTypes, bodyStatements, requestBuilderVarExpr); + + for (Statement statement : listOfAutoPopulatedStatements) { + statement.accept(writerVisitor); + } + + String expected = LineFormatter.lines(""); + assertEquals(expected, writerVisitor.write()); + } + + @Test + public void createRequestMutatorBody_TestFieldIncorrectName_shouldReturnNull() { + List bodyStatements = new ArrayList<>(); + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestIncorrectField") + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.STRING) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + Reference RequestBuilderRef = + VaporReference.builder() + .setEnclosingClassNames(METHOD.inputType().reference().name()) + .setName("Builder") + .setPakkage(METHOD.inputType().reference().pakkage()) + .build(); + + TypeNode requestBuilderType = TypeNode.withReference(RequestBuilderRef); + + VariableExpr requestBuilderVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("requestBuilder").setType(requestBuilderType).build()) + .setIsDecl(false) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + List listOfAutoPopulatedStatements = + AbstractTransportServiceStubClassComposer.createRequestMutatorBody( + METHOD, messageTypes, bodyStatements, requestBuilderVarExpr); + + for (Statement statement : listOfAutoPopulatedStatements) { + statement.accept(writerVisitor); + } + + String expected = LineFormatter.lines(""); + assertEquals(expected, writerVisitor.write()); + } + + @Test + public void createRequestMutator_TestField() { + String ECHO_PACKAGE = "com.google.showcase.v1beta1"; + List autoPopulatedFieldList = new ArrayList<>(); + autoPopulatedFieldList.add("TestField"); + + Method METHOD = + Method.builder() + .setName("TestMethod") + .setInputType( + TypeNode.withReference( + VaporReference.builder() + .setName("SampleRequest") + .setPakkage(ECHO_PACKAGE) + .build())) + .setOutputType(TypeNode.STRING) + .setAutoPopulatedFields(autoPopulatedFieldList) + .build(); + + Field FIELD = + Field.builder() + .setName("TestField") + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.STRING) + .build(); + List fieldList = new ArrayList<>(); + fieldList.add(FIELD); + + Message MESSAGE = + Message.builder() + .setFullProtoName("com.google.showcase.v1beta1.SampleRequest") + .setName("SampleRequest") + .setType(TypeNode.STRING) + .setFields(fieldList) + .build(); + + ImmutableMap messageTypes = + ImmutableMap.of("com.google.showcase.v1beta1.SampleRequest", MESSAGE); + + LambdaExpr requestMutator = + AbstractTransportServiceStubClassComposer.createRequestMutatorClassInstance( + METHOD, messageTypes); + + requestMutator.accept(writerVisitor); + + String expected = + LineFormatter.lines( + "request -> {\n", + "SampleRequest.Builder requestBuilder = request.toBuilder();\n", + "if (Strings.isNullOrEmpty(request.getTestField())) {\n", + "requestBuilder.setTestField(UUID.randomUUID().toString());\n", + "}\n", + "return requestBuilder.build();\n", + "}"); + assertEquals(expected, writerVisitor.write()); + } +} diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java index 43e5411b30..3345645c8c 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/defaultvalue/DefaultValueComposerTest.java @@ -569,6 +569,11 @@ public void createSimpleMessage_containsMessagesEnumsAndResourceName() { + "FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\").toString())" + ".setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\", \"[FOOBAR]\").toString())" + ".setRequestId(\"requestId693933066\")" + + ".setSecondRequestId(\"secondRequestId344404470\")" + + ".setThirdRequestId(true)" + + ".setFourthRequestId(\"fourthRequestId-2116417776\")" + + ".setFifthRequestId(\"fifthRequestId959024147\")" + + ".setSixthRequestId(\"sixthRequestId1005218260\")" + ".setSeverity(Severity.forNumber(0))" + ".setFoobar(Foobar.newBuilder().build()).build()", writerVisitor.write()); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden index f67a50d9c0..f1bf7437b4 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClient.golden @@ -518,6 +518,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); @@ -548,6 +553,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); @@ -624,6 +634,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); @@ -652,6 +667,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); @@ -683,6 +703,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); @@ -1092,6 +1117,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); @@ -1122,6 +1152,11 @@ public class EchoClient implements BackgroundResource { * .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) * .setRequestId("requestId693933066") + * .setSecondRequestId("secondRequestId344404470") + * .setThirdRequestId(true) + * .setFourthRequestId("fourthRequestId-2116417776") + * .setFifthRequestId("fifthRequestId959024147") + * .setSixthRequestId("sixthRequestId1005218260") * .setSeverity(Severity.forNumber(0)) * .setFoobar(Foobar.newBuilder().build()) * .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden index 009d8688df..a2a88d6bf3 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/EchoClientTest.golden @@ -109,6 +109,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -459,6 +464,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -485,6 +495,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -519,6 +534,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -545,6 +565,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -579,6 +604,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -605,6 +635,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -864,6 +899,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); @@ -878,6 +918,11 @@ public class EchoClientTest { Assert.assertEquals(request.getName(), actualRequest.getName()); Assert.assertEquals(request.getParent(), actualRequest.getParent()); Assert.assertEquals(request.getRequestId(), actualRequest.getRequestId()); + Assert.assertEquals(request.getSecondRequestId(), actualRequest.getSecondRequestId()); + Assert.assertEquals(request.getThirdRequestId(), actualRequest.getThirdRequestId()); + Assert.assertEquals(request.getFourthRequestId(), actualRequest.getFourthRequestId()); + Assert.assertEquals(request.getFifthRequestId(), actualRequest.getFifthRequestId()); + Assert.assertEquals(request.getSixthRequestId(), actualRequest.getSixthRequestId()); Assert.assertEquals(request.getContent(), actualRequest.getContent()); Assert.assertEquals(request.getError(), actualRequest.getError()); Assert.assertEquals(request.getSeverity(), actualRequest.getSeverity()); @@ -899,6 +944,11 @@ public class EchoClientTest { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden index 940ab7d4c4..c74e415fef 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcEchoStub.golden @@ -14,6 +14,7 @@ import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.base.Strings; import com.google.longrunning.Operation; import com.google.longrunning.stub.GrpcOperationsStub; import com.google.showcase.v1beta1.BlockRequest; @@ -30,6 +31,7 @@ import com.google.showcase.v1beta1.WaitResponse; import io.grpc.MethodDescriptor; import io.grpc.protobuf.ProtoUtils; import java.io.IOException; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.Generated; @@ -183,6 +185,17 @@ public class GrpcEchoStub extends EchoStub { GrpcCallSettings echoTransportSettings = GrpcCallSettings.newBuilder() .setMethodDescriptor(echoMethodDescriptor) + .setRequestMutator( + request -> { + EchoRequest.Builder requestBuilder = request.toBuilder(); + if (Strings.isNullOrEmpty(request.getRequestId())) { + requestBuilder.setRequestId(UUID.randomUUID().toString()); + } + if (Strings.isNullOrEmpty(request.getSecondRequestId())) { + requestBuilder.setSecondRequestId(UUID.randomUUID().toString()); + } + return requestBuilder.build(); + }) .build(); GrpcCallSettings expandTransportSettings = GrpcCallSettings.newBuilder() diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden index e17e9367c8..e67e1d5045 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChat.golden @@ -44,6 +44,11 @@ public class AsyncChat { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden index 02e38bd9db..16fdf6e73a 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncChatAgain.golden @@ -44,6 +44,11 @@ public class AsyncChatAgain { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden index cd8c21c72d..756c28f9b2 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollect.golden @@ -62,6 +62,11 @@ public class AsyncCollect { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden index 5ca5cd0bcc..8b22c05f7d 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncCollideName.golden @@ -43,6 +43,11 @@ public class AsyncCollideName { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden index 087faaebf5..685c2e16cb 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/AsyncEcho.golden @@ -43,6 +43,11 @@ public class AsyncEcho { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden index f299524598..e4beee3fcd 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncCollideName.golden @@ -42,6 +42,11 @@ public class SyncCollideName { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden index a0918f576d..29c754d9ae 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/samples/echoclient/SyncEcho.golden @@ -42,6 +42,11 @@ public class SyncEcho { .setName(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setParent(FoobarName.ofProjectFoobarName("[PROJECT]", "[FOOBAR]").toString()) .setRequestId("requestId693933066") + .setSecondRequestId("secondRequestId344404470") + .setThirdRequestId(true) + .setFourthRequestId("fourthRequestId-2116417776") + .setFifthRequestId("fifthRequestId959024147") + .setSixthRequestId("sixthRequestId1005218260") .setSeverity(Severity.forNumber(0)) .setFoobar(Foobar.newBuilder().build()) .build(); diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden index 18904bdfbe..d58ce093b9 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonEchoStub.golden @@ -23,6 +23,7 @@ import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.longrunning.Operation; import com.google.protobuf.TypeRegistry; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.Generated; @@ -408,6 +410,17 @@ public class HttpJsonEchoStub extends EchoStub { HttpJsonCallSettings.newBuilder() .setMethodDescriptor(echoMethodDescriptor) .setTypeRegistry(typeRegistry) + .setRequestMutator( + request -> { + EchoRequest.Builder requestBuilder = request.toBuilder(); + if (Strings.isNullOrEmpty(request.getRequestId())) { + requestBuilder.setRequestId(UUID.randomUUID().toString()); + } + if (Strings.isNullOrEmpty(request.getSecondRequestId())) { + requestBuilder.setSecondRequestId(UUID.randomUUID().toString()); + } + return requestBuilder.build(); + }) .build(); HttpJsonCallSettings expandTransportSettings = HttpJsonCallSettings.newBuilder() diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java index 44a921f3b0..47be643ffa 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientCallableMethodSampleComposerTest.java @@ -580,6 +580,11 @@ public void valid_composeStreamCallableMethod_bidiStream() { " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + " \"[FOOBAR]\").toString())\n", " .setRequestId(\"requestId693933066\")\n", + " .setSecondRequestId(\"secondRequestId344404470\")\n", + " .setThirdRequestId(true)\n", + " .setFourthRequestId(\"fourthRequestId-2116417776\")\n", + " .setFifthRequestId(\"fifthRequestId959024147\")\n", + " .setSixthRequestId(\"sixthRequestId1005218260\")\n", " .setSeverity(Severity.forNumber(0))\n", " .setFoobar(Foobar.newBuilder().build())\n", " .build();\n", @@ -713,6 +718,11 @@ public void valid_composeStreamCallableMethod_clientStream() { " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + " \"[FOOBAR]\").toString())\n", " .setRequestId(\"requestId693933066\")\n", + " .setSecondRequestId(\"secondRequestId344404470\")\n", + " .setThirdRequestId(true)\n", + " .setFourthRequestId(\"fourthRequestId-2116417776\")\n", + " .setFifthRequestId(\"fifthRequestId959024147\")\n", + " .setSixthRequestId(\"sixthRequestId1005218260\")\n", " .setSeverity(Severity.forNumber(0))\n", " .setFoobar(Foobar.newBuilder().build())\n", " .build();\n", @@ -820,6 +830,11 @@ public void valid_composeRegularCallableMethod_unaryRpc() { " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + " \"[FOOBAR]\").toString())\n", " .setRequestId(\"requestId693933066\")\n", + " .setSecondRequestId(\"secondRequestId344404470\")\n", + " .setThirdRequestId(true)\n", + " .setFourthRequestId(\"fourthRequestId-2116417776\")\n", + " .setFifthRequestId(\"fifthRequestId959024147\")\n", + " .setSixthRequestId(\"sixthRequestId1005218260\")\n", " .setSeverity(Severity.forNumber(0))\n", " .setFoobar(Foobar.newBuilder().build())\n", " .build();\n", diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java index 265882ac3f..affffb9c09 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientHeaderSampleComposerTest.java @@ -224,6 +224,11 @@ public void composeClassHeaderSample_firstMethodHasNoSignatures() { " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + " \"[FOOBAR]\").toString())\n", " .setRequestId(\"requestId693933066\")\n", + " .setSecondRequestId(\"secondRequestId344404470\")\n", + " .setThirdRequestId(true)\n", + " .setFourthRequestId(\"fourthRequestId-2116417776\")\n", + " .setFifthRequestId(\"fifthRequestId959024147\")\n", + " .setSixthRequestId(\"sixthRequestId1005218260\")\n", " .setSeverity(Severity.forNumber(0))\n", " .setFoobar(Foobar.newBuilder().build())\n", " .build();\n", diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java index 9839af8f31..6d473be885 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/samplecode/ServiceClientMethodSampleComposerTest.java @@ -336,6 +336,11 @@ public void valid_composeDefaultSample_pureUnaryReturnVoid() { " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + " \"[FOOBAR]\").toString())\n", " .setRequestId(\"requestId693933066\")\n", + " .setSecondRequestId(\"secondRequestId344404470\")\n", + " .setThirdRequestId(true)\n", + " .setFourthRequestId(\"fourthRequestId-2116417776\")\n", + " .setFifthRequestId(\"fifthRequestId959024147\")\n", + " .setSixthRequestId(\"sixthRequestId1005218260\")\n", " .setSeverity(Severity.forNumber(0))\n", " .setFoobar(Foobar.newBuilder().build())\n", " .build();\n", @@ -399,6 +404,11 @@ public void valid_composeDefaultSample_pureUnaryReturnResponse() { " .setParent(FoobarName.ofProjectFoobarName(\"[PROJECT]\"," + " \"[FOOBAR]\").toString())\n", " .setRequestId(\"requestId693933066\")\n", + " .setSecondRequestId(\"secondRequestId344404470\")\n", + " .setThirdRequestId(true)\n", + " .setFourthRequestId(\"fourthRequestId-2116417776\")\n", + " .setFifthRequestId(\"fifthRequestId959024147\")\n", + " .setSixthRequestId(\"sixthRequestId1005218260\")\n", " .setSeverity(Severity.forNumber(0))\n", " .setFoobar(Foobar.newBuilder().build())\n", " .build();\n", diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java new file mode 100644 index 0000000000..89ba60eabd --- /dev/null +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/model/FieldTest.java @@ -0,0 +1,76 @@ +// 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.api.generator.gapic.model; + +import static org.junit.Assert.assertEquals; + +import com.google.api.FieldInfo.Format; +import com.google.api.generator.engine.ast.TypeNode; +import org.junit.Test; + +public class FieldTest { + + @Test + public void shouldAutoPopulate() { + Field FIELD = + Field.builder() + .setName("SampleField") + .setIsRequired(false) + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.STRING) + .build(); + + assertEquals(true, FIELD.canBeAutoPopulated()); + } + + @Test + public void isRequired_shouldNotAutoPopulate() { + Field FIELD = + Field.builder() + .setName("SampleField") + .setIsRequired(true) + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.STRING) + .build(); + + assertEquals(false, FIELD.canBeAutoPopulated()); + } + + @Test + public void fieldInfoFormatNotUUID4_shouldNotAutoPopulate() { + Field FIELD = + Field.builder() + .setName("SampleField") + .setIsRequired(true) + .setFieldInfoFormat(Format.IPV6) + .setType(TypeNode.STRING) + .build(); + + assertEquals(false, FIELD.canBeAutoPopulated()); + } + + @Test + public void typeNotString_shouldNotAutoPopulate() { + Field FIELD = + Field.builder() + .setName("SampleField") + .setIsRequired(true) + .setFieldInfoFormat(Format.UUID4) + .setType(TypeNode.BOOLEAN) + .build(); + + assertEquals(false, FIELD.canBeAutoPopulated()); + } +} diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java index 16340e0a6b..8fdf2576c9 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/gapic/protoparser/ParserTest.java @@ -145,7 +145,14 @@ public void parseMethods_basic() { assertEquals(echoMethod.name(), "Echo"); assertEquals(echoMethod.stream(), Method.Stream.NONE); assertEquals(true, echoMethod.hasAutoPopulatedFields()); - assertEquals(Arrays.asList("request_id"), echoMethod.autoPopulatedFields()); + assertEquals( + Arrays.asList( + "request_id", + "second_request_id", + "third_request_id", + "fourth_request_id", + "non_existent_field"), + echoMethod.autoPopulatedFields()); // Detailed method signature parsing tests are in a separate unit test. List> methodSignatures = echoMethod.methodSignatures(); @@ -451,6 +458,21 @@ public void parseFields_autoPopulated() { field = message.fieldMap().get("severity"); assertEquals(false, field.isRequired()); assertEquals(null, field.fieldInfoFormat()); + field = message.fieldMap().get("second_request_id"); + assertEquals(false, field.isRequired()); + assertEquals(Format.UUID4, field.fieldInfoFormat()); + field = message.fieldMap().get("third_request_id"); + assertEquals(false, field.isRequired()); + assertEquals(Format.UUID4, field.fieldInfoFormat()); + field = message.fieldMap().get("fourth_request_id"); + assertEquals(false, field.isRequired()); + assertEquals(Format.IPV4_OR_IPV6, field.fieldInfoFormat()); + field = message.fieldMap().get("fifth_request_id"); + assertEquals(false, field.isRequired()); + assertEquals(Format.UUID4, field.fieldInfoFormat()); + field = message.fieldMap().get("sixth_request_id"); + assertEquals(true, field.isRequired()); + assertEquals(Format.UUID4, field.fieldInfoFormat()); message = messageTypes.get("com.google.showcase.v1beta1.ExpandRequest"); field = message.fieldMap().get("request_id"); @@ -465,7 +487,12 @@ public void parseAutoPopulatedMethodsAndFields_exists() { assertEquals( true, autoPopulatedMethodsWithFields.containsKey("google.showcase.v1beta1.Echo.Echo")); assertEquals( - Arrays.asList("request_id"), + Arrays.asList( + "request_id", + "second_request_id", + "third_request_id", + "fourth_request_id", + "non_existent_field"), autoPopulatedMethodsWithFields.get("google.showcase.v1beta1.Echo.Echo")); } diff --git a/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java b/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java index 79d37baf4f..8a812ea437 100644 --- a/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java +++ b/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java @@ -27,6 +27,7 @@ import com.google.api.generator.gapic.protoparser.BatchingSettingsConfigParser; import com.google.api.generator.gapic.protoparser.Parser; import com.google.api.generator.gapic.protoparser.ServiceConfigParser; +import com.google.api.generator.gapic.protoparser.ServiceYamlParser; import com.google.bookshop.v1beta1.BookshopProto; import com.google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTestingOuterClass; import com.google.logging.v2.LogEntryProto; @@ -162,12 +163,18 @@ public GapicContext parseShowcaseEcho() { ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0); assertEquals(echoServiceDescriptor.getName(), "Echo"); + String serviceYamlFilename = "echo_v1beta1.yaml"; + Path serviceYamlPath = Paths.get(testFilesDirectory, serviceYamlFilename); + Optional serviceYamlOpt = + ServiceYamlParser.parse(serviceYamlPath.toString()); + assertTrue(serviceYamlOpt.isPresent()); + Map messageTypes = Parser.parseMessages(echoFileDescriptor); Map resourceNames = Parser.parseResourceNames(echoFileDescriptor); Set outputResourceNames = new HashSet<>(); List services = Parser.parseService( - echoFileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames); + echoFileDescriptor, messageTypes, resourceNames, serviceYamlOpt, outputResourceNames); // Explicitly adds service description, since this is not parsed from source code location // in test protos, as it would from a protoc CodeGeneratorRequest diff --git a/gapic-generator-java/src/test/proto/echo.proto b/gapic-generator-java/src/test/proto/echo.proto index d963447340..effa0325cd 100644 --- a/gapic-generator-java/src/test/proto/echo.proto +++ b/gapic-generator-java/src/test/proto/echo.proto @@ -185,6 +185,34 @@ message EchoRequest { (google.api.field_info).format = UUID4 ]; + // This field is added to test that autopopulation works for multiple fields + string second_request_id = 8 [ + (google.api.field_behavior) = OPTIONAL, + (google.api.field_info).format = UUID4 + ]; + + // This field is added to test that autopopulation should not populate this field since it is not of type String + bool third_request_id = 9 [ + (google.api.field_info).format = UUID4 + ]; + + // This field is added to test that autopopulation should not populate this field since it is not annotated with UUID4 format + string fourth_request_id = 10 [ + (google.api.field_info).format = IPV4_OR_IPV6 + ]; + + // This field is added to test that autopopulation should not populate this field since it is not designated in the service_yaml + string fifth_request_id = 11 [ + (google.api.field_info).format = UUID4 + ]; + + // This field is added to test that autopopulation should not populate this field since it marked as Required + string sixth_request_id = 12 [ + (google.api.field_info).format = UUID4, + (google.api.field_behavior) = REQUIRED + ]; + + oneof response { // The content to be echoed by the server. string content = 1; diff --git a/gapic-generator-java/src/test/resources/echo_v1beta1.yaml b/gapic-generator-java/src/test/resources/echo_v1beta1.yaml index 57d9f90115..a6aea48e87 100644 --- a/gapic-generator-java/src/test/resources/echo_v1beta1.yaml +++ b/gapic-generator-java/src/test/resources/echo_v1beta1.yaml @@ -97,7 +97,10 @@ http: - post: '/v1beta3/{name=operations/**}:cancel' publishing: method_settings: - # TODO: Add more test cases for scenarios where the field does not exist, the field is not a String, etc. Eventually the API Linter should handle some of those cases. - selector: google.showcase.v1beta1.Echo.Echo auto_populated_fields: - - request_id \ No newline at end of file + - request_id + - second_request_id + - third_request_id + - fourth_request_id + - non_existent_field \ No newline at end of file diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java index a5aef3d69f..fae4ae9d25 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallSettings.java @@ -30,6 +30,7 @@ package com.google.api.gax.grpc; import com.google.api.core.BetaApi; +import com.google.api.gax.rpc.RequestMutator; import com.google.api.gax.rpc.RequestParamsExtractor; import io.grpc.MethodDescriptor; @@ -37,11 +38,13 @@ public class GrpcCallSettings { private final MethodDescriptor methodDescriptor; private final RequestParamsExtractor paramsExtractor; + private final RequestMutator requestMutator; private final boolean alwaysAwaitTrailers; private GrpcCallSettings(Builder builder) { this.methodDescriptor = builder.methodDescriptor; this.paramsExtractor = builder.paramsExtractor; + this.requestMutator = builder.requestMutator; this.alwaysAwaitTrailers = builder.shouldAwaitTrailers; } @@ -53,6 +56,10 @@ public RequestParamsExtractor getParamsExtractor() { return paramsExtractor; } + public RequestMutator getRequestMutator() { + return requestMutator; + } + @BetaApi public boolean shouldAwaitTrailers() { return alwaysAwaitTrailers; @@ -76,6 +83,8 @@ public Builder toBuilder() { public static class Builder { private MethodDescriptor methodDescriptor; private RequestParamsExtractor paramsExtractor; + + private RequestMutator requestMutator; private boolean shouldAwaitTrailers; private Builder() {} @@ -83,6 +92,7 @@ private Builder() {} private Builder(GrpcCallSettings settings) { this.methodDescriptor = settings.methodDescriptor; this.paramsExtractor = settings.paramsExtractor; + this.requestMutator = settings.requestMutator; this.shouldAwaitTrailers = settings.alwaysAwaitTrailers; } @@ -98,6 +108,11 @@ public Builder setParamsExtractor( return this; } + public Builder setRequestMutator(RequestMutator requestMutator) { + this.requestMutator = requestMutator; + return this; + } + @BetaApi public Builder setShouldAwaitTrailers(boolean b) { this.shouldAwaitTrailers = b; diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java index 8a0c4e0b37..974feb0c43 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcCallableFactory.java @@ -85,7 +85,13 @@ public static UnaryCallable createBas GrpcRawCallableFactory.createUnaryCallable( grpcCallSettings, callSettings.getRetryableCodes()); - callable = Callables.retrying(callable, callSettings, clientContext); + if (grpcCallSettings.getRequestMutator() != null) { + callable = + Callables.retrying( + callable, callSettings, clientContext, grpcCallSettings.getRequestMutator()); + } else { + callable = Callables.retrying(callable, callSettings, clientContext); + } return callable; } diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java index 5b6a5f1bad..33041145dd 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcDirectCallable.java @@ -30,6 +30,7 @@ package com.google.api.gax.grpc; import com.google.api.core.ApiFuture; +import com.google.api.core.InternalApi; import com.google.api.core.ListenableFutureToApiFuture; import com.google.api.gax.rpc.ApiCallContext; import com.google.api.gax.rpc.UnaryCallable; @@ -43,6 +44,7 @@ * *

Package-private for internal use. */ +@InternalApi class GrpcDirectCallable extends UnaryCallable { private final MethodDescriptor descriptor; private final boolean awaitTrailers; diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java index 7dd7732175..04411fc3d7 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallSettings.java @@ -29,6 +29,7 @@ */ package com.google.api.gax.httpjson; +import com.google.api.gax.rpc.RequestMutator; import com.google.api.gax.rpc.RequestParamsExtractor; import com.google.protobuf.TypeRegistry; @@ -36,11 +37,14 @@ public class HttpJsonCallSettings { private final ApiMethodDescriptor methodDescriptor; private final RequestParamsExtractor paramsExtractor; + + private final RequestMutator requestMutator; private final TypeRegistry typeRegistry; private HttpJsonCallSettings(Builder builder) { this.methodDescriptor = builder.methodDescriptor; this.paramsExtractor = builder.paramsExtractor; + this.requestMutator = builder.requestMutator; this.typeRegistry = builder.typeRegistry; } @@ -52,6 +56,10 @@ public RequestParamsExtractor getParamsExtractor() { return paramsExtractor; } + public RequestMutator getRequestMutator() { + return requestMutator; + } + public TypeRegistry getTypeRegistry() { return typeRegistry; } @@ -72,6 +80,8 @@ public Builder toBuilder() { } public static class Builder { + + private RequestMutator requestMutator; private ApiMethodDescriptor methodDescriptor; private RequestParamsExtractor paramsExtractor; private TypeRegistry typeRegistry; @@ -94,6 +104,11 @@ public Builder setParamsExtractor( return this; } + public Builder setRequestMutator(RequestMutator requestMutator) { + this.requestMutator = requestMutator; + return this; + } + public Builder setTypeRegistry(TypeRegistry typeRegistry) { this.typeRegistry = typeRegistry; return this; diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java index d95751e3b0..33e2ff886e 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java @@ -30,6 +30,7 @@ package com.google.api.gax.httpjson; import com.google.api.core.InternalApi; +import com.google.api.core.ObsoleteApi; import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.BatchingCallSettings; import com.google.api.gax.rpc.Callables; @@ -72,6 +73,22 @@ private static UnaryCallable createDi return callable; } + /** Create httpJson UnaryCallable with request mutator. */ + static UnaryCallable createUnaryCallable( + UnaryCallable innerCallable, + UnaryCallSettings callSettings, + HttpJsonCallSettings httpJsonCallSettings, + ClientContext clientContext) { + UnaryCallable callable = + new HttpJsonExceptionCallable<>(innerCallable, callSettings.getRetryableCodes()); + callable = + Callables.retrying( + callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator()); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); + } + + /** Use {@link #createUnaryCallable createUnaryCallable} method instead. */ + @ObsoleteApi("Please use other httpJson UnaryCallable method instead") static UnaryCallable createUnaryCallable( UnaryCallable innerCallable, UnaryCallSettings callSettings, @@ -96,7 +113,9 @@ public static UnaryCallable createBas ClientContext clientContext) { UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings); callable = new HttpJsonExceptionCallable<>(callable, callSettings.getRetryableCodes()); - callable = Callables.retrying(callable, callSettings, clientContext); + callable = + Callables.retrying( + callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator()); return callable; } @@ -123,7 +142,7 @@ public static UnaryCallable createUna clientContext.getTracerFactory(), getSpanName(httpJsonCallSettings.getMethodDescriptor())); - return createUnaryCallable(innerCallable, callSettings, clientContext); + return createUnaryCallable(innerCallable, callSettings, httpJsonCallSettings, clientContext); } /** @@ -141,7 +160,8 @@ UnaryCallable createPagedCallable( PagedCallSettings pagedCallSettings, ClientContext clientContext) { UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings); - callable = createUnaryCallable(callable, pagedCallSettings, clientContext); + callable = + createUnaryCallable(callable, pagedCallSettings, httpJsonCallSettings, clientContext); UnaryCallable pagedCallable = Callables.paged(callable, pagedCallSettings); return pagedCallable.withDefaultCallContext(clientContext.getDefaultCallContext()); @@ -162,7 +182,8 @@ public static UnaryCallable createBat BatchingCallSettings batchingCallSettings, ClientContext clientContext) { UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings); - callable = createUnaryCallable(callable, batchingCallSettings, clientContext); + callable = + createUnaryCallable(callable, batchingCallSettings, httpJsonCallSettings, clientContext); callable = Callables.batching(callable, batchingCallSettings, clientContext); return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java index d03d7e57f0..f7b9935d31 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java @@ -29,9 +29,14 @@ */ package com.google.api.gax.httpjson; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpMethods; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpStatusCodes; import com.google.api.core.ApiFuture; @@ -50,15 +55,16 @@ import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.UnknownException; import com.google.common.collect.ImmutableSet; -import com.google.common.truth.Truth; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.protobuf.TypeRegistry; import java.util.Set; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.threeten.bp.Duration; @@ -68,10 +74,27 @@ public class RetryingTest { @SuppressWarnings("unchecked") private final UnaryCallable callInt = Mockito.mock(UnaryCallable.class); + private final ApiMethodDescriptor FAKE_METHOD_DESCRIPTOR_FOR_REQUEST_MUTATOR = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.cloud.v1.Fake/FakeMethodForRequestMutator") + .setHttpMethod(HttpMethods.POST) + .setRequestFormatter(Mockito.mock(HttpRequestFormatter.class)) + .setResponseParser(Mockito.mock(HttpResponseParser.class)) + .build(); + + private final Integer initialRequest = 1; + private final Integer modifiedRequest = 0; + + private final HttpJsonCallSettings httpJsonCallSettings = + HttpJsonCallSettings.newBuilder() + .setRequestMutator(request -> modifiedRequest) + .setMethodDescriptor(FAKE_METHOD_DESCRIPTOR_FOR_REQUEST_MUTATOR) + .setTypeRegistry(TypeRegistry.newBuilder().build()) + .build(); + private RecordingScheduler executor; private FakeApiClock fakeClock; private ClientContext clientContext; - private static final int HTTP_CODE_PRECONDITION_FAILED = 412; private HttpResponseException HTTP_SERVICE_UNAVAILABLE_EXCEPTION = @@ -112,7 +135,7 @@ public void teardown() { @Test public void retry() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) @@ -121,8 +144,14 @@ public void retry() { UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - Truth.assertThat(callable.call(1)).isEqualTo(2); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + assertThat(callable.call(initialRequest)).isEqualTo(2); + + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0, 0).inOrder(); } @Test @@ -140,7 +169,7 @@ public void retryTotalTimeoutExceeded() { httpResponseException, HttpJsonStatusCode.of(Code.FAILED_PRECONDITION), false); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(apiException)) .thenReturn(ApiFutures.immediateFuture(2)); @@ -152,14 +181,19 @@ public void retryTotalTimeoutExceeded() { .build(); UnaryCallSettings callSettings = createSettings(retryable, retrySettings); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - assertThrows(ApiException.class, () -> callable.call(1)); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + assertThrows(ApiException.class, () -> callable.call(initialRequest)); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0); } @Test public void retryMaxAttemptsExceeded() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) .thenReturn(ApiFutures.immediateFuture(2)); @@ -167,14 +201,19 @@ public void retryMaxAttemptsExceeded() { RetrySettings retrySettings = FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(2).build(); UnaryCallSettings callSettings = createSettings(retryable, retrySettings); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - assertThrows(ApiException.class, () -> callable.call(1)); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + assertThrows(ApiException.class, () -> callable.call(initialRequest)); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0).inOrder(); } @Test public void retryWithinMaxAttempts() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) .thenReturn(ApiFutures.immediateFuture(2)); @@ -182,9 +221,13 @@ public void retryWithinMaxAttempts() { RetrySettings retrySettings = FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build(); UnaryCallSettings callSettings = createSettings(retryable, retrySettings); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - callable.call(1); - Truth.assertThat(callable.call(1)).isEqualTo(2); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + assertThat(callable.call(initialRequest)).isEqualTo(2); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0).inOrder(); } @Test @@ -196,7 +239,7 @@ public void retryOnStatusUnknown() { "temporary redirect", new HttpHeaders()) .build(); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(throwable)) .thenReturn(ApiFutures.immediateFailedFuture(throwable)) .thenReturn(ApiFutures.immediateFailedFuture(throwable)) @@ -204,22 +247,32 @@ public void retryOnStatusUnknown() { UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - Truth.assertThat(callable.call(1)).isEqualTo(2); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + assertThat(callable.call(initialRequest)).isEqualTo(2); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0, 0).inOrder(); } @Test public void retryOnUnexpectedException() { ImmutableSet retryable = ImmutableSet.of(Code.UNKNOWN); Throwable throwable = new RuntimeException("foobar"); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(throwable)); UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - ApiException exception = assertThrows(ApiException.class, () -> callable.call(1)); - Truth.assertThat(exception).hasCauseThat().isSameInstanceAs(throwable); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + ApiException exception = assertThrows(ApiException.class, () -> callable.call(initialRequest)); + assertThat(exception).hasCauseThat().isSameInstanceAs(throwable); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder(); } @Test @@ -235,15 +288,20 @@ public void retryNoRecover() { httpResponseException, HttpJsonStatusCode.of(Code.FAILED_PRECONDITION), false); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(apiException)) .thenReturn(ApiFutures.immediateFuture(2)); UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - ApiException exception = assertThrows(ApiException.class, () -> callable.call(1)); - Truth.assertThat(exception).isSameInstanceAs(apiException); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + ApiException exception = assertThrows(ApiException.class, () -> callable.call(initialRequest)); + assertThat(exception).isSameInstanceAs(apiException); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0); } @Test @@ -253,19 +311,24 @@ public void retryKeepFailing() { new HttpResponseException.Builder( HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, "Unavailable", new HttpHeaders()) .build(); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(throwable)); UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); // Need to advance time inside the call. - ApiFuture future = callable.futureCall(1); + ApiFuture future = callable.futureCall(initialRequest); UncheckedExecutionException exception = assertThrows(UncheckedExecutionException.class, () -> Futures.getUnchecked(future)); - Truth.assertThat(exception).hasCauseThat().isInstanceOf(ApiException.class); - Truth.assertThat(exception).hasCauseThat().hasMessageThat().contains("Unavailable"); + assertThat(exception).hasCauseThat().isInstanceOf(ApiException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("Unavailable"); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getValue()).isEqualTo(0); } @Test @@ -289,34 +352,45 @@ public void testKnownStatusCode() { HTTP_CODE_PRECONDITION_FAILED, "precondition failed", new HttpHeaders()) .setMessage(throwableMessage) .build(); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(throwable)); UnaryCallSettings callSettings = UnaryCallSettings.newUnaryCallSettingsBuilder() .setRetryableCodes(retryable) .build(); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); ApiException exception = - assertThrows(FailedPreconditionException.class, () -> callable.call(1)); - Truth.assertThat(exception.getStatusCode().getTransportCode()) + assertThrows(FailedPreconditionException.class, () -> callable.call(initialRequest)); + assertThat(exception.getStatusCode().getTransportCode()) .isEqualTo(HTTP_CODE_PRECONDITION_FAILED); - Truth.assertThat(exception).hasMessageThat().contains("precondition failed"); + assertThat(exception).hasMessageThat().contains("precondition failed"); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder(); } @Test public void testUnknownStatusCode() { ImmutableSet retryable = ImmutableSet.of(); - Mockito.when(callInt.futureCall((Integer) Mockito.any(), (ApiCallContext) Mockito.any())) + Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) .thenReturn(ApiFutures.immediateFailedFuture(new RuntimeException("unknown"))); UnaryCallSettings callSettings = UnaryCallSettings.newUnaryCallSettingsBuilder() .setRetryableCodes(retryable) .build(); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); - UnknownException exception = assertThrows(UnknownException.class, () -> callable.call(1)); - Truth.assertThat(exception).hasMessageThat().isEqualTo("java.lang.RuntimeException: unknown"); + HttpJsonCallableFactory.createUnaryCallable( + callInt, callSettings, httpJsonCallSettings, clientContext); + UnknownException exception = + assertThrows(UnknownException.class, () -> callable.call(initialRequest)); + assertThat(exception).hasMessageThat().isEqualTo("java.lang.RuntimeException: unknown"); + // Capture the argument passed to futureCall + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); + verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder(); } public static UnaryCallSettings createSettings( diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java index 28a76fe721..b1f4b51d6a 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/Callables.java @@ -51,11 +51,53 @@ public class Callables { private Callables() {} + /** + * Create a callable object that represents a Unary API method. Designed for use by generated + * code. + * + * @param innerCallable the callable to issue calls + * @param callSettings {@link UnaryCallSettings} to configure the unary call-related settings + * with. + * @param clientContext {@link ClientContext} to use to connect to the service. + * @return {@link UnaryCallable} callable object. + */ public static UnaryCallable retrying( UnaryCallable innerCallable, UnaryCallSettings callSettings, ClientContext clientContext) { + ScheduledRetryingExecutor retryingExecutor = + getRetryingExecutor(callSettings, clientContext); + return new RetryingCallable<>( + clientContext.getDefaultCallContext(), innerCallable, retryingExecutor); + } + + /** + * Create a callable object that represents a Unary API method that contains a Request Mutator. + * Designed for use by generated code. + * + * @param innerCallable the callable to issue calls + * @param callSettings {@link UnaryCallSettings} to configure the unary call-related settings + * with. + * @param clientContext {@link ClientContext} to use to connect to the service. + * @param requestMutator {@link RequestMutator} to modify the request. Currently only used for + * autopopulated fields. + * @return {@link UnaryCallable} callable object. + */ + public static UnaryCallable retrying( + UnaryCallable innerCallable, + UnaryCallSettings callSettings, + ClientContext clientContext, + RequestMutator requestMutator) { + + ScheduledRetryingExecutor retryingExecutor = + getRetryingExecutor(callSettings, clientContext); + return new RetryingCallable<>( + clientContext.getDefaultCallContext(), innerCallable, retryingExecutor, requestMutator); + } + + private static ScheduledRetryingExecutor getRetryingExecutor( + UnaryCallSettings callSettings, ClientContext clientContext) { UnaryCallSettings settings = callSettings; if (areRetriesDisabled(settings.getRetryableCodes(), settings.getRetrySettings())) { @@ -73,8 +115,7 @@ public static UnaryCallable retrying( new ExponentialRetryAlgorithm(settings.getRetrySettings(), clientContext.getClock())); ScheduledRetryingExecutor retryingExecutor = new ScheduledRetryingExecutor<>(retryAlgorithm, clientContext.getExecutor()); - return new RetryingCallable<>( - clientContext.getDefaultCallContext(), innerCallable, retryingExecutor); + return retryingExecutor; } public static ServerStreamingCallable retrying( diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java new file mode 100644 index 0000000000..d26949d87d --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RequestMutator.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.rpc; + +import com.google.api.core.InternalApi; + +/** + * A request mutator takes a {@code request} message, applies some Function to it, and then returns + * the modified {@code request} message. This is currently only used for autopopulation of the + * request ID. + * + *

Implementations of this interface are expected to be autogenerated. + * + * @param request message type + */ +@InternalApi("For use by transport-specific implementations") +@FunctionalInterface +public interface RequestMutator { + /** + * Applies a Function to {@code request} message + * + * @param request request message + */ + RequestT apply(RequestT request); +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java index 0a92794a20..e4fe13295a 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/RetryingCallable.java @@ -43,20 +43,35 @@ class RetryingCallable extends UnaryCallable callable; private final RetryingExecutorWithContext executor; + private final RequestMutator requestMutator; + RetryingCallable( ApiCallContext callContextPrototype, UnaryCallable callable, RetryingExecutorWithContext executor) { + this(callContextPrototype, callable, executor, null); + } + + RetryingCallable( + ApiCallContext callContextPrototype, + UnaryCallable callable, + RetryingExecutorWithContext executor, + RequestMutator requestMutator) { this.callContextPrototype = Preconditions.checkNotNull(callContextPrototype); this.callable = Preconditions.checkNotNull(callable); this.executor = Preconditions.checkNotNull(executor); + this.requestMutator = requestMutator; } @Override public RetryingFuture futureCall(RequestT request, ApiCallContext inputContext) { ApiCallContext context = callContextPrototype.nullToSelf(inputContext); + RequestT modifiedRequest = request; + if (this.requestMutator != null) { + modifiedRequest = requestMutator.apply(request); + } AttemptCallable retryCallable = - new AttemptCallable<>(callable, request, context); + new AttemptCallable<>(callable, modifiedRequest, context); RetryingFuture retryingFuture = executor.createFuture(retryCallable, inputContext); retryCallable.setExternalFuture(retryingFuture); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java index 04e025fecb..8d24a19c53 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/CallableTest.java @@ -29,17 +29,20 @@ */ package com.google.api.gax.rpc; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.api.core.ApiFuture; import com.google.api.core.SettableApiFuture; import com.google.api.gax.retrying.RetrySettings; import com.google.api.gax.rpc.testing.FakeCallContext; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; @@ -78,17 +81,28 @@ public void testNonRetriedCallable() throws Exception { innerResult = SettableApiFuture.create(); when(innerCallable.futureCall(anyString(), any(ApiCallContext.class))).thenReturn(innerResult); Duration timeout = Duration.ofMillis(5L); + String initialRequest = "Is your refrigerator running?"; + String modifiedRequest = "What about now?"; + + RequestMutator requestMutator = (request -> modifiedRequest); UnaryCallSettings callSettings = UnaryCallSettings.newUnaryCallSettingsBuilder().setSimpleTimeoutNoRetries(timeout).build(); UnaryCallable callable = - Callables.retrying(innerCallable, callSettings, clientContext); - innerResult.set("No, my refrigerator is not running!"); + Callables.retrying(innerCallable, callSettings, clientContext, requestMutator); + String expectedResponse = "No, my refrigerator is not running!"; + innerResult.set(expectedResponse); + + ApiFuture futureResponse = callable.futureCall(initialRequest, callContext); - callable.futureCall("Is your refrigerator running?", callContext); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + verify(innerCallable).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + String expectedRequest = "What about now?"; + assertEquals(expectedRequest, argumentCaptor.getValue()); verify(callContext, atLeastOnce()).getRetrySettings(); verify(callContext).getTimeout(); verify(callContext).withTimeout(timeout); + assertEquals(expectedResponse, futureResponse.get()); } @Test @@ -96,21 +110,33 @@ public void testNonRetriedCallableWithRetrySettings() throws Exception { innerResult = SettableApiFuture.create(); when(innerCallable.futureCall(anyString(), any(ApiCallContext.class))).thenReturn(innerResult); + String initialRequest = "Is your refrigerator running?"; + String modifiedRequest = "What about now?"; + RequestMutator requestMutator = (request -> modifiedRequest); + UnaryCallSettings callSettings = UnaryCallSettings.newUnaryCallSettingsBuilder() .setSimpleTimeoutNoRetries(Duration.ofMillis(10L)) .build(); UnaryCallable callable = - Callables.retrying(innerCallable, callSettings, clientContext); - innerResult.set("No, my refrigerator is not running!"); + Callables.retrying(innerCallable, callSettings, clientContext, requestMutator); + String expectedResponse = "No, my refrigerator is not running!"; + innerResult.set(expectedResponse); Duration timeout = retrySettings.getInitialRpcTimeout(); - callable.futureCall("Is your refrigerator running?", callContextWithRetrySettings); + ApiFuture futureResponse = + callable.futureCall(initialRequest, callContextWithRetrySettings); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + verify(innerCallable).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); + String expectedRequest = "What about now?"; + assertEquals(expectedRequest, argumentCaptor.getValue()); verify(callContextWithRetrySettings, atLeastOnce()).getRetrySettings(); verify(callContextWithRetrySettings).getTimeout(); verify(callContextWithRetrySettings).withTimeout(timeout); + assertEquals(expectedResponse, futureResponse.get()); } @Test diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java new file mode 100644 index 0000000000..0411892979 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/RetryingCallableTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.rpc; + +import static org.mockito.Mockito.when; + +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.retrying.RetryingExecutorWithContext; +import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.retrying.TimedAttemptSettings; +import com.google.api.gax.rpc.testing.FakeCallContext; +import java.util.concurrent.Callable; +import org.junit.Test; +import org.mockito.Mockito; +import org.threeten.bp.Duration; +import org.threeten.bp.temporal.ChronoUnit; + +public class RetryingCallableTest { + @Test + public void futureCall() { + FakeCallContext fakeCallContext = FakeCallContext.createDefault(); + UnaryCallable innerCallable = Mockito.mock(UnaryCallable.class); + RetryingExecutorWithContext executor = Mockito.mock(RetryingExecutorWithContext.class); + RetryingFuture retryingFuture = Mockito.mock(RetryingFuture.class); + TimedAttemptSettings fakeAttemptSettings = + TimedAttemptSettings.newBuilder() + .setRpcTimeout(Duration.of(1, ChronoUnit.SECONDS)) + .setAttemptCount(3) + .setGlobalSettings(RetrySettings.newBuilder().build()) + .setRetryDelay(Duration.of(1, ChronoUnit.SECONDS)) + .setRandomizedRetryDelay(Duration.of(1, ChronoUnit.SECONDS)) + .setFirstAttemptStartTimeNanos(5) + .build(); + + when(retryingFuture.getAttemptSettings()).thenReturn(fakeAttemptSettings); + when(executor.createFuture(Mockito.any(Callable.class), Mockito.eq(fakeCallContext))) + .thenReturn(retryingFuture); + Integer modifiedRequest = 5; + RequestMutator requestMutator = request -> modifiedRequest; + RetryingCallable retryingCallable = + new RetryingCallable<>(fakeCallContext, innerCallable, executor, requestMutator); + + retryingCallable.futureCall(1, fakeCallContext); + Mockito.verify(innerCallable) + .futureCall(Mockito.eq(modifiedRequest), Mockito.any(ApiCallContext.class)); + } +} diff --git a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/GrpcEchoStub.java b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/GrpcEchoStub.java index ebdcb34c32..217a66b80c 100644 --- a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/GrpcEchoStub.java +++ b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/GrpcEchoStub.java @@ -37,6 +37,7 @@ import com.google.cloud.location.ListLocationsRequest; import com.google.cloud.location.ListLocationsResponse; import com.google.cloud.location.Location; +import com.google.common.base.Strings; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; @@ -62,6 +63,7 @@ import io.grpc.protobuf.ProtoUtils; import java.io.IOException; import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.Generated; @@ -305,6 +307,14 @@ protected GrpcEchoStub( builder.add(request.getOtherHeader(), "qux", ECHO_7_PATH_TEMPLATE); return builder.build(); }) + .setRequestMutator( + request -> { + EchoRequest.Builder requestBuilder = request.toBuilder(); + if (Strings.isNullOrEmpty(request.getRequestId())) { + requestBuilder.setRequestId(UUID.randomUUID().toString()); + } + return requestBuilder.build(); + }) .build(); GrpcCallSettings echoErrorDetailsTransportSettings = diff --git a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java index 1116e8a9b2..80d9439ec9 100644 --- a/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java +++ b/showcase/gapic-showcase/src/main/java/com/google/showcase/v1beta1/stub/HttpJsonEchoStub.java @@ -46,6 +46,7 @@ import com.google.cloud.location.ListLocationsRequest; import com.google.cloud.location.ListLocationsResponse; import com.google.cloud.location.Location; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.Policy; @@ -73,6 +74,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.Generated; @@ -660,6 +662,14 @@ protected HttpJsonEchoStub( builder.add(request.getOtherHeader(), "qux", ECHO_7_PATH_TEMPLATE); return builder.build(); }) + .setRequestMutator( + request -> { + EchoRequest.Builder requestBuilder = request.toBuilder(); + if (Strings.isNullOrEmpty(request.getRequestId())) { + requestBuilder.setRequestId(UUID.randomUUID().toString()); + } + return requestBuilder.build(); + }) .build(); HttpJsonCallSettings echoErrorDetailsTransportSettings = diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITAutoPopulatedFields.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITAutoPopulatedFields.java new file mode 100644 index 0000000000..d448a2af8b --- /dev/null +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITAutoPopulatedFields.java @@ -0,0 +1,373 @@ +/* + * 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 + * + * https://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.showcase.v1beta1.it; + +import static org.junit.Assert.assertThrows; + +import com.google.api.gax.httpjson.ApiMethodDescriptor; +import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall; +import com.google.api.gax.httpjson.HttpJsonCallOptions; +import com.google.api.gax.httpjson.HttpJsonChannel; +import com.google.api.gax.httpjson.HttpJsonClientCall; +import com.google.api.gax.httpjson.HttpJsonClientInterceptor; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.rpc.StatusCode.Code; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.truth.Truth; +import com.google.rpc.Status; +import com.google.showcase.v1beta1.EchoClient; +import com.google.showcase.v1beta1.EchoRequest; +import com.google.showcase.v1beta1.EchoResponse; +import com.google.showcase.v1beta1.it.util.TestClientInitializer; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.MethodDescriptor; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.threeten.bp.Duration; + +public class ITAutoPopulatedFields { + + private static class HttpJsonInterceptor implements HttpJsonClientInterceptor { + private Consumer onRequestIntercepted; + + private HttpJsonInterceptor() {} + + private void setOnRequestIntercepted(Consumer onRequestIntercepted) { + this.onRequestIntercepted = onRequestIntercepted; + } + + @Override + public HttpJsonClientCall interceptCall( + ApiMethodDescriptor method, + HttpJsonCallOptions callOptions, + HttpJsonChannel next) { + HttpJsonClientCall call = next.newCall(method, callOptions); + + return new ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall( + call) { + @Override + public void sendMessage(ReqT message) { + // Capture the request message + if (onRequestIntercepted != null) { + onRequestIntercepted.accept(message); + } + super.sendMessage(message); + } + }; + } + } + + // Implement a request interceptor to retrieve the request ID being sent on the request. + private static class GRPCInterceptor implements ClientInterceptor { + private Consumer onRequestIntercepted; + + private GRPCInterceptor() {} + + private void setOnRequestIntercepted(Consumer onRequestIntercepted) { + this.onRequestIntercepted = onRequestIntercepted; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + @Override + public void sendMessage(ReqT message) { + // Capture the request message + if (onRequestIntercepted != null) { + onRequestIntercepted.accept(message); + } + super.sendMessage(message); + } + }; + } + } + + private GRPCInterceptor grpcRequestInterceptor; + private HttpJsonInterceptor httpJsonInterceptor; + private EchoClient grpcClientWithoutRetries; + private EchoClient grpcClientWithRetries; + + private EchoClient httpJsonClient; + private EchoClient httpJsonClientWithRetries; + + @Before + public void createClients() throws Exception { + RetrySettings defaultRetrySettings = + RetrySettings.newBuilder() + .setInitialRpcTimeout(Duration.ofMillis(5000L)) + .setRpcTimeoutMultiplier(1.0) + .setMaxRpcTimeout(Duration.ofMillis(5000L)) + .setTotalTimeout(Duration.ofMillis(5000L)) + // Cap retries at 5 + .setMaxAttempts(5) + .build(); + + // Adding `Code.INTERNAL` is necessary because for httpJson requests, the httpJson status code + // is mapped here: + // https://github.com/googleapis/sdk-platform-java/blob/acdde47445916dd306ce8b91489fab45c9c2ef50/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonStatusCode.java#L96-L133 + // Therefore, just setting the error code to `Code.UNKNOWN` for httpJson will get translated + // instead to `Code.INTERNAL`. + Set retryableCodes = ImmutableSet.of(Code.UNKNOWN, Code.INTERNAL); + + // Create gRPC Interceptor and Client + grpcRequestInterceptor = new ITAutoPopulatedFields.GRPCInterceptor(); + grpcClientWithoutRetries = + TestClientInitializer.createGrpcEchoClient(ImmutableList.of(grpcRequestInterceptor)); + grpcClientWithRetries = + TestClientInitializer.createGrpcEchoClientWithRetrySettings( + defaultRetrySettings, retryableCodes, ImmutableList.of(grpcRequestInterceptor)); + + // Create HttpJson Interceptor and Client + httpJsonInterceptor = new ITAutoPopulatedFields.HttpJsonInterceptor(); + httpJsonClient = + TestClientInitializer.createHttpJsonEchoClient(ImmutableList.of(httpJsonInterceptor)); + httpJsonClientWithRetries = + TestClientInitializer.createHttpJsonEchoClientWithRetrySettings( + defaultRetrySettings, retryableCodes, ImmutableList.of(httpJsonInterceptor)); + } + + @After + public void destroyClient() { + grpcClientWithoutRetries.close(); + grpcClientWithRetries.close(); + httpJsonClient.close(); + } + + @Test + public void testGrpc_autoPopulateRequestIdWhenAttemptedOnceSuccessfully() { + List capturedRequestIds = new ArrayList<>(); + grpcRequestInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + grpcClientWithoutRetries.echo(EchoRequest.newBuilder().build()); + Truth.assertThat(capturedRequestIds).isNotEmpty(); + // Autopopulation of UUID is currently only configured for format UUID4. + Integer UUIDVersion = 4; + Truth.assertThat(UUID.fromString(capturedRequestIds.get(0)).version()).isEqualTo(UUIDVersion); + } + + @Test + public void testGrpc_shouldNotAutoPopulateRequestIdIfSetInRequest() { + List capturedRequestIds = new ArrayList<>(); + grpcRequestInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + String UUIDsent = UUID.randomUUID().toString(); + grpcClientWithoutRetries.echo(EchoRequest.newBuilder().setRequestId(UUIDsent).build()); + Truth.assertThat(capturedRequestIds).isNotEmpty(); + Truth.assertThat(capturedRequestIds).contains(UUIDsent); + } + + @Test + public void testHttpJson_autoPopulateRequestIdWhenAttemptedOnceSuccessfully() { + List capturedRequestIds = new ArrayList<>(); + httpJsonInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + httpJsonClient.echo(EchoRequest.newBuilder().build()); + Truth.assertThat(capturedRequestIds).isNotEmpty(); + // Autopopulation of UUID is currently only configured for format UUID4. + Integer UUIDVersion = 4; + Truth.assertThat(UUID.fromString(capturedRequestIds.get(0)).version()).isEqualTo(UUIDVersion); + } + + @Test + public void testHttpJson_shouldNotAutoPopulateRequestIdIfSetInRequest() { + String UUIDsent = UUID.randomUUID().toString(); + List capturedRequestIds = new ArrayList<>(); + httpJsonInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + httpJsonClient.echo(EchoRequest.newBuilder().setRequestId(UUIDsent).build()); + Truth.assertThat(capturedRequestIds).isNotEmpty(); + Truth.assertThat(capturedRequestIds).contains(UUIDsent); + } + + @Test + public void testGRPC_setsSameRequestIdIfSetInRequestWhenRequestsAreRetried() throws Exception { + List capturedRequestIds = new ArrayList<>(); + grpcRequestInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + String UUIDsent = UUID.randomUUID().toString(); + EchoRequest requestSent = + EchoRequest.newBuilder() + .setRequestId(UUIDsent) + .setError(Status.newBuilder().setCode(Code.UNKNOWN.ordinal()).build()) + .build(); + + try { + RetryingFuture retryingFuture = + (RetryingFuture) + grpcClientWithRetries.echoCallable().futureCall(requestSent); + assertThrows(ExecutionException.class, () -> retryingFuture.get(10, TimeUnit.SECONDS)); + // assert that the number of request IDs is equal to the max attempt + Truth.assertThat(capturedRequestIds).hasSize(5); + // assert that each request ID sent is the same as the UUIDSent + Truth.assertThat(capturedRequestIds) + .containsExactly(UUIDsent, UUIDsent, UUIDsent, UUIDsent, UUIDsent); + } finally { + grpcClientWithRetries.close(); + grpcClientWithRetries.awaitTermination( + TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + } + + @Test + public void testGRPC_setsSameAutoPopulatedRequestIdWhenRequestsAreRetried() throws Exception { + List capturedRequestIds = new ArrayList<>(); + grpcRequestInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + + EchoRequest requestSent = + EchoRequest.newBuilder() + .setError(Status.newBuilder().setCode(Code.UNKNOWN.ordinal()).build()) + .build(); + + try { + RetryingFuture retryingFuture = + (RetryingFuture) + grpcClientWithRetries.echoCallable().futureCall(requestSent); + assertThrows(ExecutionException.class, () -> retryingFuture.get(10, TimeUnit.SECONDS)); + // assert that the number of request IDs is equal to the max attempt + Truth.assertThat(capturedRequestIds).hasSize(5); + // assert that each request ID sent is the same + Truth.assertThat(capturedRequestIds) + .containsExactly( + capturedRequestIds.get(0), + capturedRequestIds.get(0), + capturedRequestIds.get(0), + capturedRequestIds.get(0), + capturedRequestIds.get(0)); + } finally { + grpcClientWithRetries.close(); + grpcClientWithRetries.awaitTermination( + TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + } + + @Test + public void testHttpJson_setsSameRequestIdIfSetInRequestWhenRequestsAreRetried() + throws Exception { + List capturedRequestIds = new ArrayList<>(); + httpJsonInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + String UUIDsent = UUID.randomUUID().toString(); + EchoRequest requestSent = + EchoRequest.newBuilder() + .setRequestId(UUIDsent) + .setError(Status.newBuilder().setCode(Code.UNKNOWN.getHttpStatusCode()).build()) + .build(); + try { + RetryingFuture retryingFuture = + (RetryingFuture) + httpJsonClientWithRetries.echoCallable().futureCall(requestSent); + assertThrows(ExecutionException.class, () -> retryingFuture.get(10, TimeUnit.SECONDS)); + // assert that the number of request IDs is equal to the max attempt + Truth.assertThat(capturedRequestIds).hasSize(5); + // assert that each request ID sent is the same as the UUIDSent + Truth.assertThat(capturedRequestIds) + .containsExactly(UUIDsent, UUIDsent, UUIDsent, UUIDsent, UUIDsent); + } finally { + httpJsonClientWithRetries.close(); + httpJsonClientWithRetries.awaitTermination( + TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + } + + @Test + public void testHttpJson_setsSameAutoPopulatedRequestIdWhenRequestsAreRetried() throws Exception { + List capturedRequestIds = new ArrayList<>(); + httpJsonInterceptor.setOnRequestIntercepted( + request -> { + if (request instanceof EchoRequest) { + EchoRequest echoRequest = (EchoRequest) request; + capturedRequestIds.add(echoRequest.getRequestId()); + } + }); + EchoRequest requestSent = + EchoRequest.newBuilder() + .setError(Status.newBuilder().setCode(Code.UNKNOWN.getHttpStatusCode()).build()) + .build(); + try { + RetryingFuture retryingFuture = + (RetryingFuture) + httpJsonClientWithRetries.echoCallable().futureCall(requestSent); + assertThrows(ExecutionException.class, () -> retryingFuture.get(10, TimeUnit.SECONDS)); + // assert that the number of request IDs is equal to the max attempt + Truth.assertThat(capturedRequestIds).hasSize(5); + // assert that each request ID sent is the same + Truth.assertThat(capturedRequestIds) + .containsExactly( + capturedRequestIds.get(0), + capturedRequestIds.get(0), + capturedRequestIds.get(0), + capturedRequestIds.get(0), + capturedRequestIds.get(0)); + } finally { + httpJsonClientWithRetries.close(); + httpJsonClientWithRetries.awaitTermination( + TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + } +} diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java index d2606402d8..e620e254c6 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java @@ -61,6 +61,31 @@ public static EchoClient createGrpcEchoClient(List intercepto return EchoClient.create(grpcEchoSettings); } + public static EchoClient createGrpcEchoClientWithRetrySettings( + RetrySettings retrySettings, + Set retryableCodes, + List interceptorList) + throws Exception { + EchoStubSettings.Builder grpcEchoSettingsBuilder = EchoStubSettings.newBuilder(); + grpcEchoSettingsBuilder + .echoSettings() + .setRetrySettings(retrySettings) + .setRetryableCodes(retryableCodes); + EchoSettings grpcEchoSettings = EchoSettings.create(grpcEchoSettingsBuilder.build()); + grpcEchoSettings = + grpcEchoSettings + .toBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider( + EchoSettings.defaultGrpcTransportProviderBuilder() + .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) + .setInterceptorProvider(() -> interceptorList) + .build()) + .setEndpoint("localhost:7469") + .build(); + return EchoClient.create(grpcEchoSettings); + } + public static EchoClient createHttpJsonEchoClient() throws Exception { return createHttpJsonEchoClient(ImmutableList.of()); } @@ -81,6 +106,32 @@ public static EchoClient createHttpJsonEchoClient(List retryableCodes, + List interceptorList) + throws Exception { + EchoStubSettings.Builder httpJsonEchoSettingsBuilder = EchoStubSettings.newHttpJsonBuilder(); + httpJsonEchoSettingsBuilder + .echoSettings() + .setRetrySettings(retrySettings) + .setRetryableCodes(retryableCodes); + EchoSettings httpJsonEchoSettings = EchoSettings.create(httpJsonEchoSettingsBuilder.build()); + httpJsonEchoSettings = + httpJsonEchoSettings + .toBuilder() + .setCredentialsProvider(NoCredentialsProvider.create()) + .setTransportChannelProvider( + EchoSettings.defaultHttpJsonTransportProviderBuilder() + .setHttpTransport( + new NetHttpTransport.Builder().doNotValidateCertificate().build()) + .setInterceptorProvider(() -> interceptorList) + .setEndpoint("http://localhost:7469") + .build()) + .build(); + return EchoClient.create(httpJsonEchoSettings); + } + public static EchoClient createGrpcEchoClientCustomBlockSettings( RetrySettings retrySettings, Set retryableCodes) throws Exception { EchoStubSettings.Builder grpcEchoSettingsBuilder = EchoStubSettings.newBuilder(); From 8e2536b44b3bf0c8d577bb7e5cec5099bf71321b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 19:11:31 -0500 Subject: [PATCH 11/11] chore(main): release 2.34.0 (#2417) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .cloudbuild/cloudbuild-test-a.yaml | 2 +- .cloudbuild/cloudbuild-test-b.yaml | 2 +- .cloudbuild/cloudbuild.yaml | 2 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++ WORKSPACE | 2 +- api-common-java/pom.xml | 4 +-- coverage-report/pom.xml | 8 ++--- gapic-generator-java-bom/pom.xml | 26 +++++++-------- gapic-generator-java-pom-parent/pom.xml | 2 +- gapic-generator-java/pom.xml | 6 ++-- gax-java/README.md | 12 +++---- gax-java/dependencies.properties | 8 ++--- gax-java/gax-bom/pom.xml | 20 ++++++------ gax-java/gax-grpc/pom.xml | 4 +-- gax-java/gax-httpjson/pom.xml | 4 +-- gax-java/gax/pom.xml | 4 +-- gax-java/pom.xml | 14 ++++---- .../grpc-google-common-protos/pom.xml | 4 +-- java-common-protos/pom.xml | 10 +++--- .../proto-google-common-protos/pom.xml | 4 +-- java-core/google-cloud-core-bom/pom.xml | 10 +++--- java-core/google-cloud-core-grpc/pom.xml | 4 +-- java-core/google-cloud-core-http/pom.xml | 4 +-- java-core/google-cloud-core/pom.xml | 4 +-- java-core/pom.xml | 6 ++-- java-iam/grpc-google-iam-v1/pom.xml | 4 +-- java-iam/grpc-google-iam-v2/pom.xml | 4 +-- java-iam/grpc-google-iam-v2beta/pom.xml | 4 +-- java-iam/pom.xml | 22 ++++++------- java-iam/proto-google-iam-v1/pom.xml | 4 +-- java-iam/proto-google-iam-v2/pom.xml | 4 +-- java-iam/proto-google-iam-v2beta/pom.xml | 4 +-- java-shared-dependencies/README.md | 2 +- .../dependency-convergence-check/pom.xml | 2 +- .../first-party-dependencies/pom.xml | 10 +++--- java-shared-dependencies/pom.xml | 8 ++--- .../third-party-dependencies/pom.xml | 2 +- .../upper-bound-check/pom.xml | 4 +-- sdk-platform-java-config/pom.xml | 4 +-- showcase/pom.xml | 2 +- versions.txt | 32 +++++++++---------- 42 files changed, 156 insertions(+), 140 deletions(-) diff --git a/.cloudbuild/cloudbuild-test-a.yaml b/.cloudbuild/cloudbuild-test-a.yaml index ce86b57f64..5da2f41078 100644 --- a/.cloudbuild/cloudbuild-test-a.yaml +++ b/.cloudbuild/cloudbuild-test-a.yaml @@ -14,7 +14,7 @@ timeout: 7200s # 2 hours substitutions: - _SHARED_DEPENDENCIES_VERSION: '3.23.1-SNAPSHOT' # {x-version-update:google-cloud-shared-dependencies:current} + _SHARED_DEPENDENCIES_VERSION: '3.24.0' # {x-version-update:google-cloud-shared-dependencies:current} _JAVA_SHARED_CONFIG_VERSION: '1.7.1' steps: diff --git a/.cloudbuild/cloudbuild-test-b.yaml b/.cloudbuild/cloudbuild-test-b.yaml index 2962473493..5fa0b16766 100644 --- a/.cloudbuild/cloudbuild-test-b.yaml +++ b/.cloudbuild/cloudbuild-test-b.yaml @@ -14,7 +14,7 @@ timeout: 7200s # 2 hours substitutions: - _SHARED_DEPENDENCIES_VERSION: '3.23.1-SNAPSHOT' # {x-version-update:google-cloud-shared-dependencies:current} + _SHARED_DEPENDENCIES_VERSION: '3.24.0' # {x-version-update:google-cloud-shared-dependencies:current} _JAVA_SHARED_CONFIG_VERSION: '1.7.1' steps: diff --git a/.cloudbuild/cloudbuild.yaml b/.cloudbuild/cloudbuild.yaml index bdb68a394e..ae3d152d9f 100644 --- a/.cloudbuild/cloudbuild.yaml +++ b/.cloudbuild/cloudbuild.yaml @@ -14,7 +14,7 @@ timeout: 7200s # 2 hours substitutions: - _SHARED_DEPENDENCIES_VERSION: '3.23.1-SNAPSHOT' # {x-version-update:google-cloud-shared-dependencies:current} + _SHARED_DEPENDENCIES_VERSION: '3.24.0' # {x-version-update:google-cloud-shared-dependencies:current} _JAVA_SHARED_CONFIG_VERSION: '1.7.1' steps: # GraalVM A build diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7ef8288ed5..7a1c4674e4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.33.0" + ".": "2.34.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c1ed7304..96c80a2b00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [2.34.0](https://github.com/googleapis/sdk-platform-java/compare/v2.33.0...v2.34.0) (2024-01-31) + + +### Features + +* autopopulate fields in the request ([#2353](https://github.com/googleapis/sdk-platform-java/issues/2353)) ([b28235a](https://github.com/googleapis/sdk-platform-java/commit/b28235ab20fd174deddafc0426b8d20352af6e85)) +* enable generation with postprocessing of multiple service versions ([#2342](https://github.com/googleapis/sdk-platform-java/issues/2342)) ([363e35e](https://github.com/googleapis/sdk-platform-java/commit/363e35e46e41c88b810e4b0672906f73cb7c38b6)) +* MetricsTracer implementation ([#2421](https://github.com/googleapis/sdk-platform-java/issues/2421)) ([5c291e8](https://github.com/googleapis/sdk-platform-java/commit/5c291e8786b8e976979ec2e26b13f0327333bb02)) +* move new client script ([#2333](https://github.com/googleapis/sdk-platform-java/issues/2333)) ([acdde47](https://github.com/googleapis/sdk-platform-java/commit/acdde47445916dd306ce8b91489fab45c9c2ef50)) + + +### Bug Fixes + +* Endpoint resolution uses user set endpoint from ClientSettings ([#2429](https://github.com/googleapis/sdk-platform-java/issues/2429)) ([46b0a85](https://github.com/googleapis/sdk-platform-java/commit/46b0a857eaa4484c5f1ebe1170338fc90a994375)) +* Move direct path misconfiguration log to before creating the first channel ([#2430](https://github.com/googleapis/sdk-platform-java/issues/2430)) ([9916540](https://github.com/googleapis/sdk-platform-java/commit/99165403902ff91ecb0b14b858333855e7a10c60)) + ## [2.33.0](https://github.com/googleapis/sdk-platform-java/compare/v2.32.0...v2.33.0) (2024-01-24) diff --git a/WORKSPACE b/WORKSPACE index 9587d18a8e..2a36da16fd 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -60,7 +60,7 @@ maven_install( repositories = ["https://repo.maven.apache.org/maven2/"], ) -_gapic_generator_java_version = "2.33.1-SNAPSHOT" # {x-version-update:gapic-generator-java:current} +_gapic_generator_java_version = "2.34.0" # {x-version-update:gapic-generator-java:current} maven_install( artifacts = [ diff --git a/api-common-java/pom.xml b/api-common-java/pom.xml index 1e0aa6ea2e..634ce87ad4 100644 --- a/api-common-java/pom.xml +++ b/api-common-java/pom.xml @@ -5,14 +5,14 @@ com.google.api api-common jar - 2.24.1-SNAPSHOT + 2.25.0 API Common Common utilities for Google APIs in Java com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index 1a329e354a..39fa549992 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -31,22 +31,22 @@ com.google.api gax - 2.41.1-SNAPSHOT + 2.42.0 com.google.api gax-grpc - 2.41.1-SNAPSHOT + 2.42.0 com.google.api gax-httpjson - 2.41.1-SNAPSHOT + 2.42.0 com.google.api api-common - 2.24.1-SNAPSHOT + 2.25.0 diff --git a/gapic-generator-java-bom/pom.xml b/gapic-generator-java-bom/pom.xml index f803972c4f..6a7cb04b1b 100644 --- a/gapic-generator-java-bom/pom.xml +++ b/gapic-generator-java-bom/pom.xml @@ -4,7 +4,7 @@ com.google.api gapic-generator-java-bom pom - 2.33.1-SNAPSHOT + 2.34.0 GAPIC Generator Java BOM BOM for the libraries in gapic-generator-java repository. Users should not @@ -15,7 +15,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -75,61 +75,61 @@ com.google.api api-common - 2.24.1-SNAPSHOT + 2.25.0 com.google.api gax-bom - 2.41.1-SNAPSHOT + 2.42.0 pom import com.google.api gapic-generator-java - 2.33.1-SNAPSHOT + 2.34.0 com.google.api.grpc grpc-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 com.google.api.grpc proto-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 com.google.api.grpc proto-google-iam-v1 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc proto-google-iam-v2 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc proto-google-iam-v2beta - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc grpc-google-iam-v1 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc grpc-google-iam-v2 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc grpc-google-iam-v2beta - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/gapic-generator-java-pom-parent/pom.xml b/gapic-generator-java-pom-parent/pom.xml index 98607a685e..9bf02cdf3c 100644 --- a/gapic-generator-java-pom-parent/pom.xml +++ b/gapic-generator-java-pom-parent/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 pom GAPIC Generator Java POM Parent https://github.com/googleapis/sdk-platform-java diff --git a/gapic-generator-java/pom.xml b/gapic-generator-java/pom.xml index 74bc39c81e..b0016c80c8 100644 --- a/gapic-generator-java/pom.xml +++ b/gapic-generator-java/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.google.api gapic-generator-java - 2.33.1-SNAPSHOT + 2.34.0 GAPIC Generator Java GAPIC generator Java @@ -22,7 +22,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -31,7 +31,7 @@ com.google.api gapic-generator-java-bom - 2.33.1-SNAPSHOT + 2.34.0 pom import diff --git a/gax-java/README.md b/gax-java/README.md index cb8d3bb116..88df3f9977 100644 --- a/gax-java/README.md +++ b/gax-java/README.md @@ -34,27 +34,27 @@ If you are using Maven, add this to your pom.xml file com.google.api gax - 2.41.0 + 2.42.0 com.google.api gax-grpc - 2.41.0 + 2.42.0 ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.api:gax:2.41.0', - 'com.google.api:gax-grpc:2.41.0' +compile 'com.google.api:gax:2.42.0', + 'com.google.api:gax-grpc:2.42.0' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.api" % "gax" % "2.41.0" -libraryDependencies += "com.google.api" % "gax-grpc" % "2.41.0" +libraryDependencies += "com.google.api" % "gax" % "2.42.0" +libraryDependencies += "com.google.api" % "gax-grpc" % "2.42.0" ``` [//]: # ({x-version-update-end}) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 46145b1551..01787402a9 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -8,16 +8,16 @@ # Versions of oneself # {x-version-update-start:gax:current} -version.gax=2.41.1-SNAPSHOT +version.gax=2.42.0 # {x-version-update-end} # {x-version-update-start:gax:current} -version.gax_grpc=2.41.1-SNAPSHOT +version.gax_grpc=2.42.0 # {x-version-update-end} # {x-version-update-start:gax:current} -version.gax_bom=2.41.1-SNAPSHOT +version.gax_bom=2.42.0 # {x-version-update-end} # {x-version-update-start:gax:current} -version.gax_httpjson=2.41.1-SNAPSHOT +version.gax_httpjson=2.42.0 # {x-version-update-end} # Versions for dependencies which actual artifacts differ between Bazel and Gradle. diff --git a/gax-java/gax-bom/pom.xml b/gax-java/gax-bom/pom.xml index 0f9c05eaa2..b1ff32cca5 100644 --- a/gax-java/gax-bom/pom.xml +++ b/gax-java/gax-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.api gax-bom - 2.41.1-SNAPSHOT + 2.42.0 pom GAX (Google Api eXtensions) for Java (BOM) Google Api eXtensions for Java (BOM) @@ -43,55 +43,55 @@ com.google.api gax - 2.41.1-SNAPSHOT + 2.42.0 com.google.api gax - 2.41.1-SNAPSHOT + 2.42.0 test-jar testlib com.google.api gax - 2.41.1-SNAPSHOT + 2.42.0 testlib com.google.api gax-grpc - 2.41.1-SNAPSHOT + 2.42.0 com.google.api gax-grpc - 2.41.1-SNAPSHOT + 2.42.0 test-jar testlib com.google.api gax-grpc - 2.41.1-SNAPSHOT + 2.42.0 testlib com.google.api gax-httpjson - 2.41.1-SNAPSHOT + 2.42.0 com.google.api gax-httpjson - 2.41.1-SNAPSHOT + 2.42.0 test-jar testlib com.google.api gax-httpjson - 2.41.1-SNAPSHOT + 2.42.0 testlib diff --git a/gax-java/gax-grpc/pom.xml b/gax-java/gax-grpc/pom.xml index 655bed36bd..cad83c87cd 100644 --- a/gax-java/gax-grpc/pom.xml +++ b/gax-java/gax-grpc/pom.xml @@ -3,7 +3,7 @@ 4.0.0 gax-grpc - 2.41.1-SNAPSHOT + 2.42.0 jar GAX (Google Api eXtensions) for Java (gRPC) Google Api eXtensions for Java (gRPC) @@ -11,7 +11,7 @@ com.google.api gax-parent - 2.41.1-SNAPSHOT + 2.42.0 diff --git a/gax-java/gax-httpjson/pom.xml b/gax-java/gax-httpjson/pom.xml index 28ed2c25c8..91ba2be7dc 100644 --- a/gax-java/gax-httpjson/pom.xml +++ b/gax-java/gax-httpjson/pom.xml @@ -3,7 +3,7 @@ 4.0.0 gax-httpjson - 2.41.1-SNAPSHOT + 2.42.0 jar GAX (Google Api eXtensions) for Java (HTTP JSON) Google Api eXtensions for Java (HTTP JSON) @@ -11,7 +11,7 @@ com.google.api gax-parent - 2.41.1-SNAPSHOT + 2.42.0 diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index 6473de3e20..dc8b7ae1a0 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -3,7 +3,7 @@ 4.0.0 gax - 2.41.1-SNAPSHOT + 2.42.0 jar GAX (Google Api eXtensions) for Java (Core) Google Api eXtensions for Java (Core) @@ -11,7 +11,7 @@ com.google.api gax-parent - 2.41.1-SNAPSHOT + 2.42.0 diff --git a/gax-java/pom.xml b/gax-java/pom.xml index fad3ac4f99..d96d3d3246 100644 --- a/gax-java/pom.xml +++ b/gax-java/pom.xml @@ -4,14 +4,14 @@ com.google.api gax-parent pom - 2.41.1-SNAPSHOT + 2.42.0 GAX (Google Api eXtensions) for Java (Parent) Google Api eXtensions for Java (Parent) com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -50,7 +50,7 @@ com.google.api api-common - 2.24.1-SNAPSHOT + 2.25.0 com.google.auth @@ -108,24 +108,24 @@ com.google.api gax - 2.41.1-SNAPSHOT + 2.42.0 com.google.api gax - 2.41.1-SNAPSHOT + 2.42.0 test-jar testlib com.google.api.grpc proto-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 com.google.api.grpc grpc-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 io.grpc diff --git a/java-common-protos/grpc-google-common-protos/pom.xml b/java-common-protos/grpc-google-common-protos/pom.xml index a139d17528..ed1026f0ab 100644 --- a/java-common-protos/grpc-google-common-protos/pom.xml +++ b/java-common-protos/grpc-google-common-protos/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 grpc-google-common-protos GRPC library for grpc-google-common-protos com.google.api.grpc google-common-protos-parent - 2.32.1-SNAPSHOT + 2.33.0 diff --git a/java-common-protos/pom.xml b/java-common-protos/pom.xml index 148b528c50..8d28d4fb6c 100644 --- a/java-common-protos/pom.xml +++ b/java-common-protos/pom.xml @@ -4,7 +4,7 @@ com.google.api.grpc google-common-protos-parent pom - 2.32.1-SNAPSHOT + 2.33.0 Google Common Protos Parent Java idiomatic client for Google Cloud Platform services. @@ -13,7 +13,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -61,7 +61,7 @@ com.google.cloud third-party-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import @@ -75,7 +75,7 @@ com.google.api.grpc grpc-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 io.grpc @@ -87,7 +87,7 @@ com.google.api.grpc proto-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 com.google.guava diff --git a/java-common-protos/proto-google-common-protos/pom.xml b/java-common-protos/proto-google-common-protos/pom.xml index fdd4aad8ae..4fb8633234 100644 --- a/java-common-protos/proto-google-common-protos/pom.xml +++ b/java-common-protos/proto-google-common-protos/pom.xml @@ -3,13 +3,13 @@ 4.0.0 com.google.api.grpc proto-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 proto-google-common-protos PROTO library for proto-google-common-protos com.google.api.grpc google-common-protos-parent - 2.32.1-SNAPSHOT + 2.33.0 diff --git a/java-core/google-cloud-core-bom/pom.xml b/java-core/google-cloud-core-bom/pom.xml index 16591c7dac..6b959aad03 100644 --- a/java-core/google-cloud-core-bom/pom.xml +++ b/java-core/google-cloud-core-bom/pom.xml @@ -3,13 +3,13 @@ 4.0.0 com.google.cloud google-cloud-core-bom - 2.31.1-SNAPSHOT + 2.32.0 pom com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../../gapic-generator-java-pom-parent @@ -23,17 +23,17 @@ com.google.cloud google-cloud-core - 2.31.1-SNAPSHOT + 2.32.0 com.google.cloud google-cloud-core-grpc - 2.31.1-SNAPSHOT + 2.32.0 com.google.cloud google-cloud-core-http - 2.31.1-SNAPSHOT + 2.32.0 diff --git a/java-core/google-cloud-core-grpc/pom.xml b/java-core/google-cloud-core-grpc/pom.xml index 22c8a54e7c..4d0fc0eb02 100644 --- a/java-core/google-cloud-core-grpc/pom.xml +++ b/java-core/google-cloud-core-grpc/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-grpc - 2.31.1-SNAPSHOT + 2.32.0 jar Google Cloud Core gRPC @@ -12,7 +12,7 @@ com.google.cloud google-cloud-core-parent - 2.31.1-SNAPSHOT + 2.32.0 google-cloud-core-grpc diff --git a/java-core/google-cloud-core-http/pom.xml b/java-core/google-cloud-core-http/pom.xml index 20ab155679..19709544bf 100644 --- a/java-core/google-cloud-core-http/pom.xml +++ b/java-core/google-cloud-core-http/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core-http - 2.31.1-SNAPSHOT + 2.32.0 jar Google Cloud Core HTTP @@ -12,7 +12,7 @@ com.google.cloud google-cloud-core-parent - 2.31.1-SNAPSHOT + 2.32.0 google-cloud-core-http diff --git a/java-core/google-cloud-core/pom.xml b/java-core/google-cloud-core/pom.xml index c77cd7e10a..f90c30d29b 100644 --- a/java-core/google-cloud-core/pom.xml +++ b/java-core/google-cloud-core/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-core - 2.31.1-SNAPSHOT + 2.32.0 jar Google Cloud Core @@ -12,7 +12,7 @@ com.google.cloud google-cloud-core-parent - 2.31.1-SNAPSHOT + 2.32.0 google-cloud-core diff --git a/java-core/pom.xml b/java-core/pom.xml index 1a5dbc2052..f283f2ad3f 100644 --- a/java-core/pom.xml +++ b/java-core/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-core-parent pom - 2.31.1-SNAPSHOT + 2.32.0 Google Cloud Core Parent Java idiomatic client for Google Cloud Platform services. @@ -13,7 +13,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -33,7 +33,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import diff --git a/java-iam/grpc-google-iam-v1/pom.xml b/java-iam/grpc-google-iam-v1/pom.xml index 383edc5ad1..b02ca9744f 100644 --- a/java-iam/grpc-google-iam-v1/pom.xml +++ b/java-iam/grpc-google-iam-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-iam-v1 - 1.27.1-SNAPSHOT + 1.28.0 grpc-google-iam-v1 GRPC library for grpc-google-iam-v1 com.google.cloud google-iam-parent - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/java-iam/grpc-google-iam-v2/pom.xml b/java-iam/grpc-google-iam-v2/pom.xml index cde0a76a03..494c33f129 100644 --- a/java-iam/grpc-google-iam-v2/pom.xml +++ b/java-iam/grpc-google-iam-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-iam-v2 - 1.27.1-SNAPSHOT + 1.28.0 grpc-google-iam-v2 GRPC library for proto-google-iam-v2 com.google.cloud google-iam-parent - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/java-iam/grpc-google-iam-v2beta/pom.xml b/java-iam/grpc-google-iam-v2beta/pom.xml index 9a8ded0cef..ffb3dfcc35 100644 --- a/java-iam/grpc-google-iam-v2beta/pom.xml +++ b/java-iam/grpc-google-iam-v2beta/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-iam-v2beta - 1.27.1-SNAPSHOT + 1.28.0 grpc-google-iam-v2beta GRPC library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/java-iam/pom.xml b/java-iam/pom.xml index c6fa565caf..072e092fd5 100644 --- a/java-iam/pom.xml +++ b/java-iam/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-iam-parent pom - 1.27.1-SNAPSHOT + 1.28.0 Google IAM Parent Java idiomatic client for Google Cloud Platform services. @@ -13,7 +13,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -60,7 +60,7 @@ com.google.cloud third-party-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import @@ -88,44 +88,44 @@ com.google.api gax-bom - 2.41.1-SNAPSHOT + 2.42.0 pom import com.google.api.grpc proto-google-iam-v2 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc grpc-google-iam-v2 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc proto-google-common-protos - 2.32.1-SNAPSHOT + 2.33.0 com.google.api.grpc proto-google-iam-v2beta - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc grpc-google-iam-v1 - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc grpc-google-iam-v2beta - 1.27.1-SNAPSHOT + 1.28.0 com.google.api.grpc proto-google-iam-v1 - 1.27.1-SNAPSHOT + 1.28.0 javax.annotation diff --git a/java-iam/proto-google-iam-v1/pom.xml b/java-iam/proto-google-iam-v1/pom.xml index c260543c85..98ac0f86ab 100644 --- a/java-iam/proto-google-iam-v1/pom.xml +++ b/java-iam/proto-google-iam-v1/pom.xml @@ -3,13 +3,13 @@ 4.0.0 com.google.api.grpc proto-google-iam-v1 - 1.27.1-SNAPSHOT + 1.28.0 proto-google-iam-v1 PROTO library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/java-iam/proto-google-iam-v2/pom.xml b/java-iam/proto-google-iam-v2/pom.xml index ea830bc5dd..c9eeafcb44 100644 --- a/java-iam/proto-google-iam-v2/pom.xml +++ b/java-iam/proto-google-iam-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-iam-v2 - 1.27.1-SNAPSHOT + 1.28.0 proto-google-iam-v2 Proto library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/java-iam/proto-google-iam-v2beta/pom.xml b/java-iam/proto-google-iam-v2beta/pom.xml index 54475015b1..9255511be0 100644 --- a/java-iam/proto-google-iam-v2beta/pom.xml +++ b/java-iam/proto-google-iam-v2beta/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-iam-v2beta - 1.27.1-SNAPSHOT + 1.28.0 proto-google-iam-v2beta Proto library for proto-google-iam-v1 com.google.cloud google-iam-parent - 1.27.1-SNAPSHOT + 1.28.0 diff --git a/java-shared-dependencies/README.md b/java-shared-dependencies/README.md index 365048a0ef..3199dad83a 100644 --- a/java-shared-dependencies/README.md +++ b/java-shared-dependencies/README.md @@ -14,7 +14,7 @@ If you are using Maven, add this to the `dependencyManagement` section. com.google.cloud google-cloud-shared-dependencies - 3.23.0 + 3.24.0 pom import diff --git a/java-shared-dependencies/dependency-convergence-check/pom.xml b/java-shared-dependencies/dependency-convergence-check/pom.xml index 6840b12de0..f2c0917ddf 100644 --- a/java-shared-dependencies/dependency-convergence-check/pom.xml +++ b/java-shared-dependencies/dependency-convergence-check/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud shared-dependencies-dependency-convergence-test - 3.23.1-SNAPSHOT + 3.24.0 Dependency convergence test for certain artifacts in Google Cloud Shared Dependencies An dependency convergence test case for the shared dependencies BOM. A failure of this test case means diff --git a/java-shared-dependencies/first-party-dependencies/pom.xml b/java-shared-dependencies/first-party-dependencies/pom.xml index c5019e9684..7ca9988f8a 100644 --- a/java-shared-dependencies/first-party-dependencies/pom.xml +++ b/java-shared-dependencies/first-party-dependencies/pom.xml @@ -6,7 +6,7 @@ com.google.cloud first-party-dependencies pom - 3.23.1-SNAPSHOT + 3.24.0 Google Cloud First-party Shared Dependencies Shared first-party dependencies for Google Cloud Java libraries. @@ -33,7 +33,7 @@ com.google.api gapic-generator-java-bom - 2.33.1-SNAPSHOT + 2.34.0 pom import @@ -45,7 +45,7 @@ com.google.cloud google-cloud-core-bom - 2.31.1-SNAPSHOT + 2.32.0 pom import @@ -69,13 +69,13 @@ com.google.cloud google-cloud-core - 2.31.1-SNAPSHOT + 2.32.0 test-jar com.google.cloud google-cloud-core - 2.31.1-SNAPSHOT + 2.32.0 tests diff --git a/java-shared-dependencies/pom.xml b/java-shared-dependencies/pom.xml index e68d6215b6..035c4e2412 100644 --- a/java-shared-dependencies/pom.xml +++ b/java-shared-dependencies/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-shared-dependencies pom - 3.23.1-SNAPSHOT + 3.24.0 first-party-dependencies third-party-dependencies @@ -17,7 +17,7 @@ com.google.api gapic-generator-java-pom-parent - 2.33.1-SNAPSHOT + 2.34.0 ../gapic-generator-java-pom-parent @@ -31,14 +31,14 @@ com.google.cloud first-party-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import com.google.cloud third-party-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import diff --git a/java-shared-dependencies/third-party-dependencies/pom.xml b/java-shared-dependencies/third-party-dependencies/pom.xml index 3af13353d3..5b0da14d5e 100644 --- a/java-shared-dependencies/third-party-dependencies/pom.xml +++ b/java-shared-dependencies/third-party-dependencies/pom.xml @@ -6,7 +6,7 @@ com.google.cloud third-party-dependencies pom - 3.23.1-SNAPSHOT + 3.24.0 Google Cloud Third-party Shared Dependencies Shared third-party dependencies for Google Cloud Java libraries. diff --git a/java-shared-dependencies/upper-bound-check/pom.xml b/java-shared-dependencies/upper-bound-check/pom.xml index db5789cd5c..5b0f62a5c3 100644 --- a/java-shared-dependencies/upper-bound-check/pom.xml +++ b/java-shared-dependencies/upper-bound-check/pom.xml @@ -4,7 +4,7 @@ com.google.cloud shared-dependencies-upper-bound-test pom - 3.23.1-SNAPSHOT + 3.24.0 Upper bound test for Google Cloud Shared Dependencies An upper bound test case for the shared dependencies BOM. A failure of this test case means @@ -30,7 +30,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import diff --git a/sdk-platform-java-config/pom.xml b/sdk-platform-java-config/pom.xml index cc33a7d085..4770a70f22 100644 --- a/sdk-platform-java-config/pom.xml +++ b/sdk-platform-java-config/pom.xml @@ -4,7 +4,7 @@ com.google.cloud sdk-platform-java-config pom - 3.23.1-SNAPSHOT + 3.24.0 SDK Platform For Java Configurations Shared build configuration for Google Cloud Java libraries. @@ -17,6 +17,6 @@ - 3.23.1-SNAPSHOT + 3.24.0 \ No newline at end of file diff --git a/showcase/pom.xml b/showcase/pom.xml index 546296279c..c7f62b15fd 100644 --- a/showcase/pom.xml +++ b/showcase/pom.xml @@ -34,7 +34,7 @@ com.google.cloud google-cloud-shared-dependencies - 3.23.1-SNAPSHOT + 3.24.0 pom import diff --git a/versions.txt b/versions.txt index 92faa04311..acedcf4b31 100644 --- a/versions.txt +++ b/versions.txt @@ -1,19 +1,19 @@ # Format: # module:released-version:current-version -gapic-generator-java:2.33.0:2.33.1-SNAPSHOT -api-common:2.24.0:2.24.1-SNAPSHOT -gax:2.41.0:2.41.1-SNAPSHOT -gax-grpc:2.41.0:2.41.1-SNAPSHOT -gax-httpjson:0.126.0:0.126.1-SNAPSHOT -proto-google-common-protos:2.32.0:2.32.1-SNAPSHOT -grpc-google-common-protos:2.32.0:2.32.1-SNAPSHOT -proto-google-iam-v1:1.27.0:1.27.1-SNAPSHOT -grpc-google-iam-v1:1.27.0:1.27.1-SNAPSHOT -proto-google-iam-v2beta:1.27.0:1.27.1-SNAPSHOT -grpc-google-iam-v2beta:1.27.0:1.27.1-SNAPSHOT -google-iam-policy:1.27.0:1.27.1-SNAPSHOT -proto-google-iam-v2:1.27.0:1.27.1-SNAPSHOT -grpc-google-iam-v2:1.27.0:1.27.1-SNAPSHOT -google-cloud-core:2.31.0:2.31.1-SNAPSHOT -google-cloud-shared-dependencies:3.23.0:3.23.1-SNAPSHOT +gapic-generator-java:2.34.0:2.34.0 +api-common:2.25.0:2.25.0 +gax:2.42.0:2.42.0 +gax-grpc:2.42.0:2.42.0 +gax-httpjson:0.127.0:0.127.0 +proto-google-common-protos:2.33.0:2.33.0 +grpc-google-common-protos:2.33.0:2.33.0 +proto-google-iam-v1:1.28.0:1.28.0 +grpc-google-iam-v1:1.28.0:1.28.0 +proto-google-iam-v2beta:1.28.0:1.28.0 +grpc-google-iam-v2beta:1.28.0:1.28.0 +google-iam-policy:1.28.0:1.28.0 +proto-google-iam-v2:1.28.0:1.28.0 +grpc-google-iam-v2:1.28.0:1.28.0 +google-cloud-core:2.32.0:2.32.0 +google-cloud-shared-dependencies:3.24.0:3.24.0