From 3d765ae66979ae066bcea07fe9ca01008ec4e4d7 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 27 Jan 2021 19:22:40 +0100 Subject: [PATCH 01/35] chore(deps): update dependency google-cloud-logging to v2.2.0 (#164) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index db786f889..cb5be4aef 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ -google-cloud-logging==2.1.1 +google-cloud-logging==2.2.0 google-cloud-storage==1.35.0 google-cloud-pubsub==2.2.0 google-cloud-bigquery==2.6.2 From 18a0bb7fd8d158baef532b1c715524e5f6303311 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Fri, 29 Jan 2021 17:08:02 -0800 Subject: [PATCH 02/35] build: migrate to flakybot (#170) --- .kokoro/test-samples.sh | 8 ++++---- .kokoro/trampoline_v2.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index ba97b53d5..e75891832 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -87,11 +87,11 @@ for file in samples/**/requirements.txt; do python3.6 -m nox -s "$RUN_TESTS_SESSION" EXIT=$? - # If this is a periodic build, send the test log to the Build Cop Bot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop - $KOKORO_GFILE_DIR/linux_amd64/buildcop + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot fi if [[ $EXIT -ne 0 ]]; then diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh index 719bcd5ba..4af6cdc26 100755 --- a/.kokoro/trampoline_v2.sh +++ b/.kokoro/trampoline_v2.sh @@ -159,7 +159,7 @@ if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then "KOKORO_GITHUB_COMMIT" "KOKORO_GITHUB_PULL_REQUEST_NUMBER" "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For Build Cop Bot + # For FlakyBot "KOKORO_GITHUB_COMMIT_URL" "KOKORO_GITHUB_PULL_REQUEST_URL" ) From 1a3c047b616f0201af14f2c97ca81ea8d1098745 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 2 Feb 2021 19:32:33 +0100 Subject: [PATCH 03/35] chore(deps): update dependency google-cloud-bigquery to v2.7.0 (#166) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index cb5be4aef..94edb42b3 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 google-cloud-storage==1.35.0 google-cloud-pubsub==2.2.0 -google-cloud-bigquery==2.6.2 +google-cloud-bigquery==2.7.0 From 7cc727598c33e7e264ddbeef0a2604a3c215b260 Mon Sep 17 00:00:00 2001 From: Nicole Zhu <69952136+nicoleczhu@users.noreply.github.com> Date: Thu, 4 Feb 2021 10:59:04 -0800 Subject: [PATCH 04/35] docs: add python std_logging to sample browser (#173) * docs: add existing region tag to python std logging This is so this existing Python example will show up here: https://cloud.google.com/logging/docs/samples/logging-stdlogging --- samples/snippets/handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/samples/snippets/handler.py b/samples/snippets/handler.py index 9a63d022f..0a708c138 100644 --- a/samples/snippets/handler.py +++ b/samples/snippets/handler.py @@ -16,6 +16,7 @@ def use_logging_handler(): + # [START logging_stdlogging] # [START logging_handler_setup] # Imports the Cloud Logging client library import google.cloud.logging @@ -43,6 +44,7 @@ def use_logging_handler(): # [END logging_handler_usage] print("Logged: {}".format(text)) + # [END logging_stdlogging] if __name__ == "__main__": From 11976b9f3834e4ab976940fe88c2f5784d1b1795 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 8 Feb 2021 09:25:39 -0800 Subject: [PATCH 05/35] chore: update templates (#168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * chore: add 3.9 to noxfile template Since the python-docs-samples noxfile-template doesn't sync with this, I wanted to make sure the noxfile template matched the most recent change [here](https://github.com/GoogleCloudPlatform/python-docs-samples/pull/4968/files) cc @tmatsuo Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Fri Jan 15 17:24:05 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 56ddc68f36b32341e9f22c2c59b4ce6aa3ba635f Source-Link: https://github.com/googleapis/synthtool/commit/56ddc68f36b32341e9f22c2c59b4ce6aa3ba635f * build(python): make `NOX_SESSION` optional I added this accidentally in #889. `NOX_SESSION` should be passed down if it is set but not marked required. Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Tue Jan 19 09:38:04 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: ba960d730416fe05c50547e975ce79fcee52c671 Source-Link: https://github.com/googleapis/synthtool/commit/ba960d730416fe05c50547e975ce79fcee52c671 * chore: Add header checker config to python library synth Now that we have it working in [python-docs-samples](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/.github/header-checker-lint.yml) we should consider adding it to the 🐍 libraries :) Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Mon Jan 25 13:24:08 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 573f7655311b553a937f9123bee17bf78497db95 Source-Link: https://github.com/googleapis/synthtool/commit/573f7655311b553a937f9123bee17bf78497db95 * chore: add noxfile parameters for extra dependencies Also, add tests for some noxfile parameters for assurance that the template generates valid Python. Co-authored-by: Jeffrey Rennie Source-Author: Tim Swast Source-Date: Tue Jan 26 12:26:57 2021 -0600 Source-Repo: googleapis/synthtool Source-Sha: 778d8beae28d6d87eb01fdc839a4b4d966ed2ebe Source-Link: https://github.com/googleapis/synthtool/commit/778d8beae28d6d87eb01fdc839a4b4d966ed2ebe --- .github/header-checker-lint.yml | 15 +++++++++++++++ .trampolinerc | 1 - logging-v2-py.tar.gz | 0 noxfile.py | 1 + samples/snippets/noxfile.py | 2 +- synth.metadata | 8 ++++---- 6 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 .github/header-checker-lint.yml delete mode 100644 logging-v2-py.tar.gz diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 000000000..fc281c05b --- /dev/null +++ b/.github/header-checker-lint.yml @@ -0,0 +1,15 @@ +{"allowedCopyrightHolders": ["Google LLC"], + "allowedLicenses": ["Apache-2.0", "MIT", "BSD-3"], + "ignoreFiles": ["**/requirements.txt", "**/requirements-test.txt"], + "sourceFileExtensions": [ + "ts", + "js", + "java", + "sh", + "Dockerfile", + "yaml", + "py", + "html", + "txt" + ] +} \ No newline at end of file diff --git a/.trampolinerc b/.trampolinerc index c7d663ae9..383b6ec89 100644 --- a/.trampolinerc +++ b/.trampolinerc @@ -18,7 +18,6 @@ required_envvars+=( "STAGING_BUCKET" "V2_STAGING_BUCKET" - "NOX_SESSION" ) # Add env vars which are passed down into the container here. diff --git a/logging-v2-py.tar.gz b/logging-v2-py.tar.gz deleted file mode 100644 index e69de29bb..000000000 diff --git a/noxfile.py b/noxfile.py index e68736640..19eef5d15 100644 --- a/noxfile.py +++ b/noxfile.py @@ -84,6 +84,7 @@ def default(session): session.install("asyncmock", "pytest-asyncio") session.install("mock", "pytest", "pytest-cov", "flask", "webob", "django") + session.install("-e", ".") # Run py.test against the unit tests. diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index bca0522ec..97bf7da80 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -85,7 +85,7 @@ def get_pytest_env_vars() -> Dict[str, str]: # DO NOT EDIT - automatically generated. # All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8"] +ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9"] # Any default versions that should be ignored. IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] diff --git a/synth.metadata b/synth.metadata index 28c8b61cc..407d059c2 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "c89dea4899a2fd7175c2849f158fb921fc017a15" + "sha": "3d765ae66979ae066bcea07fe9ca01008ec4e4d7" } }, { @@ -19,14 +19,14 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "16ec872dd898d7de6e1822badfac32484b5d9031" + "sha": "778d8beae28d6d87eb01fdc839a4b4d966ed2ebe" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "16ec872dd898d7de6e1822badfac32484b5d9031" + "sha": "778d8beae28d6d87eb01fdc839a4b4d966ed2ebe" } } ], @@ -49,6 +49,7 @@ ".github/ISSUE_TEMPLATE/feature_request.md", ".github/ISSUE_TEMPLATE/support_request.md", ".github/PULL_REQUEST_TEMPLATE.md", + ".github/header-checker-lint.yml", ".github/release-please.yml", ".github/snippet-bot.yml", ".gitignore", @@ -131,7 +132,6 @@ "google/cloud/logging_v2/types/logging.py", "google/cloud/logging_v2/types/logging_config.py", "google/cloud/logging_v2/types/logging_metrics.py", - "logging-v2-py.tar.gz", "mypy.ini", "noxfile.py", "renovate.json", From ae257aa4dc970138b8725114ce8bce5984347d39 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 9 Feb 2021 12:07:46 -0800 Subject: [PATCH 06/35] test: added submodule and kokoro configs for environment tests (#175) --- .flake8 | 3 + .gitmodules | 3 + .../appengine_flex_container/common.cfg | 34 ++++++++++ .../appengine_flex_container/continuous.cfg | 1 + .../appengine_flex_container/presubmit.cfg | 1 + .../appengine_flex_python/common.cfg | 34 ++++++++++ .../appengine_flex_python/continuous.cfg | 1 + .../appengine_flex_python/presubmit.cfg | 1 + .../environment/appengine_standard/common.cfg | 34 ++++++++++ .../appengine_standard/continuous.cfg | 1 + .../appengine_standard/presubmit.cfg | 1 + .kokoro/environment/cloudrun/common.cfg | 34 ++++++++++ .kokoro/environment/cloudrun/continuous.cfg | 1 + .kokoro/environment/cloudrun/presubmit.cfg | 1 + .kokoro/environment/compute/common.cfg | 34 ++++++++++ .kokoro/environment/compute/continuous.cfg | 1 + .kokoro/environment/compute/presubmit.cfg | 1 + .kokoro/environment/functions/common.cfg | 34 ++++++++++ .kokoro/environment/functions/continuous.cfg | 1 + .kokoro/environment/functions/presubmit.cfg | 1 + .kokoro/environment/kubernetes/common.cfg | 34 ++++++++++ .kokoro/environment/kubernetes/continuous.cfg | 1 + .kokoro/environment/kubernetes/presubmit.cfg | 1 + .kokoro/environment_tests.sh | 64 +++++++++++++++++++ .trampolinerc | 6 +- synth.py | 21 +++++- tests/environment | 1 + 27 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 .gitmodules create mode 100644 .kokoro/environment/appengine_flex_container/common.cfg create mode 100644 .kokoro/environment/appengine_flex_container/continuous.cfg create mode 100644 .kokoro/environment/appengine_flex_container/presubmit.cfg create mode 100644 .kokoro/environment/appengine_flex_python/common.cfg create mode 100644 .kokoro/environment/appengine_flex_python/continuous.cfg create mode 100644 .kokoro/environment/appengine_flex_python/presubmit.cfg create mode 100644 .kokoro/environment/appengine_standard/common.cfg create mode 100644 .kokoro/environment/appengine_standard/continuous.cfg create mode 100644 .kokoro/environment/appengine_standard/presubmit.cfg create mode 100644 .kokoro/environment/cloudrun/common.cfg create mode 100644 .kokoro/environment/cloudrun/continuous.cfg create mode 100644 .kokoro/environment/cloudrun/presubmit.cfg create mode 100644 .kokoro/environment/compute/common.cfg create mode 100644 .kokoro/environment/compute/continuous.cfg create mode 100644 .kokoro/environment/compute/presubmit.cfg create mode 100644 .kokoro/environment/functions/common.cfg create mode 100644 .kokoro/environment/functions/continuous.cfg create mode 100644 .kokoro/environment/functions/presubmit.cfg create mode 100644 .kokoro/environment/kubernetes/common.cfg create mode 100644 .kokoro/environment/kubernetes/continuous.cfg create mode 100644 .kokoro/environment/kubernetes/presubmit.cfg create mode 100755 .kokoro/environment_tests.sh create mode 160000 tests/environment diff --git a/.flake8 b/.flake8 index 29227d4cf..1a7e4989e 100644 --- a/.flake8 +++ b/.flake8 @@ -18,6 +18,9 @@ [flake8] ignore = E203, E266, E501, W503 exclude = + # Exclude environment test code. + tests/environment/** + # Exclude generated code. **/proto/** **/gapic/** diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..19484c9e8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/environment"] + path = tests/environment + url = https://github.com/googleapis/env-tests-logging.git diff --git a/.kokoro/environment/appengine_flex_container/common.cfg b/.kokoro/environment/appengine_flex_container/common.cfg new file mode 100644 index 000000000..6271ac646 --- /dev/null +++ b/.kokoro/environment/appengine_flex_container/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "appengine_flex_container" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/appengine_flex_container/continuous.cfg b/.kokoro/environment/appengine_flex_container/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/appengine_flex_container/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/appengine_flex_container/presubmit.cfg b/.kokoro/environment/appengine_flex_container/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/appengine_flex_container/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment/appengine_flex_python/common.cfg b/.kokoro/environment/appengine_flex_python/common.cfg new file mode 100644 index 000000000..2c64e607e --- /dev/null +++ b/.kokoro/environment/appengine_flex_python/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "appengine_flex_python" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/appengine_flex_python/continuous.cfg b/.kokoro/environment/appengine_flex_python/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/appengine_flex_python/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/appengine_flex_python/presubmit.cfg b/.kokoro/environment/appengine_flex_python/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/appengine_flex_python/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment/appengine_standard/common.cfg b/.kokoro/environment/appengine_standard/common.cfg new file mode 100644 index 000000000..7cd4797e0 --- /dev/null +++ b/.kokoro/environment/appengine_standard/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "appengine_standard" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/appengine_standard/continuous.cfg b/.kokoro/environment/appengine_standard/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/appengine_standard/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/appengine_standard/presubmit.cfg b/.kokoro/environment/appengine_standard/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/appengine_standard/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment/cloudrun/common.cfg b/.kokoro/environment/cloudrun/common.cfg new file mode 100644 index 000000000..a54b820a8 --- /dev/null +++ b/.kokoro/environment/cloudrun/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "cloudrun" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/cloudrun/continuous.cfg b/.kokoro/environment/cloudrun/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/cloudrun/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/cloudrun/presubmit.cfg b/.kokoro/environment/cloudrun/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/cloudrun/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment/compute/common.cfg b/.kokoro/environment/compute/common.cfg new file mode 100644 index 000000000..bebc25341 --- /dev/null +++ b/.kokoro/environment/compute/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "compute" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/compute/continuous.cfg b/.kokoro/environment/compute/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/compute/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/compute/presubmit.cfg b/.kokoro/environment/compute/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/compute/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment/functions/common.cfg b/.kokoro/environment/functions/common.cfg new file mode 100644 index 000000000..8ae854c46 --- /dev/null +++ b/.kokoro/environment/functions/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "functions" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/functions/continuous.cfg b/.kokoro/environment/functions/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/functions/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/functions/presubmit.cfg b/.kokoro/environment/functions/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/functions/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment/kubernetes/common.cfg b/.kokoro/environment/kubernetes/common.cfg new file mode 100644 index 000000000..3c9aadb67 --- /dev/null +++ b/.kokoro/environment/kubernetes/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "kubernetes" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/kubernetes/continuous.cfg b/.kokoro/environment/kubernetes/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/kubernetes/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/kubernetes/presubmit.cfg b/.kokoro/environment/kubernetes/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/kubernetes/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/environment_tests.sh b/.kokoro/environment_tests.sh new file mode 100755 index 000000000..528cbc478 --- /dev/null +++ b/.kokoro/environment_tests.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Copyright 2021 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 -eox pipefail + +if [[ -z "${ENVIRONMENT:-}" ]]; then + echo "ENVIRONMENT not set. Exiting" + exit 1 +fi + +cd python-logging/tests/environment + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Setup service account credentials. +export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json +gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS + +# Setup project id. +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") +gcloud config set project $PROJECT_ID + +# authenticate docker +gcloud auth configure-docker -q + +# Remove old nox +python3.6 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3.6 -m pip install --upgrade --quiet nox +python3.6 -m nox --version + +# create a unique id for this run +UUID=$(python -c 'import uuid; print(uuid.uuid1())' | head -c 7) +export ENVCTL_ID=ci-$UUID +echo $ENVCTL_ID + +# Run the specified environment test +set +e +python3.6 -m nox --session "tests(language='python', platform='$ENVIRONMENT')" +TEST_STATUS_CODE=$? + +# destroy resources +echo "cleaning up..." +/workspace/python-logging/tests/environment/envctl/envctl python $ENVIRONMENT destroy + +# exit with proper status code +exit $TEST_STATUS_CODE diff --git a/.trampolinerc b/.trampolinerc index 383b6ec89..2c845a3de 100644 --- a/.trampolinerc +++ b/.trampolinerc @@ -15,13 +15,11 @@ # Template for .trampolinerc # Add required env vars here. -required_envvars+=( - "STAGING_BUCKET" - "V2_STAGING_BUCKET" -) +required_envvars+=() # Add env vars which are passed down into the container here. pass_down_envvars+=( + "ENVIRONMENT" "STAGING_BUCKET" "V2_STAGING_BUCKET" "NOX_SESSION" diff --git a/synth.py b/synth.py index 5be817361..4ee4bd1df 100644 --- a/synth.py +++ b/synth.py @@ -68,6 +68,25 @@ ) s.move(templated_files, excludes=[".coveragerc", "docs/multiprocessing.rst"]) +# adjust .trampolinerc for environment tests +s.replace( + ".trampolinerc", + "required_envvars[^\)]*\)", + "required_envvars+=()" +) +s.replace( + ".trampolinerc", + "pass_down_envvars\+\=\(", + 'pass_down_envvars+=(\n "ENVIRONMENT"' +) + +# don't lint environment tests +s.replace( + ".flake8", + "exclude =", + 'exclude =\n # Exclude environment test code.\n tests/environment/**\n' +) + # -------------------------------------------------------------------------- # Samples templates # -------------------------------------------------------------------------- @@ -80,4 +99,4 @@ python.py_samples() -s.shell.run(["nox", "-s", "blacken"], hide_output=False) \ No newline at end of file +s.shell.run(["nox", "-s", "blacken"], hide_output=False) diff --git a/tests/environment b/tests/environment new file mode 160000 index 000000000..aec50a912 --- /dev/null +++ b/tests/environment @@ -0,0 +1 @@ +Subproject commit aec50a9122b0ff3a41c20925d171d51d948f4b80 From 69963b57054169e92ec05a1ec034c429c971f821 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 9 Feb 2021 23:25:41 +0100 Subject: [PATCH 07/35] chore(deps): update dependency google-cloud-storage to v1.35.1 (#172) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 94edb42b3..ce47db3a9 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 -google-cloud-storage==1.35.0 +google-cloud-storage==1.35.1 google-cloud-pubsub==2.2.0 google-cloud-bigquery==2.7.0 From 53f568e116f84cad5a95fa0e9d1c1589df3de441 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 9 Feb 2021 15:55:18 -0800 Subject: [PATCH 08/35] tests: update environment test config (#178) --- .kokoro/environment/appengine_flex_container/common.cfg | 2 +- .kokoro/environment/appengine_flex_python/common.cfg | 2 +- .kokoro/environment/appengine_standard/common.cfg | 2 +- .kokoro/environment/cloudrun/common.cfg | 2 +- .kokoro/environment/compute/common.cfg | 2 +- .kokoro/environment/functions/common.cfg | 2 +- .kokoro/environment/kubernetes/common.cfg | 2 +- .kokoro/environment_tests.sh | 9 ++++++++- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.kokoro/environment/appengine_flex_container/common.cfg b/.kokoro/environment/appengine_flex_container/common.cfg index 6271ac646..1555bf28f 100644 --- a/.kokoro/environment/appengine_flex_container/common.cfg +++ b/.kokoro/environment/appengine_flex_container/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment/appengine_flex_python/common.cfg b/.kokoro/environment/appengine_flex_python/common.cfg index 2c64e607e..9d3506cb9 100644 --- a/.kokoro/environment/appengine_flex_python/common.cfg +++ b/.kokoro/environment/appengine_flex_python/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment/appengine_standard/common.cfg b/.kokoro/environment/appengine_standard/common.cfg index 7cd4797e0..07242418a 100644 --- a/.kokoro/environment/appengine_standard/common.cfg +++ b/.kokoro/environment/appengine_standard/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment/cloudrun/common.cfg b/.kokoro/environment/cloudrun/common.cfg index a54b820a8..a9a26f468 100644 --- a/.kokoro/environment/cloudrun/common.cfg +++ b/.kokoro/environment/cloudrun/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment/compute/common.cfg b/.kokoro/environment/compute/common.cfg index bebc25341..ac601ef09 100644 --- a/.kokoro/environment/compute/common.cfg +++ b/.kokoro/environment/compute/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment/functions/common.cfg b/.kokoro/environment/functions/common.cfg index 8ae854c46..96b0940fe 100644 --- a/.kokoro/environment/functions/common.cfg +++ b/.kokoro/environment/functions/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment/kubernetes/common.cfg b/.kokoro/environment/kubernetes/common.cfg index 3c9aadb67..a9fcc33e3 100644 --- a/.kokoro/environment/kubernetes/common.cfg +++ b/.kokoro/environment/kubernetes/common.cfg @@ -30,5 +30,5 @@ env_vars: { } env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "python-logging/.kokoro/environment_tests.sh" + value: "github/python-logging/.kokoro/environment_tests.sh" } diff --git a/.kokoro/environment_tests.sh b/.kokoro/environment_tests.sh index 528cbc478..6c7b3677d 100755 --- a/.kokoro/environment_tests.sh +++ b/.kokoro/environment_tests.sh @@ -20,7 +20,14 @@ if [[ -z "${ENVIRONMENT:-}" ]]; then exit 1 fi -cd python-logging/tests/environment +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/python-logging" +fi + +# make sure submodule is up to date +git submodule update --init --recursive + +cd "${PROJECT_ROOT}/tests/environment" # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 From f5b34d180340984383b5a71a42d3ad8f318f4cec Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 9 Feb 2021 16:19:14 -0800 Subject: [PATCH 09/35] tests: clean up properly after completion (#179) --- .kokoro/environment_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kokoro/environment_tests.sh b/.kokoro/environment_tests.sh index 6c7b3677d..b7b96ce9d 100755 --- a/.kokoro/environment_tests.sh +++ b/.kokoro/environment_tests.sh @@ -65,7 +65,7 @@ TEST_STATUS_CODE=$? # destroy resources echo "cleaning up..." -/workspace/python-logging/tests/environment/envctl/envctl python $ENVIRONMENT destroy +${PROJECT_ROOT}/tests/environment/envctl/envctl python $ENVIRONMENT destroy # exit with proper status code exit $TEST_STATUS_CODE From bf8e2b89b6c300298261040512443b8ce4cd1a65 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 10 Feb 2021 02:42:24 +0100 Subject: [PATCH 10/35] chore(deps): update dependency google-cloud-pubsub to v2.3.0 (#176) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index ce47db3a9..45b03d5fc 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 google-cloud-storage==1.35.1 -google-cloud-pubsub==2.2.0 +google-cloud-pubsub==2.3.0 google-cloud-bigquery==2.7.0 From c34a6211e1fa064e2a725007bf974a4bc7e0a153 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 11 Feb 2021 09:37:29 -0800 Subject: [PATCH 11/35] tests: fix environment test issues (#181) --- .kokoro/environment_tests.sh | 12 ++++++++++++ tests/environment | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.kokoro/environment_tests.sh b/.kokoro/environment_tests.sh index b7b96ce9d..f8f138ea1 100755 --- a/.kokoro/environment_tests.sh +++ b/.kokoro/environment_tests.sh @@ -43,6 +43,9 @@ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") gcloud config set project $PROJECT_ID +# set a default zone. +gcloud config set compute/zone us-central1-b + # authenticate docker gcloud auth configure-docker -q @@ -53,6 +56,15 @@ python3.6 -m pip uninstall --yes --quiet nox-automation python3.6 -m pip install --upgrade --quiet nox python3.6 -m nox --version +# Install kubectl +if [[ "${ENVIRONMENT}" == "kubernetes" ]]; then + curl -LO https://dl.k8s.io/release/v1.20.0/bin/linux/amd64/kubectl + chmod +x kubectl + mkdir -p ~/.local/bin + mv ./kubectl ~/.local/bin + export PATH=$PATH:~/.local/bin +fi + # create a unique id for this run UUID=$(python -c 'import uuid; print(uuid.uuid1())' | head -c 7) export ENVCTL_ID=ci-$UUID diff --git a/tests/environment b/tests/environment index aec50a912..e4cab8acd 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit aec50a9122b0ff3a41c20925d171d51d948f4b80 +Subproject commit e4cab8acdbf0054a9e4b5b3b3a2bbc68f108d982 From 7c8d1133cd0f14872e7a55f1e967b94be14a7837 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 23 Feb 2021 20:23:51 -0800 Subject: [PATCH 12/35] tests: fix failing kubernetes test (#194) --- tests/environment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/environment b/tests/environment index e4cab8acd..46658b49b 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit e4cab8acdbf0054a9e4b5b3b3a2bbc68f108d982 +Subproject commit 46658b49b65d40a1311704b3fe898f8737bb4137 From a0d6943aa8607c7d03dc5fbe53e4431921e85b8c Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 25 Feb 2021 15:44:06 -0800 Subject: [PATCH 13/35] tests: retry flaky environment tests (#196) --- tests/environment | 2 +- tests/system/test_system.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/environment b/tests/environment index 46658b49b..f35514893 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit 46658b49b65d40a1311704b3fe898f8737bb4137 +Subproject commit f35514893542dfa29f65214eea96b490f04f3d72 diff --git a/tests/system/test_system.py b/tests/system/test_system.py index 9cd0ac253..e6f5aa7cf 100644 --- a/tests/system/test_system.py +++ b/tests/system/test_system.py @@ -72,10 +72,10 @@ def _list_entries(logger): :rtype: list :returns: List of all entries consumed. """ - inner = RetryResult(_has_entries, delay=1, backoff=2, max_tries=6)(_consume_entries) + inner = RetryResult(_has_entries, delay=2, backoff=2, max_tries=6)(_consume_entries) outer = RetryErrors( (ServiceUnavailable, ResourceExhausted, InternalServerError), - delay=1, + delay=2, backoff=2, max_tries=6, )(inner) From d9bd785c91906161bb32437fcfd9fc1aa5eec028 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 25 Feb 2021 16:12:40 -0800 Subject: [PATCH 14/35] chore: Re-generated to pick up changes from synthtool. (#177) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * build: migrate to flakybot Source-Author: Justin Beckwith Source-Date: Thu Jan 28 22:22:38 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: d1bb9173100f62c0cfc8f3138b62241e7f47ca6a Source-Link: https://github.com/googleapis/synthtool/commit/d1bb9173100f62c0cfc8f3138b62241e7f47ca6a * chore(python): include py.typed files in release A py.typed file must be included in the released package for it to be considered typed by type checkers. https://www.python.org/dev/peps/pep-0561/#packaging-type-information. See https://github.com/googleapis/python-secret-manager/issues/79 Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Fri Feb 5 17:32:06 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: 33366574ffb9e11737b3547eb6f020ecae0536e8 Source-Link: https://github.com/googleapis/synthtool/commit/33366574ffb9e11737b3547eb6f020ecae0536e8 * docs: update python contributing guide Adds details about blacken, updates version for system tests, and shows how to pass through pytest arguments. Source-Author: Chris Cotter Source-Date: Mon Feb 8 17:13:36 2021 -0500 Source-Repo: googleapis/synthtool Source-Sha: 4679e7e415221f03ff2a71e3ffad75b9ec41d87e Source-Link: https://github.com/googleapis/synthtool/commit/4679e7e415221f03ff2a71e3ffad75b9ec41d87e --- CONTRIBUTING.rst | 22 ++++++++++++++++++---- MANIFEST.in | 4 ++-- synth.metadata | 6 +++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index cd48664d8..f6ddd7268 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -70,9 +70,14 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: $ nox -s unit-2.7 - $ nox -s unit-3.7 + $ nox -s unit-3.8 $ ... +- Args to pytest can be passed through the nox command separated by a `--`. For + example, to run a single test:: + + $ nox -s unit-3.8 -- -k + .. note:: The unit tests and system tests are described in the @@ -93,8 +98,12 @@ On Debian/Ubuntu:: ************ Coding Style ************ +- We use the automatic code formatter ``black``. You can run it using + the nox session ``blacken``. This will eliminate many lint errors. Run via:: + + $ nox -s blacken -- PEP8 compliance, with exceptions defined in the linter configuration. +- PEP8 compliance is required, with exceptions defined in the linter configuration. If you have ``nox`` installed, you can test that you have not introduced any non-compliant code via:: @@ -133,13 +142,18 @@ Running System Tests - To run system tests, you can execute:: - $ nox -s system-3.7 + # Run all system tests + $ nox -s system-3.8 $ nox -s system-2.7 + # Run a single system test + $ nox -s system-3.8 -- -k + + .. note:: System tests are only configured to run under Python 2.7 and - Python 3.7. For expediency, we do not run them in older versions + Python 3.8. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local diff --git a/MANIFEST.in b/MANIFEST.in index e9e29d120..e783f4c62 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,10 +16,10 @@ # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE -recursive-include google *.json *.proto +recursive-include google *.json *.proto py.typed recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ # Exclude scripts for samples readmegen -prune scripts/readme-gen \ No newline at end of file +prune scripts/readme-gen diff --git a/synth.metadata b/synth.metadata index 407d059c2..9e8a2ffc4 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "3d765ae66979ae066bcea07fe9ca01008ec4e4d7" + "sha": "11976b9f3834e4ab976940fe88c2f5784d1b1795" } }, { @@ -19,14 +19,14 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "778d8beae28d6d87eb01fdc839a4b4d966ed2ebe" + "sha": "4679e7e415221f03ff2a71e3ffad75b9ec41d87e" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "778d8beae28d6d87eb01fdc839a4b4d966ed2ebe" + "sha": "4679e7e415221f03ff2a71e3ffad75b9ec41d87e" } } ], From 95984dc501ba881ea39842ebd978100f065b2d28 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 26 Feb 2021 01:12:59 +0100 Subject: [PATCH 15/35] chore(deps): update dependency google-cloud-pubsub to v2.4.0 (#191) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 45b03d5fc..83dc20e1a 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 google-cloud-storage==1.35.1 -google-cloud-pubsub==2.3.0 +google-cloud-pubsub==2.4.0 google-cloud-bigquery==2.7.0 From db9bceecadfcd70a1268c70557e2affd02c73d5a Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 26 Feb 2021 01:47:49 +0100 Subject: [PATCH 16/35] chore(deps): update dependency google-cloud-storage to v1.36.1 (#182) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 83dc20e1a..d85a19852 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 -google-cloud-storage==1.35.1 +google-cloud-storage==1.36.1 google-cloud-pubsub==2.4.0 google-cloud-bigquery==2.7.0 From de9c4c8bca265f6ecf98ecb9aa145891586232c8 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 25 Feb 2021 16:48:44 -0800 Subject: [PATCH 17/35] chore: Re-generated to pick up changes from googleapis. (#171) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * chore: update Go generator, rules_go, and protobuf PiperOrigin-RevId: 352816749 Source-Author: Google APIs Source-Date: Wed Jan 20 10:06:23 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: ceaaf31b3d13badab7cf9d3b570f5639db5593d9 Source-Link: https://github.com/googleapis/googleapis/commit/ceaaf31b3d13badab7cf9d3b570f5639db5593d9 * chore: upgrade gapic-generator-python to 0.40.5 PiperOrigin-RevId: 354996675 Source-Author: Google APIs Source-Date: Mon Feb 1 12:11:49 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 20712b8fe95001b312f62c6c5f33e3e3ec92cfaf Source-Link: https://github.com/googleapis/googleapis/commit/20712b8fe95001b312f62c6c5f33e3e3ec92cfaf Co-authored-by: Daniel Sanche --- .kokoro/test-samples.sh | 8 +- .kokoro/trampoline_v2.sh | 2 +- .../services/config_service_v2/client.py | 18 +- .../config_service_v2/transports/grpc.py | 23 ++- .../transports/grpc_asyncio.py | 23 ++- .../services/logging_service_v2/client.py | 18 +- .../logging_service_v2/transports/grpc.py | 23 ++- .../transports/grpc_asyncio.py | 23 ++- .../services/metrics_service_v2/client.py | 18 +- .../metrics_service_v2/transports/grpc.py | 23 ++- .../transports/grpc_asyncio.py | 23 ++- synth.metadata | 6 +- .../logging_v2/test_config_service_v2.py | 187 ++++++++++------- .../logging_v2/test_logging_service_v2.py | 188 +++++++++++------- .../logging_v2/test_metrics_service_v2.py | 188 +++++++++++------- 15 files changed, 469 insertions(+), 302 deletions(-) diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index e75891832..ba97b53d5 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -87,11 +87,11 @@ for file in samples/**/requirements.txt; do python3.6 -m nox -s "$RUN_TESTS_SESSION" EXIT=$? - # If this is a periodic build, send the test log to the FlakyBot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. + # If this is a periodic build, send the test log to the Build Cop Bot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot - $KOKORO_GFILE_DIR/linux_amd64/flakybot + chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop + $KOKORO_GFILE_DIR/linux_amd64/buildcop fi if [[ $EXIT -ne 0 ]]; then diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh index 4af6cdc26..719bcd5ba 100755 --- a/.kokoro/trampoline_v2.sh +++ b/.kokoro/trampoline_v2.sh @@ -159,7 +159,7 @@ if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then "KOKORO_GITHUB_COMMIT" "KOKORO_GITHUB_PULL_REQUEST_NUMBER" "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For FlakyBot + # For Build Cop Bot "KOKORO_GITHUB_COMMIT_URL" "KOKORO_GITHUB_PULL_REQUEST_URL" ) diff --git a/google/cloud/logging_v2/services/config_service_v2/client.py b/google/cloud/logging_v2/services/config_service_v2/client.py index 7d6492ba0..37a28d7a2 100644 --- a/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/google/cloud/logging_v2/services/config_service_v2/client.py @@ -338,21 +338,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -395,7 +391,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) diff --git a/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py b/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py index a64405fba..b749eb5d3 100644 --- a/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py +++ b/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -104,6 +109,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -113,11 +123,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -161,12 +166,18 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, options=[ diff --git a/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py b/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py index aa094ea0e..f0f1ca070 100644 --- a/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py +++ b/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,6 +134,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -149,6 +154,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -158,11 +168,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -206,12 +211,18 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, options=[ diff --git a/google/cloud/logging_v2/services/logging_service_v2/client.py b/google/cloud/logging_v2/services/logging_service_v2/client.py index a340eb205..0e046c957 100644 --- a/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -292,21 +292,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -349,7 +345,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) diff --git a/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py b/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py index f8007bb0d..e90b2a5fe 100644 --- a/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py +++ b/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -104,6 +109,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -113,11 +123,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -161,12 +166,18 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, options=[ diff --git a/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py b/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py index 6adea9ca5..40037da25 100644 --- a/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py +++ b/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,6 +134,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -149,6 +154,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -158,11 +168,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -206,12 +211,18 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, options=[ diff --git a/google/cloud/logging_v2/services/metrics_service_v2/client.py b/google/cloud/logging_v2/services/metrics_service_v2/client.py index cc6e491fc..850236a57 100644 --- a/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -286,21 +286,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -343,7 +339,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) diff --git a/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py b/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py index 1cb9262ab..e55bf32e5 100644 --- a/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py +++ b/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -104,6 +109,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -113,11 +123,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -161,12 +166,18 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, options=[ diff --git a/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py b/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py index ddbd16da6..ec93d3850 100644 --- a/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py +++ b/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,6 +134,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -149,6 +154,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -158,11 +168,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -206,12 +211,18 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, options=[ diff --git a/synth.metadata b/synth.metadata index 9e8a2ffc4..9ca738bf8 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,15 +4,15 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "11976b9f3834e4ab976940fe88c2f5784d1b1795" + "sha": "18a0bb7fd8d158baef532b1c715524e5f6303311" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "520682435235d9c503983a360a2090025aa47cd1", - "internalRef": "350246057" + "sha": "20712b8fe95001b312f62c6c5f33e3e3ec92cfaf", + "internalRef": "354996675" } }, { diff --git a/tests/unit/gapic/logging_v2/test_config_service_v2.py b/tests/unit/gapic/logging_v2/test_config_service_v2.py index a2685b497..c819769ee 100644 --- a/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -175,7 +175,7 @@ def test_config_service_v2_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -191,7 +191,7 @@ def test_config_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -207,7 +207,7 @@ def test_config_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -235,7 +235,7 @@ def test_config_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -296,29 +296,25 @@ def test_config_service_v2_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -327,66 +323,53 @@ def test_config_service_v2_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -412,7 +395,7 @@ def test_config_service_v2_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -442,7 +425,7 @@ def test_config_service_v2_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -461,7 +444,7 @@ def test_config_service_v2_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -5306,6 +5289,56 @@ def test_config_service_v2_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.ConfigServiceV2GrpcTransport, + transports.ConfigServiceV2GrpcAsyncIOTransport, + ], +) +def test_config_service_v2_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/logging.admin", + "https://www.googleapis.com/auth/logging.read", + ), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_config_service_v2_host_no_port(): client = ConfigServiceV2Client( credentials=credentials.AnonymousCredentials(), @@ -5350,6 +5383,8 @@ def test_config_service_v2_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -5407,6 +5442,8 @@ def test_config_service_v2_transport_channel_mtls_with_client_cert_source( assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ diff --git a/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 110a383c0..c811acac5 100644 --- a/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -182,7 +182,7 @@ def test_logging_service_v2_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -198,7 +198,7 @@ def test_logging_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -214,7 +214,7 @@ def test_logging_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -242,7 +242,7 @@ def test_logging_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -303,29 +303,25 @@ def test_logging_service_v2_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -334,66 +330,53 @@ def test_logging_service_v2_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -419,7 +402,7 @@ def test_logging_service_v2_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -449,7 +432,7 @@ def test_logging_service_v2_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -468,7 +451,7 @@ def test_logging_service_v2_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1971,6 +1954,57 @@ def test_logging_service_v2_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.LoggingServiceV2GrpcTransport, + transports.LoggingServiceV2GrpcAsyncIOTransport, + ], +) +def test_logging_service_v2_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/logging.admin", + "https://www.googleapis.com/auth/logging.read", + "https://www.googleapis.com/auth/logging.write", + ), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_logging_service_v2_host_no_port(): client = LoggingServiceV2Client( credentials=credentials.AnonymousCredentials(), @@ -2015,6 +2049,8 @@ def test_logging_service_v2_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -2073,6 +2109,8 @@ def test_logging_service_v2_transport_channel_mtls_with_client_cert_source( assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ diff --git a/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 8ae5fdc54..7230b0d87 100644 --- a/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -181,7 +181,7 @@ def test_metrics_service_v2_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -197,7 +197,7 @@ def test_metrics_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -213,7 +213,7 @@ def test_metrics_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -241,7 +241,7 @@ def test_metrics_service_v2_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -302,29 +302,25 @@ def test_metrics_service_v2_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -333,66 +329,53 @@ def test_metrics_service_v2_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -418,7 +401,7 @@ def test_metrics_service_v2_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -448,7 +431,7 @@ def test_metrics_service_v2_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -467,7 +450,7 @@ def test_metrics_service_v2_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1916,6 +1899,57 @@ def test_metrics_service_v2_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.MetricsServiceV2GrpcTransport, + transports.MetricsServiceV2GrpcAsyncIOTransport, + ], +) +def test_metrics_service_v2_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/logging.admin", + "https://www.googleapis.com/auth/logging.read", + "https://www.googleapis.com/auth/logging.write", + ), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_metrics_service_v2_host_no_port(): client = MetricsServiceV2Client( credentials=credentials.AnonymousCredentials(), @@ -1960,6 +1994,8 @@ def test_metrics_service_v2_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -2018,6 +2054,8 @@ def test_metrics_service_v2_transport_channel_mtls_with_client_cert_source( assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ From 22f836ac82b872e3402bf70e39a3b76c2142380e Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 26 Feb 2021 20:00:36 +0100 Subject: [PATCH 18/35] chore(deps): update dependency google-cloud-bigquery to v2.10.0 (#180) Co-authored-by: Daniel Sanche --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index d85a19852..131f3bbee 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 +google-cloud-bigquery==2.10.0 google-cloud-storage==1.36.1 google-cloud-pubsub==2.4.0 -google-cloud-bigquery==2.7.0 From 4eda6813d19df8a119f1dcd47ff79389310d4a6f Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 5 Mar 2021 14:17:46 -0800 Subject: [PATCH 19/35] feat: detect monitored resources on all GCP environments (#200) --- google/cloud/logging_v2/client.py | 27 +- .../handlers/_monitored_resources.py | 195 ++++++++++++++ .../cloud/logging_v2/handlers/app_engine.py | 14 +- google/cloud/logging_v2/handlers/handlers.py | 9 +- tests/environment | 2 +- .../handlers/test__monitored_resources.py | 249 ++++++++++++++++++ tests/unit/handlers/test_handlers.py | 31 ++- tests/unit/test_client.py | 8 +- 8 files changed, 493 insertions(+), 42 deletions(-) create mode 100644 google/cloud/logging_v2/handlers/_monitored_resources.py create mode 100644 tests/unit/handlers/test__monitored_resources.py diff --git a/google/cloud/logging_v2/client.py b/google/cloud/logging_v2/client.py index ee65d288a..f196f443a 100644 --- a/google/cloud/logging_v2/client.py +++ b/google/cloud/logging_v2/client.py @@ -29,7 +29,6 @@ from google.cloud.client import ClientWithProject from google.cloud.environment_vars import DISABLE_GRPC from google.cloud.logging_v2._helpers import _add_defaults_to_filter -from google.cloud.logging_v2._helpers import retrieve_metadata_server from google.cloud.logging_v2._http import Connection from google.cloud.logging_v2._http import _LoggingAPI as JSONLoggingAPI from google.cloud.logging_v2._http import _MetricsAPI as JSONMetricsAPI @@ -39,6 +38,9 @@ from google.cloud.logging_v2.handlers import ContainerEngineHandler from google.cloud.logging_v2.handlers import setup_logging from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS +from google.cloud.logging_v2.resource import Resource +from google.cloud.logging_v2.handlers._monitored_resources import detect_resource + from google.cloud.logging_v2.logger import Logger from google.cloud.logging_v2.metric import Metric @@ -48,14 +50,8 @@ _DISABLE_GRPC = os.getenv(DISABLE_GRPC, False) _USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC -_APPENGINE_FLEXIBLE_ENV_VM = "GAE_APPENGINE_HOSTNAME" -"""Environment variable set in App Engine when vm:true is set.""" - -_APPENGINE_INSTANCE_ID = "GAE_INSTANCE" -"""Environment variable set in App Engine standard and flexible environment.""" - -_GKE_CLUSTER_NAME = "instance/attributes/cluster-name" -"""Attribute in metadata server when in GKE environment.""" +_GAE_RESOURCE_TYPE = "gae_app" +_GKE_RESOURCE_TYPE = "k8s_container" class Client(ClientWithProject): @@ -348,17 +344,20 @@ def get_default_handler(self, **kw): Returns: logging.Handler: The default log handler based on the environment """ - gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME) + monitored_resource = kw.pop("resource", detect_resource(self.project)) if ( - _APPENGINE_FLEXIBLE_ENV_VM in os.environ - or _APPENGINE_INSTANCE_ID in os.environ + isinstance(monitored_resource, Resource) + and monitored_resource.type == _GAE_RESOURCE_TYPE ): return AppEngineHandler(self, **kw) - elif gke_cluster_name is not None: + elif ( + isinstance(monitored_resource, Resource) + and monitored_resource.type == _GKE_RESOURCE_TYPE + ): return ContainerEngineHandler(**kw) else: - return CloudLoggingHandler(self, **kw) + return CloudLoggingHandler(self, resource=monitored_resource, **kw) def setup_logging( self, *, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw diff --git a/google/cloud/logging_v2/handlers/_monitored_resources.py b/google/cloud/logging_v2/handlers/_monitored_resources.py new file mode 100644 index 000000000..4bc30d4fb --- /dev/null +++ b/google/cloud/logging_v2/handlers/_monitored_resources.py @@ -0,0 +1,195 @@ +# Copyright 2021 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. + +import os + +from google.cloud.logging_v2.resource import Resource +from google.cloud.logging_v2._helpers import retrieve_metadata_server + +_GAE_SERVICE_ENV = "GAE_SERVICE" +_GAE_VERSION_ENV = "GAE_VERSION" +_GAE_INSTANCE_ENV = "GAE_INSTANCE" +_GAE_ENV_VARS = [_GAE_SERVICE_ENV, _GAE_VERSION_ENV, _GAE_INSTANCE_ENV] +"""Environment variables set in App Engine environment.""" + +_CLOUD_RUN_SERVICE_ID = "K_SERVICE" +_CLOUD_RUN_REVISION_ID = "K_REVISION" +_CLOUD_RUN_CONFIGURATION_ID = "K_CONFIGURATION" +_CLOUD_RUN_ENV_VARS = [ + _CLOUD_RUN_SERVICE_ID, + _CLOUD_RUN_REVISION_ID, + _CLOUD_RUN_CONFIGURATION_ID, +] +"""Environment variables set in Cloud Run environment.""" + +_FUNCTION_TARGET = "FUNCTION_TARGET" +_FUNCTION_SIGNATURE = "FUNCTION_SIGNATURE_TYPE" +_FUNCTION_NAME = "FUNCTION_NAME" +_FUNCTION_REGION = "FUNCTION_REGION" +_FUNCTION_ENTRY = "ENTRY_POINT" +_FUNCTION_ENV_VARS = [_FUNCTION_TARGET, _FUNCTION_SIGNATURE, _CLOUD_RUN_SERVICE_ID] +_LEGACY_FUNCTION_ENV_VARS = [_FUNCTION_NAME, _FUNCTION_REGION, _FUNCTION_ENTRY] +"""Environment variables set in Cloud Functions environments.""" + + +_REGION_ID = "instance/region" +_ZONE_ID = "instance/zone" +_GCE_INSTANCE_ID = "instance/id" +"""Attribute in metadata server for compute region and instance.""" + +_GKE_CLUSTER_NAME = "instance/attributes/cluster-name" +"""Attribute in metadata server when in GKE environment.""" + + +def _create_functions_resource(project): + """Create a standardized Cloud Functions resource. + Args: + project (str): The project ID to pass on to the resource + Returns: + google.cloud.logging.Resource + """ + region = retrieve_metadata_server(_REGION_ID) + if _FUNCTION_NAME in os.environ: + function_name = os.environ.get(_FUNCTION_NAME) + elif _CLOUD_RUN_SERVICE_ID in os.environ: + function_name = os.environ.get(_CLOUD_RUN_SERVICE_ID) + else: + function_name = "" + resource = Resource( + type="cloud_function", + labels={ + "project_id": project, + "function_name": function_name, + "region": region if region else "", + }, + ) + return resource + + +def _create_kubernetes_resource(project): + """Create a standardized Kubernetes resource. + Args: + project (str): The project ID to pass on to the resource + Returns: + google.cloud.logging.Resource + """ + zone = retrieve_metadata_server(_ZONE_ID) + cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME) + + resource = Resource( + type="k8s_container", + labels={ + "project_id": project, + "location": zone if zone else "", + "cluster_name": cluster_name if cluster_name else "", + }, + ) + return resource + + +def _create_compute_resource(project): + """Create a standardized Compute Engine resource. + Args: + project (str): The project ID to pass on to the resource + Returns: + google.cloud.logging.Resource + """ + instance = retrieve_metadata_server(_GCE_INSTANCE_ID) + zone = retrieve_metadata_server(_ZONE_ID) + resource = Resource( + type="gce_instance", + labels={ + "project_id": project, + "instance_id": instance if instance else "", + "zone": zone if zone else "", + }, + ) + return resource + + +def _create_cloud_run_resource(project): + """Create a standardized Cloud Run resource. + Args: + project (str): The project ID to pass on to the resource + Returns: + google.cloud.logging.Resource + """ + region = retrieve_metadata_server(_REGION_ID) + resource = Resource( + type="cloud_run_revision", + labels={ + "project_id": project, + "service_name": os.environ.get(_CLOUD_RUN_SERVICE_ID, ""), + "revision_name": os.environ.get(_CLOUD_RUN_REVISION_ID, ""), + "location": region if region else "", + "configuration_name": os.environ.get(_CLOUD_RUN_CONFIGURATION_ID, ""), + }, + ) + return resource + + +def _create_app_engine_resource(project): + """Create a standardized App Engine resource. + Args: + project (str): The project ID to pass on to the resource + Returns: + google.cloud.logging.Resource + """ + zone = retrieve_metadata_server(_ZONE_ID) + resource = Resource( + type="gae_app", + labels={ + "project_id": project, + "module_id": os.environ.get(_GAE_SERVICE_ENV, ""), + "version_id": os.environ.get(_GAE_VERSION_ENV, ""), + "zone": zone if zone else "", + }, + ) + return resource + + +def _create_global_resource(project): + return Resource(type="global", labels={"project_id": project}) + + +def detect_resource(project): + """Return the default monitored resource based on the local environment. + Args: + project (str): The project ID to pass on to the resource + Returns: + google.cloud.logging.Resource: The default resource based on the environment + """ + gke_cluster_name = retrieve_metadata_server(_GKE_CLUSTER_NAME) + gce_instance_name = retrieve_metadata_server(_GCE_INSTANCE_ID) + + if all([env in os.environ for env in _GAE_ENV_VARS]): + # App Engine Flex or Standard + return _create_app_engine_resource(project) + elif gke_cluster_name is not None: + # Kubernetes Engine + return _create_kubernetes_resource(project) + elif all([env in os.environ for env in _LEGACY_FUNCTION_ENV_VARS]) or all( + [env in os.environ for env in _FUNCTION_ENV_VARS] + ): + # Cloud Functions + return _create_functions_resource(project) + elif all([env in os.environ for env in _CLOUD_RUN_ENV_VARS]): + # Cloud Run + return _create_cloud_run_resource(project) + elif gce_instance_name is not None: + # Compute Engine + return _create_compute_resource(project) + else: + # use generic global resource + return _create_global_resource(project) diff --git a/google/cloud/logging_v2/handlers/app_engine.py b/google/cloud/logging_v2/handlers/app_engine.py index a5d57c53e..7d16ab07a 100644 --- a/google/cloud/logging_v2/handlers/app_engine.py +++ b/google/cloud/logging_v2/handlers/app_engine.py @@ -22,8 +22,10 @@ import os from google.cloud.logging_v2.handlers._helpers import get_request_data +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_app_engine_resource, +) from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport -from google.cloud.logging_v2.resource import Resource _DEFAULT_GAE_LOGGER_NAME = "app" @@ -75,15 +77,7 @@ def get_gae_resource(self): Returns: google.cloud.logging_v2.resource.Resource: Monitored resource for GAE. """ - gae_resource = Resource( - type="gae_app", - labels={ - "project_id": self.project_id, - "module_id": self.module_id, - "version_id": self.version_id, - }, - ) - return gae_resource + return _create_app_engine_resource(self.project_id) def get_gae_labels(self): """Return the labels for GAE app. diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index fd99f7adc..c2ad6f355 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -17,7 +17,7 @@ import logging from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport -from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE +from google.cloud.logging_v2.handlers._monitored_resources import detect_resource DEFAULT_LOGGER_NAME = "python" @@ -59,7 +59,7 @@ def __init__( *, name=DEFAULT_LOGGER_NAME, transport=BackgroundThreadTransport, - resource=_GLOBAL_RESOURCE, + resource=None, labels=None, stream=None, ): @@ -78,12 +78,15 @@ def __init__( :class:`.BackgroundThreadTransport`. The other option is :class:`.SyncTransport`. resource (~logging_v2.resource.Resource): - Resource for this Handler. Defaults to ``GLOBAL_RESOURCE``. + Resource for this Handler. If not given, will be inferred from the environment. labels (Optional[dict]): Monitored resource of the entry, defaults to the global resource type. stream (Optional[IO]): Stream to be used by the handler. """ super(CloudLoggingHandler, self).__init__(stream) + if not resource: + # infer the correct monitored resource from the local environment + resource = detect_resource(client.project) self.name = name self.client = client self.transport = transport(client, name) diff --git a/tests/environment b/tests/environment index f35514893..0e3311158 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit f35514893542dfa29f65214eea96b490f04f3d72 +Subproject commit 0e331115867ca5a26b1efd9d99c43fbb1cb9363b diff --git a/tests/unit/handlers/test__monitored_resources.py b/tests/unit/handlers/test__monitored_resources.py new file mode 100644 index 000000000..00fade39c --- /dev/null +++ b/tests/unit/handlers/test__monitored_resources.py @@ -0,0 +1,249 @@ +# Copyright 2021 Google LLC All Rights Reserved. +# +# 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. + +import unittest + +import mock +import os + + +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_functions_resource, +) +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_app_engine_resource, +) +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_kubernetes_resource, +) +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_cloud_run_resource, +) +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_compute_resource, +) +from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_global_resource, +) +from google.cloud.logging_v2.handlers._monitored_resources import detect_resource +from google.cloud.logging_v2.handlers import _monitored_resources +from google.cloud.logging_v2.resource import Resource + + +class Test_Create_Resources(unittest.TestCase): + + PROJECT = "test-project" + LOCATION = "test-location" + NAME = "test-name" + CLUSTER = "test-cluster" + VERSION = "1" + CONFIG = "test-config" + + def _mock_metadata(self, endpoint): + if ( + endpoint == _monitored_resources._ZONE_ID + or endpoint == _monitored_resources._REGION_ID + ): + return self.LOCATION + elif ( + endpoint == _monitored_resources._GKE_CLUSTER_NAME + or endpoint == _monitored_resources._GCE_INSTANCE_ID + ): + return self.NAME + else: + return None + + def setUp(self): + os.environ.clear() + + def test_create_legacy_functions_resource(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_metadata, + ) + + os.environ[_monitored_resources._CLOUD_RUN_SERVICE_ID] = self.NAME + with patch: + legacy_func_resource = _create_functions_resource(self.PROJECT) + + self.assertIsInstance(legacy_func_resource, Resource) + self.assertEqual(legacy_func_resource.type, "cloud_function") + self.assertEqual(legacy_func_resource.labels["project_id"], self.PROJECT) + self.assertEqual(legacy_func_resource.labels["function_name"], self.NAME) + self.assertEqual(legacy_func_resource.labels["region"], self.LOCATION) + + def test_create_modern_functions_resource(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_metadata, + ) + os.environ[_monitored_resources._FUNCTION_NAME] = self.NAME + with patch: + func_resource = _create_functions_resource(self.PROJECT) + + self.assertIsInstance(func_resource, Resource) + self.assertEqual(func_resource.type, "cloud_function") + self.assertEqual(func_resource.labels["project_id"], self.PROJECT) + self.assertEqual(func_resource.labels["function_name"], self.NAME) + self.assertEqual(func_resource.labels["region"], self.LOCATION) + + def test_create_kubernetes_resource(self): + + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_metadata, + ) + with patch: + resource = _create_kubernetes_resource(self.PROJECT) + + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "k8s_container") + self.assertEqual(resource.labels["project_id"], self.PROJECT) + self.assertEqual(resource.labels["cluster_name"], self.NAME) + self.assertEqual(resource.labels["location"], self.LOCATION) + + def test_compute_resource(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_metadata, + ) + + with patch: + resource = _create_compute_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "gce_instance") + self.assertEqual(resource.labels["project_id"], self.PROJECT) + self.assertEqual(resource.labels["instance_id"], self.NAME) + self.assertEqual(resource.labels["zone"], self.LOCATION) + + def test_cloud_run_resource(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_metadata, + ) + os.environ[_monitored_resources._CLOUD_RUN_SERVICE_ID] = self.NAME + os.environ[_monitored_resources._CLOUD_RUN_REVISION_ID] = self.VERSION + os.environ[_monitored_resources._CLOUD_RUN_CONFIGURATION_ID] = self.CONFIG + with patch: + resource = _create_cloud_run_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "cloud_run_revision") + self.assertEqual(resource.labels["project_id"], self.PROJECT) + self.assertEqual(resource.labels["service_name"], self.NAME) + self.assertEqual(resource.labels["revision_name"], self.VERSION) + self.assertEqual(resource.labels["configuration_name"], self.CONFIG) + self.assertEqual(resource.labels["location"], self.LOCATION) + + def test_app_engine_resource(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_metadata, + ) + os.environ[_monitored_resources._GAE_SERVICE_ENV] = self.NAME + os.environ[_monitored_resources._GAE_VERSION_ENV] = self.VERSION + with patch: + resource = _create_app_engine_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "gae_app") + self.assertEqual(resource.labels["project_id"], self.PROJECT) + self.assertEqual(resource.labels["module_id"], self.NAME) + self.assertEqual(resource.labels["version_id"], self.VERSION) + self.assertEqual(resource.labels["zone"], self.LOCATION) + + def test_global_resource(self): + resource = _create_global_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "global") + self.assertEqual(resource.labels["project_id"], self.PROJECT) + + +class Test_Resource_Detection(unittest.TestCase): + + PROJECT = "test-project" + + def _mock_k8s_metadata(self, endpoint): + if ( + endpoint == _monitored_resources._GKE_CLUSTER_NAME + or endpoint == _monitored_resources._GCE_INSTANCE_ID + ): + return "TRUE" + else: + return None + + def _mock_gce_metadata(self, endpoint): + if endpoint == _monitored_resources._GCE_INSTANCE_ID: + return "TRUE" + else: + return None + + def setUp(self): + os.environ.clear() + + def test_detect_appengine(self): + for env in _monitored_resources._GAE_ENV_VARS: + os.environ[env] = "TRUE" + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "gae_app") + + def test_detect_kubernetes(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_k8s_metadata, + ) + with patch: + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "k8s_container") + + def test_detect_functions(self): + for env in _monitored_resources._FUNCTION_ENV_VARS: + os.environ[env] = "TRUE" + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "cloud_function") + + def test_detect_legacy_functions(self): + for env in _monitored_resources._LEGACY_FUNCTION_ENV_VARS: + os.environ[env] = "TRUE" + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "cloud_function") + + def test_detect_cloud_run(self): + for env in _monitored_resources._CLOUD_RUN_ENV_VARS: + os.environ[env] = "TRUE" + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "cloud_run_revision") + + def test_detect_compute_engine(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + wraps=self._mock_gce_metadata, + ) + with patch: + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "gce_instance") + + def test_detection_unknown(self): + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + return_value=None, + ) + with patch: + resource = detect_resource(self.PROJECT) + self.assertIsInstance(resource, Resource) + self.assertEqual(resource.type, "global") diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index d84c19635..0e7c63cc4 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -14,6 +14,7 @@ import logging import unittest +import mock class TestCloudLoggingHandler(unittest.TestCase): @@ -31,19 +32,27 @@ def _make_one(self, *args, **kw): def test_ctor_defaults(self): import sys - from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE + from google.cloud.logging_v2.handlers._monitored_resources import ( + _create_global_resource, + ) from google.cloud.logging_v2.handlers.handlers import DEFAULT_LOGGER_NAME - client = _Client(self.PROJECT) - handler = self._make_one(client, transport=_Transport) - self.assertEqual(handler.name, DEFAULT_LOGGER_NAME) - self.assertIs(handler.client, client) - self.assertIsInstance(handler.transport, _Transport) - self.assertIs(handler.transport.client, client) - self.assertEqual(handler.transport.name, DEFAULT_LOGGER_NAME) - self.assertIs(handler.resource, _GLOBAL_RESOURCE) - self.assertIsNone(handler.labels) - self.assertIs(handler.stream, sys.stderr) + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + return_value=None, + ) + with patch: + client = _Client(self.PROJECT) + handler = self._make_one(client, transport=_Transport) + self.assertEqual(handler.name, DEFAULT_LOGGER_NAME) + self.assertIs(handler.client, client) + self.assertIsInstance(handler.transport, _Transport) + self.assertIs(handler.transport.client, client) + self.assertEqual(handler.transport.name, DEFAULT_LOGGER_NAME) + global_resource = _create_global_resource(self.PROJECT) + self.assertEqual(handler.resource, global_resource) + self.assertIsNone(handler.labels) + self.assertIs(handler.stream, sys.stderr) def test_ctor_explicit(self): import io diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 8083e3c56..f33f1cbdc 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -718,7 +718,7 @@ def test_list_metrics_with_paging(self): def test_get_default_handler_app_engine(self): import os from google.cloud._testing import _Monkey - from google.cloud.logging_v2.client import _APPENGINE_FLEXIBLE_ENV_VM + from google.cloud.logging_v2.handlers._monitored_resources import _GAE_ENV_VARS from google.cloud.logging.handlers import AppEngineHandler credentials = _make_credentials() @@ -726,7 +726,9 @@ def test_get_default_handler_app_engine(self): project=self.PROJECT, credentials=credentials, _use_grpc=False ) - with _Monkey(os, environ={_APPENGINE_FLEXIBLE_ENV_VM: "True"}): + gae_env_vars = {var: "TRUE" for var in _GAE_ENV_VARS} + + with _Monkey(os, environ=gae_env_vars): handler = client.get_default_handler() handler.transport.worker.stop() @@ -742,7 +744,7 @@ def test_get_default_handler_container_engine(self): ) patch = mock.patch( - "google.cloud.logging_v2.client.retrieve_metadata_server", + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", return_value="test-gke-cluster", ) From dfce3da6a57b22eed8b3f8048bd1f776bf2027a5 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Fri, 5 Mar 2021 14:39:52 -0800 Subject: [PATCH 20/35] test: add functions_37 environment test config (#204) --- .kokoro/environment/functions_37/common.cfg | 39 +++++++++++++++++++ .../environment/functions_37/continuous.cfg | 1 + .../environment/functions_37/presubmit.cfg | 1 + .trampolinerc | 1 + synth.py | 2 +- tests/environment | 2 +- 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 .kokoro/environment/functions_37/common.cfg create mode 100644 .kokoro/environment/functions_37/continuous.cfg create mode 100644 .kokoro/environment/functions_37/presubmit.cfg diff --git a/.kokoro/environment/functions_37/common.cfg b/.kokoro/environment/functions_37/common.cfg new file mode 100644 index 000000000..4daa52db7 --- /dev/null +++ b/.kokoro/environment/functions_37/common.cfg @@ -0,0 +1,39 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + + +# Specify which tests to run +env_vars: { + key: "ENVIRONMENT" + value: "functions" +} + +env_vars: { + key: "RUNTIME" + value: "python37" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" + +# Use the trampoline script to run in docker. +build_file: "python-logging/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-logging/.kokoro/environment_tests.sh" +} diff --git a/.kokoro/environment/functions_37/continuous.cfg b/.kokoro/environment/functions_37/continuous.cfg new file mode 100644 index 000000000..8f43917d9 --- /dev/null +++ b/.kokoro/environment/functions_37/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/environment/functions_37/presubmit.cfg b/.kokoro/environment/functions_37/presubmit.cfg new file mode 100644 index 000000000..18a4c3532 --- /dev/null +++ b/.kokoro/environment/functions_37/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.trampolinerc b/.trampolinerc index 2c845a3de..6f984309b 100644 --- a/.trampolinerc +++ b/.trampolinerc @@ -20,6 +20,7 @@ required_envvars+=() # Add env vars which are passed down into the container here. pass_down_envvars+=( "ENVIRONMENT" + "RUNTIME" "STAGING_BUCKET" "V2_STAGING_BUCKET" "NOX_SESSION" diff --git a/synth.py b/synth.py index 4ee4bd1df..7f7008a39 100644 --- a/synth.py +++ b/synth.py @@ -77,7 +77,7 @@ s.replace( ".trampolinerc", "pass_down_envvars\+\=\(", - 'pass_down_envvars+=(\n "ENVIRONMENT"' + 'pass_down_envvars+=(\n "ENVIRONMENT"\n "RUNTIME"' ) # don't lint environment tests diff --git a/tests/environment b/tests/environment index 0e3311158..74542ed04 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit 0e331115867ca5a26b1efd9d99c43fbb1cb9363b +Subproject commit 74542ed0406167670870b7db1b170562973ede06 From 867362e803e7c11677df39f600d69d9506b3f0f8 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Fri, 5 Mar 2021 14:42:24 -0800 Subject: [PATCH 21/35] chore: report library test failures to flakybot (#199) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * build(python): enable flakybot on library unit and system tests Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Wed Feb 17 14:10:46 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: d17674372e27fb8f23013935e794aa37502071aa Source-Link: https://github.com/googleapis/synthtool/commit/d17674372e27fb8f23013935e794aa37502071aa --- .gitignore | 4 +++- .kokoro/build.sh | 10 ++++++++++ .kokoro/test-samples.sh | 8 ++++---- .kokoro/trampoline_v2.sh | 2 +- noxfile.py | 17 +++++++++++++++-- synth.metadata | 6 +++--- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index b9daa52f1..b4243ced7 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,10 @@ docs.metadata # Virtual environment env/ + +# Test logs coverage.xml -sponge_log.xml +*sponge_log.xml # System test environment variables. system_tests/local_test_setup diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 7145c57b0..cb17176f0 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -40,6 +40,16 @@ python3 -m pip uninstall --yes --quiet nox-automation python3 -m pip install --upgrade --quiet nox python3 -m nox --version +# If this is a continuous build, send the test log to the FlakyBot. +# See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. +if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]]; then + cleanup() { + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + } + trap cleanup EXIT HUP +fi + # If NOX_SESSION is set, it only runs the specified session, # otherwise run all the sessions. if [[ -n "${NOX_SESSION:-}" ]]; then diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index ba97b53d5..e75891832 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -87,11 +87,11 @@ for file in samples/**/requirements.txt; do python3.6 -m nox -s "$RUN_TESTS_SESSION" EXIT=$? - # If this is a periodic build, send the test log to the Build Cop Bot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop - $KOKORO_GFILE_DIR/linux_amd64/buildcop + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot fi if [[ $EXIT -ne 0 ]]; then diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh index 719bcd5ba..4af6cdc26 100755 --- a/.kokoro/trampoline_v2.sh +++ b/.kokoro/trampoline_v2.sh @@ -159,7 +159,7 @@ if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then "KOKORO_GITHUB_COMMIT" "KOKORO_GITHUB_PULL_REQUEST_NUMBER" "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For Build Cop Bot + # For FlakyBot "KOKORO_GITHUB_COMMIT_URL" "KOKORO_GITHUB_PULL_REQUEST_URL" ) diff --git a/noxfile.py b/noxfile.py index 19eef5d15..e29c56e42 100644 --- a/noxfile.py +++ b/noxfile.py @@ -91,6 +91,7 @@ def default(session): session.run( "py.test", "--quiet", + f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=google/cloud", "--cov=tests/unit", "--cov-append", @@ -145,9 +146,21 @@ def system(session): # Run py.test against the system tests. if system_test_exists: - session.run("py.test", "--quiet", system_test_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_path, + *session.posargs, + ) if system_test_folder_exists: - session.run("py.test", "--quiet", system_test_folder_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_folder_path, + *session.posargs, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/synth.metadata b/synth.metadata index 9ca738bf8..98c65e06a 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "18a0bb7fd8d158baef532b1c715524e5f6303311" + "sha": "de9c4c8bca265f6ecf98ecb9aa145891586232c8" } }, { @@ -19,14 +19,14 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "4679e7e415221f03ff2a71e3ffad75b9ec41d87e" + "sha": "d17674372e27fb8f23013935e794aa37502071aa" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "4679e7e415221f03ff2a71e3ffad75b9ec41d87e" + "sha": "d17674372e27fb8f23013935e794aa37502071aa" } } ], From 24b47894cc6bee72890033315beb713848ff7148 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 8 Mar 2021 11:15:39 -0800 Subject: [PATCH 22/35] test: install pyopenssl for mtls testing (#206) --- noxfile.py | 3 +++ synth.metadata | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index e29c56e42..1183ca5fb 100644 --- a/noxfile.py +++ b/noxfile.py @@ -121,6 +121,9 @@ def system(session): # Sanity check: Only run tests if the environment variable is set. if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""): session.skip("Credentials must be set via environment variable") + # Install pyopenssl for mTLS testing. + if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": + session.install("pyopenssl") system_test_exists = os.path.exists(system_test_path) system_test_folder_exists = os.path.exists(system_test_folder_path) diff --git a/synth.metadata b/synth.metadata index 98c65e06a..725861a90 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "de9c4c8bca265f6ecf98ecb9aa145891586232c8" + "sha": "867362e803e7c11677df39f600d69d9506b3f0f8" } }, { @@ -19,14 +19,14 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "d17674372e27fb8f23013935e794aa37502071aa" + "sha": "0780323da96d5a53925fe0547757181fe76e8f1e" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "d17674372e27fb8f23013935e794aa37502071aa" + "sha": "0780323da96d5a53925fe0547757181fe76e8f1e" } } ], From bfafb35ca634ff21099dde3d37dd39b589e934f3 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 8 Mar 2021 11:16:04 -0800 Subject: [PATCH 23/35] chore: Re-generated to pick up changes from googleapis. (#198) * chore: update gapic-generator-python PiperOrigin-RevId: 355923884 Source-Author: Google APIs Source-Date: Fri Feb 5 14:04:52 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 5e3dacee19405529b841b53797df799c2383536c Source-Link: https://github.com/googleapis/googleapis/commit/5e3dacee19405529b841b53797df799c2383536c * chore: update gapic-generator-python to 0.40.11 PiperOrigin-RevId: 359562873 Source-Author: Google APIs Source-Date: Thu Feb 25 10:52:32 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 07932bb995e7dc91b43620ea8402c6668c7d102c Source-Link: https://github.com/googleapis/googleapis/commit/07932bb995e7dc91b43620ea8402c6668c7d102c --- .../config_service_v2/async_client.py | 32 +- .../services/config_service_v2/pagers.py | 11 +- .../logging_service_v2/async_client.py | 32 +- .../services/logging_service_v2/client.py | 15 +- .../services/logging_service_v2/pagers.py | 11 +- .../metrics_service_v2/async_client.py | 32 +- .../services/metrics_service_v2/pagers.py | 11 +- synth.metadata | 4 +- tests/unit/gapic/logging_v2/__init__.py | 15 + .../logging_v2/test_config_service_v2.py | 382 +++++++++++++++++- .../logging_v2/test_logging_service_v2.py | 94 ++++- .../logging_v2/test_metrics_service_v2.py | 96 ++++- 12 files changed, 709 insertions(+), 26 deletions(-) diff --git a/google/cloud/logging_v2/services/config_service_v2/async_client.py b/google/cloud/logging_v2/services/config_service_v2/async_client.py index 9603b3754..a82d99b6e 100644 --- a/google/cloud/logging_v2/services/config_service_v2/async_client.py +++ b/google/cloud/logging_v2/services/config_service_v2/async_client.py @@ -90,8 +90,36 @@ class ConfigServiceV2AsyncClient: ConfigServiceV2Client.parse_common_location_path ) - from_service_account_info = ConfigServiceV2Client.from_service_account_info - from_service_account_file = ConfigServiceV2Client.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ConfigServiceV2AsyncClient: The constructed client. + """ + return ConfigServiceV2Client.from_service_account_info.__func__(ConfigServiceV2AsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ConfigServiceV2AsyncClient: The constructed client. + """ + return ConfigServiceV2Client.from_service_account_file.__func__(ConfigServiceV2AsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property diff --git a/google/cloud/logging_v2/services/config_service_v2/pagers.py b/google/cloud/logging_v2/services/config_service_v2/pagers.py index af5c5faf6..f656fef0d 100644 --- a/google/cloud/logging_v2/services/config_service_v2/pagers.py +++ b/google/cloud/logging_v2/services/config_service_v2/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.logging_v2.types import logging_config diff --git a/google/cloud/logging_v2/services/logging_service_v2/async_client.py b/google/cloud/logging_v2/services/logging_service_v2/async_client.py index 0c1ae3fae..5afd77be5 100644 --- a/google/cloud/logging_v2/services/logging_service_v2/async_client.py +++ b/google/cloud/logging_v2/services/logging_service_v2/async_client.py @@ -87,8 +87,36 @@ class LoggingServiceV2AsyncClient: LoggingServiceV2Client.parse_common_location_path ) - from_service_account_info = LoggingServiceV2Client.from_service_account_info - from_service_account_file = LoggingServiceV2Client.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + LoggingServiceV2AsyncClient: The constructed client. + """ + return LoggingServiceV2Client.from_service_account_info.__func__(LoggingServiceV2AsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + LoggingServiceV2AsyncClient: The constructed client. + """ + return LoggingServiceV2Client.from_service_account_file.__func__(LoggingServiceV2AsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property diff --git a/google/cloud/logging_v2/services/logging_service_v2/client.py b/google/cloud/logging_v2/services/logging_service_v2/client.py index 0e046c957..00d758ab5 100644 --- a/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -578,12 +578,10 @@ def write_log_entries( request.log_name = log_name if resource is not None: request.resource = resource - - if labels: - request.labels.update(labels) - - if entries: - request.entries.extend(entries) + if labels is not None: + request.labels = labels + if entries is not None: + request.entries = entries # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. @@ -699,14 +697,13 @@ def list_log_entries( # If we have keyword arguments corresponding to fields on the # request, apply these. + if resource_names is not None: + request.resource_names = resource_names if filter is not None: request.filter = filter if order_by is not None: request.order_by = order_by - if resource_names: - request.resource_names.extend(resource_names) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[self._transport.list_log_entries] diff --git a/google/cloud/logging_v2/services/logging_service_v2/pagers.py b/google/cloud/logging_v2/services/logging_service_v2/pagers.py index 5492a3a30..7ab8ac8d2 100644 --- a/google/cloud/logging_v2/services/logging_service_v2/pagers.py +++ b/google/cloud/logging_v2/services/logging_service_v2/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.api import monitored_resource_pb2 as monitored_resource # type: ignore from google.cloud.logging_v2.types import log_entry diff --git a/google/cloud/logging_v2/services/metrics_service_v2/async_client.py b/google/cloud/logging_v2/services/metrics_service_v2/async_client.py index 2c592e685..09b2c3173 100644 --- a/google/cloud/logging_v2/services/metrics_service_v2/async_client.py +++ b/google/cloud/logging_v2/services/metrics_service_v2/async_client.py @@ -80,8 +80,36 @@ class MetricsServiceV2AsyncClient: MetricsServiceV2Client.parse_common_location_path ) - from_service_account_info = MetricsServiceV2Client.from_service_account_info - from_service_account_file = MetricsServiceV2Client.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + MetricsServiceV2AsyncClient: The constructed client. + """ + return MetricsServiceV2Client.from_service_account_info.__func__(MetricsServiceV2AsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + MetricsServiceV2AsyncClient: The constructed client. + """ + return MetricsServiceV2Client.from_service_account_file.__func__(MetricsServiceV2AsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property diff --git a/google/cloud/logging_v2/services/metrics_service_v2/pagers.py b/google/cloud/logging_v2/services/metrics_service_v2/pagers.py index 51c398598..15134ac57 100644 --- a/google/cloud/logging_v2/services/metrics_service_v2/pagers.py +++ b/google/cloud/logging_v2/services/metrics_service_v2/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.logging_v2.types import logging_metrics diff --git a/synth.metadata b/synth.metadata index 725861a90..d039bf176 100644 --- a/synth.metadata +++ b/synth.metadata @@ -11,8 +11,8 @@ "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "20712b8fe95001b312f62c6c5f33e3e3ec92cfaf", - "internalRef": "354996675" + "sha": "07932bb995e7dc91b43620ea8402c6668c7d102c", + "internalRef": "359562873" } }, { diff --git a/tests/unit/gapic/logging_v2/__init__.py b/tests/unit/gapic/logging_v2/__init__.py index 8b1378917..42ffdf2bc 100644 --- a/tests/unit/gapic/logging_v2/__init__.py +++ b/tests/unit/gapic/logging_v2/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# 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. +# diff --git a/tests/unit/gapic/logging_v2/test_config_service_v2.py b/tests/unit/gapic/logging_v2/test_config_service_v2.py index c819769ee..d6a2f3983 100644 --- a/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -88,15 +88,19 @@ def test__get_default_mtls_endpoint(): ) -def test_config_service_v2_client_from_service_account_info(): +@pytest.mark.parametrize( + "client_class", [ConfigServiceV2Client, ConfigServiceV2AsyncClient,] +) +def test_config_service_v2_client_from_service_account_info(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( service_account.Credentials, "from_service_account_info" ) as factory: factory.return_value = creds info = {"valid": True} - client = ConfigServiceV2Client.from_service_account_info(info) + client = client_class.from_service_account_info(info) assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "logging.googleapis.com:443" @@ -112,9 +116,11 @@ def test_config_service_v2_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "logging.googleapis.com:443" @@ -487,6 +493,22 @@ def test_list_buckets_from_dict(): test_list_buckets(request_type=dict) +def test_list_buckets_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_buckets), "__call__") as call: + client.list_buckets() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.ListBucketsRequest() + + @pytest.mark.asyncio async def test_list_buckets_async( transport: str = "grpc_asyncio", request_type=logging_config.ListBucketsRequest @@ -830,6 +852,22 @@ def test_get_bucket_from_dict(): test_get_bucket(request_type=dict) +def test_get_bucket_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_bucket), "__call__") as call: + client.get_bucket() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.GetBucketRequest() + + @pytest.mark.asyncio async def test_get_bucket_async( transport: str = "grpc_asyncio", request_type=logging_config.GetBucketRequest @@ -982,6 +1020,22 @@ def test_create_bucket_from_dict(): test_create_bucket(request_type=dict) +def test_create_bucket_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_bucket), "__call__") as call: + client.create_bucket() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.CreateBucketRequest() + + @pytest.mark.asyncio async def test_create_bucket_async( transport: str = "grpc_asyncio", request_type=logging_config.CreateBucketRequest @@ -1134,6 +1188,22 @@ def test_update_bucket_from_dict(): test_update_bucket(request_type=dict) +def test_update_bucket_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_bucket), "__call__") as call: + client.update_bucket() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.UpdateBucketRequest() + + @pytest.mark.asyncio async def test_update_bucket_async( transport: str = "grpc_asyncio", request_type=logging_config.UpdateBucketRequest @@ -1269,6 +1339,22 @@ def test_delete_bucket_from_dict(): test_delete_bucket(request_type=dict) +def test_delete_bucket_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_bucket), "__call__") as call: + client.delete_bucket() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.DeleteBucketRequest() + + @pytest.mark.asyncio async def test_delete_bucket_async( transport: str = "grpc_asyncio", request_type=logging_config.DeleteBucketRequest @@ -1384,6 +1470,22 @@ def test_undelete_bucket_from_dict(): test_undelete_bucket(request_type=dict) +def test_undelete_bucket_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.undelete_bucket), "__call__") as call: + client.undelete_bucket() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.UndeleteBucketRequest() + + @pytest.mark.asyncio async def test_undelete_bucket_async( transport: str = "grpc_asyncio", request_type=logging_config.UndeleteBucketRequest @@ -1504,6 +1606,22 @@ def test_list_views_from_dict(): test_list_views(request_type=dict) +def test_list_views_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_views), "__call__") as call: + client.list_views() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.ListViewsRequest() + + @pytest.mark.asyncio async def test_list_views_async( transport: str = "grpc_asyncio", request_type=logging_config.ListViewsRequest @@ -1837,6 +1955,22 @@ def test_get_view_from_dict(): test_get_view(request_type=dict) +def test_get_view_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_view), "__call__") as call: + client.get_view() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.GetViewRequest() + + @pytest.mark.asyncio async def test_get_view_async( transport: str = "grpc_asyncio", request_type=logging_config.GetViewRequest @@ -1975,6 +2109,22 @@ def test_create_view_from_dict(): test_create_view(request_type=dict) +def test_create_view_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_view), "__call__") as call: + client.create_view() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.CreateViewRequest() + + @pytest.mark.asyncio async def test_create_view_async( transport: str = "grpc_asyncio", request_type=logging_config.CreateViewRequest @@ -2113,6 +2263,22 @@ def test_update_view_from_dict(): test_update_view(request_type=dict) +def test_update_view_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_view), "__call__") as call: + client.update_view() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.UpdateViewRequest() + + @pytest.mark.asyncio async def test_update_view_async( transport: str = "grpc_asyncio", request_type=logging_config.UpdateViewRequest @@ -2242,6 +2408,22 @@ def test_delete_view_from_dict(): test_delete_view(request_type=dict) +def test_delete_view_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_view), "__call__") as call: + client.delete_view() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.DeleteViewRequest() + + @pytest.mark.asyncio async def test_delete_view_async( transport: str = "grpc_asyncio", request_type=logging_config.DeleteViewRequest @@ -2362,6 +2544,22 @@ def test_list_sinks_from_dict(): test_list_sinks(request_type=dict) +def test_list_sinks_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_sinks), "__call__") as call: + client.list_sinks() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.ListSinksRequest() + + @pytest.mark.asyncio async def test_list_sinks_async( transport: str = "grpc_asyncio", request_type=logging_config.ListSinksRequest @@ -2715,6 +2913,22 @@ def test_get_sink_from_dict(): test_get_sink(request_type=dict) +def test_get_sink_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_sink), "__call__") as call: + client.get_sink() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.GetSinkRequest() + + @pytest.mark.asyncio async def test_get_sink_async( transport: str = "grpc_asyncio", request_type=logging_config.GetSinkRequest @@ -2955,6 +3169,22 @@ def test_create_sink_from_dict(): test_create_sink(request_type=dict) +def test_create_sink_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_sink), "__call__") as call: + client.create_sink() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.CreateSinkRequest() + + @pytest.mark.asyncio async def test_create_sink_async( transport: str = "grpc_asyncio", request_type=logging_config.CreateSinkRequest @@ -3207,6 +3437,22 @@ def test_update_sink_from_dict(): test_update_sink(request_type=dict) +def test_update_sink_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_sink), "__call__") as call: + client.update_sink() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.UpdateSinkRequest() + + @pytest.mark.asyncio async def test_update_sink_async( transport: str = "grpc_asyncio", request_type=logging_config.UpdateSinkRequest @@ -3440,6 +3686,22 @@ def test_delete_sink_from_dict(): test_delete_sink(request_type=dict) +def test_delete_sink_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_sink), "__call__") as call: + client.delete_sink() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.DeleteSinkRequest() + + @pytest.mark.asyncio async def test_delete_sink_async( transport: str = "grpc_asyncio", request_type=logging_config.DeleteSinkRequest @@ -3625,6 +3887,22 @@ def test_list_exclusions_from_dict(): test_list_exclusions(request_type=dict) +def test_list_exclusions_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_exclusions), "__call__") as call: + client.list_exclusions() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.ListExclusionsRequest() + + @pytest.mark.asyncio async def test_list_exclusions_async( transport: str = "grpc_asyncio", request_type=logging_config.ListExclusionsRequest @@ -3987,6 +4265,22 @@ def test_get_exclusion_from_dict(): test_get_exclusion(request_type=dict) +def test_get_exclusion_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_exclusion), "__call__") as call: + client.get_exclusion() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.GetExclusionRequest() + + @pytest.mark.asyncio async def test_get_exclusion_async( transport: str = "grpc_asyncio", request_type=logging_config.GetExclusionRequest @@ -4200,6 +4494,22 @@ def test_create_exclusion_from_dict(): test_create_exclusion(request_type=dict) +def test_create_exclusion_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_exclusion), "__call__") as call: + client.create_exclusion() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.CreateExclusionRequest() + + @pytest.mark.asyncio async def test_create_exclusion_async( transport: str = "grpc_asyncio", request_type=logging_config.CreateExclusionRequest @@ -4427,6 +4737,22 @@ def test_update_exclusion_from_dict(): test_update_exclusion(request_type=dict) +def test_update_exclusion_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_exclusion), "__call__") as call: + client.update_exclusion() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.UpdateExclusionRequest() + + @pytest.mark.asyncio async def test_update_exclusion_async( transport: str = "grpc_asyncio", request_type=logging_config.UpdateExclusionRequest @@ -4648,6 +4974,22 @@ def test_delete_exclusion_from_dict(): test_delete_exclusion(request_type=dict) +def test_delete_exclusion_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_exclusion), "__call__") as call: + client.delete_exclusion() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.DeleteExclusionRequest() + + @pytest.mark.asyncio async def test_delete_exclusion_async( transport: str = "grpc_asyncio", request_type=logging_config.DeleteExclusionRequest @@ -4841,6 +5183,24 @@ def test_get_cmek_settings_from_dict(): test_get_cmek_settings(request_type=dict) +def test_get_cmek_settings_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_cmek_settings), "__call__" + ) as call: + client.get_cmek_settings() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.GetCmekSettingsRequest() + + @pytest.mark.asyncio async def test_get_cmek_settings_async( transport: str = "grpc_asyncio", request_type=logging_config.GetCmekSettingsRequest @@ -4989,6 +5349,24 @@ def test_update_cmek_settings_from_dict(): test_update_cmek_settings(request_type=dict) +def test_update_cmek_settings_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConfigServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_cmek_settings), "__call__" + ) as call: + client.update_cmek_settings() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_config.UpdateCmekSettingsRequest() + + @pytest.mark.asyncio async def test_update_cmek_settings_async( transport: str = "grpc_asyncio", diff --git a/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/tests/unit/gapic/logging_v2/test_logging_service_v2.py index c811acac5..66f22621c 100644 --- a/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -95,15 +95,19 @@ def test__get_default_mtls_endpoint(): ) -def test_logging_service_v2_client_from_service_account_info(): +@pytest.mark.parametrize( + "client_class", [LoggingServiceV2Client, LoggingServiceV2AsyncClient,] +) +def test_logging_service_v2_client_from_service_account_info(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( service_account.Credentials, "from_service_account_info" ) as factory: factory.return_value = creds info = {"valid": True} - client = LoggingServiceV2Client.from_service_account_info(info) + client = client_class.from_service_account_info(info) assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "logging.googleapis.com:443" @@ -119,9 +123,11 @@ def test_logging_service_v2_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "logging.googleapis.com:443" @@ -487,6 +493,22 @@ def test_delete_log_from_dict(): test_delete_log(request_type=dict) +def test_delete_log_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = LoggingServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_log), "__call__") as call: + client.delete_log() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging.DeleteLogRequest() + + @pytest.mark.asyncio async def test_delete_log_async( transport: str = "grpc_asyncio", request_type=logging.DeleteLogRequest @@ -676,6 +698,24 @@ def test_write_log_entries_from_dict(): test_write_log_entries(request_type=dict) +def test_write_log_entries_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = LoggingServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.write_log_entries), "__call__" + ) as call: + client.write_log_entries() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging.WriteLogEntriesRequest() + + @pytest.mark.asyncio async def test_write_log_entries_async( transport: str = "grpc_asyncio", request_type=logging.WriteLogEntriesRequest @@ -860,6 +900,22 @@ def test_list_log_entries_from_dict(): test_list_log_entries(request_type=dict) +def test_list_log_entries_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = LoggingServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_log_entries), "__call__") as call: + client.list_log_entries() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging.ListLogEntriesRequest() + + @pytest.mark.asyncio async def test_list_log_entries_async( transport: str = "grpc_asyncio", request_type=logging.ListLogEntriesRequest @@ -1166,6 +1222,24 @@ def test_list_monitored_resource_descriptors_from_dict(): test_list_monitored_resource_descriptors(request_type=dict) +def test_list_monitored_resource_descriptors_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = LoggingServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_monitored_resource_descriptors), "__call__" + ) as call: + client.list_monitored_resource_descriptors() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging.ListMonitoredResourceDescriptorsRequest() + + @pytest.mark.asyncio async def test_list_monitored_resource_descriptors_async( transport: str = "grpc_asyncio", @@ -1429,6 +1503,22 @@ def test_list_logs_from_dict(): test_list_logs(request_type=dict) +def test_list_logs_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = LoggingServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_logs), "__call__") as call: + client.list_logs() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging.ListLogsRequest() + + @pytest.mark.asyncio async def test_list_logs_async( transport: str = "grpc_asyncio", request_type=logging.ListLogsRequest diff --git a/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 7230b0d87..6faec201e 100644 --- a/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -94,15 +94,19 @@ def test__get_default_mtls_endpoint(): ) -def test_metrics_service_v2_client_from_service_account_info(): +@pytest.mark.parametrize( + "client_class", [MetricsServiceV2Client, MetricsServiceV2AsyncClient,] +) +def test_metrics_service_v2_client_from_service_account_info(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( service_account.Credentials, "from_service_account_info" ) as factory: factory.return_value = creds info = {"valid": True} - client = MetricsServiceV2Client.from_service_account_info(info) + client = client_class.from_service_account_info(info) assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "logging.googleapis.com:443" @@ -118,9 +122,11 @@ def test_metrics_service_v2_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "logging.googleapis.com:443" @@ -493,6 +499,22 @@ def test_list_log_metrics_from_dict(): test_list_log_metrics(request_type=dict) +def test_list_log_metrics_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = MetricsServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_log_metrics), "__call__") as call: + client.list_log_metrics() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_metrics.ListLogMetricsRequest() + + @pytest.mark.asyncio async def test_list_log_metrics_async( transport: str = "grpc_asyncio", request_type=logging_metrics.ListLogMetricsRequest @@ -844,6 +866,22 @@ def test_get_log_metric_from_dict(): test_get_log_metric(request_type=dict) +def test_get_log_metric_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = MetricsServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_log_metric), "__call__") as call: + client.get_log_metric() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_metrics.GetLogMetricRequest() + + @pytest.mark.asyncio async def test_get_log_metric_async( transport: str = "grpc_asyncio", request_type=logging_metrics.GetLogMetricRequest @@ -1071,6 +1109,24 @@ def test_create_log_metric_from_dict(): test_create_log_metric(request_type=dict) +def test_create_log_metric_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = MetricsServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_log_metric), "__call__" + ) as call: + client.create_log_metric() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_metrics.CreateLogMetricRequest() + + @pytest.mark.asyncio async def test_create_log_metric_async( transport: str = "grpc_asyncio", request_type=logging_metrics.CreateLogMetricRequest @@ -1320,6 +1376,24 @@ def test_update_log_metric_from_dict(): test_update_log_metric(request_type=dict) +def test_update_log_metric_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = MetricsServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_log_metric), "__call__" + ) as call: + client.update_log_metric() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_metrics.UpdateLogMetricRequest() + + @pytest.mark.asyncio async def test_update_log_metric_async( transport: str = "grpc_asyncio", request_type=logging_metrics.UpdateLogMetricRequest @@ -1554,6 +1628,24 @@ def test_delete_log_metric_from_dict(): test_delete_log_metric(request_type=dict) +def test_delete_log_metric_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = MetricsServiceV2Client( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_log_metric), "__call__" + ) as call: + client.delete_log_metric() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == logging_metrics.DeleteLogMetricRequest() + + @pytest.mark.asyncio async def test_delete_log_metric_async( transport: str = "grpc_asyncio", request_type=logging_metrics.DeleteLogMetricRequest From 0f90a79d165314d261413cc369408e15f711129f Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 9 Mar 2021 11:45:34 -0800 Subject: [PATCH 24/35] fix: logger uses default resource (#207) --- google/cloud/logging_v2/logger.py | 4 ++- tests/environment | 2 +- tests/unit/test_logger.py | 44 ++++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/google/cloud/logging_v2/logger.py b/google/cloud/logging_v2/logger.py index 6e9c5f00d..124c33934 100644 --- a/google/cloud/logging_v2/logger.py +++ b/google/cloud/logging_v2/logger.py @@ -20,6 +20,7 @@ from google.cloud.logging_v2.entries import StructEntry from google.cloud.logging_v2.entries import TextEntry from google.cloud.logging_v2.resource import Resource +from google.cloud.logging_v2.handlers._monitored_resources import detect_resource _GLOBAL_RESOURCE = Resource(type="global", labels={}) @@ -62,6 +63,7 @@ def __init__(self, name, client, *, labels=None): self.name = name self._client = client self.labels = labels + self.default_resource = detect_resource(client.project) @property def client(self): @@ -120,7 +122,7 @@ def _do_log(self, client, _entry_class, payload=None, **kw): # Apply defaults kw["log_name"] = kw.pop("log_name", self.full_name) kw["labels"] = kw.pop("labels", self.labels) - kw["resource"] = kw.pop("resource", _GLOBAL_RESOURCE) + kw["resource"] = kw.pop("resource", self.default_resource) if payload is not None: entry = _entry_class(payload=payload, **kw) diff --git a/tests/environment b/tests/environment index 74542ed04..a7dd02716 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit 74542ed0406167670870b7db1b170562973ede06 +Subproject commit a7dd027166b8df1980881a94ba8473065497701d diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py index 869330633..53ecac8f5 100644 --- a/tests/unit/test_logger.py +++ b/tests/unit/test_logger.py @@ -99,11 +99,15 @@ def test_batch_w_alternate_client(self): self.assertIs(batch.client, client2) def test_log_empty_defaults_w_default_labels(self): + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) + DEFAULT_LABELS = {"foo": "spam"} ENTRIES = [ { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), - "resource": {"type": "global", "labels": {}}, + "resource": detect_resource(self.PROJECT)._to_dict(), "labels": DEFAULT_LABELS, } ] @@ -170,12 +174,17 @@ def test_log_empty_w_explicit(self): self.assertEqual(api._write_entries_called_with, (ENTRIES, None, None, None)) def test_log_text_defaults(self): + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) + + RESOURCE = detect_resource(self.PROJECT)._to_dict() TEXT = "TEXT" ENTRIES = [ { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), "textPayload": TEXT, - "resource": {"type": "global", "labels": {}}, + "resource": RESOURCE, } ] client = _Client(self.PROJECT) @@ -187,13 +196,18 @@ def test_log_text_defaults(self): self.assertEqual(api._write_entries_called_with, (ENTRIES, None, None, None)) def test_log_text_w_unicode_and_default_labels(self): + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) + TEXT = "TEXT" + RESOURCE = detect_resource(self.PROJECT)._to_dict() DEFAULT_LABELS = {"foo": "spam"} ENTRIES = [ { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), "textPayload": TEXT, - "resource": {"type": "global", "labels": {}}, + "resource": RESOURCE, "labels": DEFAULT_LABELS, } ] @@ -263,12 +277,17 @@ def test_log_text_explicit(self): self.assertEqual(api._write_entries_called_with, (ENTRIES, None, None, None)) def test_log_struct_defaults(self): + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) + STRUCT = {"message": "MESSAGE", "weather": "cloudy"} + RESOURCE = detect_resource(self.PROJECT)._to_dict() ENTRIES = [ { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), "jsonPayload": STRUCT, - "resource": {"type": "global", "labels": {}}, + "resource": RESOURCE, } ] client = _Client(self.PROJECT) @@ -280,13 +299,18 @@ def test_log_struct_defaults(self): self.assertEqual(api._write_entries_called_with, (ENTRIES, None, None, None)) def test_log_struct_w_default_labels(self): + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) + STRUCT = {"message": "MESSAGE", "weather": "cloudy"} + RESOURCE = detect_resource(self.PROJECT)._to_dict() DEFAULT_LABELS = {"foo": "spam"} ENTRIES = [ { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), "jsonPayload": STRUCT, - "resource": {"type": "global", "labels": {}}, + "resource": RESOURCE, "labels": DEFAULT_LABELS, } ] @@ -359,13 +383,16 @@ def test_log_proto_defaults(self): import json from google.protobuf.json_format import MessageToJson from google.protobuf.struct_pb2 import Struct, Value + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) message = Struct(fields={"foo": Value(bool_value=True)}) ENTRIES = [ { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), "protoPayload": json.loads(MessageToJson(message)), - "resource": {"type": "global", "labels": {}}, + "resource": detect_resource(self.PROJECT)._to_dict(), } ] client = _Client(self.PROJECT) @@ -380,6 +407,9 @@ def test_log_proto_w_default_labels(self): import json from google.protobuf.json_format import MessageToJson from google.protobuf.struct_pb2 import Struct, Value + from google.cloud.logging_v2.handlers._monitored_resources import ( + detect_resource, + ) message = Struct(fields={"foo": Value(bool_value=True)}) DEFAULT_LABELS = {"foo": "spam"} @@ -387,7 +417,7 @@ def test_log_proto_w_default_labels(self): { "logName": "projects/%s/logs/%s" % (self.PROJECT, self.LOGGER_NAME), "protoPayload": json.loads(MessageToJson(message)), - "resource": {"type": "global", "labels": {}}, + "resource": detect_resource(self.PROJECT)._to_dict(), "labels": DEFAULT_LABELS, } ] From 5af6ee94ee082be2b0d3dbb3dd50f19bc0dc8b38 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 11 Mar 2021 15:58:31 -0800 Subject: [PATCH 25/35] chore: upgrade gapic-generator-python to 0.42.2 (#213) PiperOrigin-RevId: 361662015 Source-Author: Google APIs Source-Date: Mon Mar 8 14:47:18 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 28a591963253d52ce3a25a918cafbdd9928de8cf Source-Link: https://github.com/googleapis/googleapis/commit/28a591963253d52ce3a25a918cafbdd9928de8cf --- google/cloud/logging_v2/types/__init__.py | 168 +++++++++++----------- synth.metadata | 6 +- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/google/cloud/logging_v2/types/__init__.py b/google/cloud/logging_v2/types/__init__.py index dce385af3..9519c0777 100644 --- a/google/cloud/logging_v2/types/__init__.py +++ b/google/cloud/logging_v2/types/__init__.py @@ -20,121 +20,121 @@ LogEntryOperation, LogEntrySourceLocation, ) +from .logging import ( + DeleteLogRequest, + ListLogEntriesRequest, + ListLogEntriesResponse, + ListLogsRequest, + ListLogsResponse, + ListMonitoredResourceDescriptorsRequest, + ListMonitoredResourceDescriptorsResponse, + TailLogEntriesRequest, + TailLogEntriesResponse, + WriteLogEntriesPartialErrors, + WriteLogEntriesRequest, + WriteLogEntriesResponse, +) from .logging_config import ( - LogBucket, - LogView, - LogSink, BigQueryOptions, - ListBucketsRequest, - ListBucketsResponse, + CmekSettings, CreateBucketRequest, - UpdateBucketRequest, - GetBucketRequest, - DeleteBucketRequest, - UndeleteBucketRequest, - ListViewsRequest, - ListViewsResponse, + CreateExclusionRequest, + CreateSinkRequest, CreateViewRequest, - UpdateViewRequest, - GetViewRequest, + DeleteBucketRequest, + DeleteExclusionRequest, + DeleteSinkRequest, DeleteViewRequest, - ListSinksRequest, - ListSinksResponse, + GetBucketRequest, + GetCmekSettingsRequest, + GetExclusionRequest, GetSinkRequest, - CreateSinkRequest, - UpdateSinkRequest, - DeleteSinkRequest, - LogExclusion, + GetViewRequest, + ListBucketsRequest, + ListBucketsResponse, ListExclusionsRequest, ListExclusionsResponse, - GetExclusionRequest, - CreateExclusionRequest, - UpdateExclusionRequest, - DeleteExclusionRequest, - GetCmekSettingsRequest, + ListSinksRequest, + ListSinksResponse, + ListViewsRequest, + ListViewsResponse, + LogBucket, + LogExclusion, + LogSink, + LogView, + UndeleteBucketRequest, + UpdateBucketRequest, UpdateCmekSettingsRequest, - CmekSettings, + UpdateExclusionRequest, + UpdateSinkRequest, + UpdateViewRequest, LifecycleState, ) -from .logging import ( - DeleteLogRequest, - WriteLogEntriesRequest, - WriteLogEntriesResponse, - WriteLogEntriesPartialErrors, - ListLogEntriesRequest, - ListLogEntriesResponse, - ListMonitoredResourceDescriptorsRequest, - ListMonitoredResourceDescriptorsResponse, - ListLogsRequest, - ListLogsResponse, - TailLogEntriesRequest, - TailLogEntriesResponse, -) from .logging_metrics import ( - LogMetric, + CreateLogMetricRequest, + DeleteLogMetricRequest, + GetLogMetricRequest, ListLogMetricsRequest, ListLogMetricsResponse, - GetLogMetricRequest, - CreateLogMetricRequest, + LogMetric, UpdateLogMetricRequest, - DeleteLogMetricRequest, ) __all__ = ( "LogEntry", "LogEntryOperation", "LogEntrySourceLocation", - "LogBucket", - "LogView", - "LogSink", + "DeleteLogRequest", + "ListLogEntriesRequest", + "ListLogEntriesResponse", + "ListLogsRequest", + "ListLogsResponse", + "ListMonitoredResourceDescriptorsRequest", + "ListMonitoredResourceDescriptorsResponse", + "TailLogEntriesRequest", + "TailLogEntriesResponse", + "WriteLogEntriesPartialErrors", + "WriteLogEntriesRequest", + "WriteLogEntriesResponse", "BigQueryOptions", - "ListBucketsRequest", - "ListBucketsResponse", + "CmekSettings", "CreateBucketRequest", - "UpdateBucketRequest", - "GetBucketRequest", - "DeleteBucketRequest", - "UndeleteBucketRequest", - "ListViewsRequest", - "ListViewsResponse", + "CreateExclusionRequest", + "CreateSinkRequest", "CreateViewRequest", - "UpdateViewRequest", - "GetViewRequest", + "DeleteBucketRequest", + "DeleteExclusionRequest", + "DeleteSinkRequest", "DeleteViewRequest", - "ListSinksRequest", - "ListSinksResponse", + "GetBucketRequest", + "GetCmekSettingsRequest", + "GetExclusionRequest", "GetSinkRequest", - "CreateSinkRequest", - "UpdateSinkRequest", - "DeleteSinkRequest", - "LogExclusion", + "GetViewRequest", + "ListBucketsRequest", + "ListBucketsResponse", "ListExclusionsRequest", "ListExclusionsResponse", - "GetExclusionRequest", - "CreateExclusionRequest", - "UpdateExclusionRequest", - "DeleteExclusionRequest", - "GetCmekSettingsRequest", + "ListSinksRequest", + "ListSinksResponse", + "ListViewsRequest", + "ListViewsResponse", + "LogBucket", + "LogExclusion", + "LogSink", + "LogView", + "UndeleteBucketRequest", + "UpdateBucketRequest", "UpdateCmekSettingsRequest", - "CmekSettings", + "UpdateExclusionRequest", + "UpdateSinkRequest", + "UpdateViewRequest", "LifecycleState", - "DeleteLogRequest", - "WriteLogEntriesRequest", - "WriteLogEntriesResponse", - "WriteLogEntriesPartialErrors", - "ListLogEntriesRequest", - "ListLogEntriesResponse", - "ListMonitoredResourceDescriptorsRequest", - "ListMonitoredResourceDescriptorsResponse", - "ListLogsRequest", - "ListLogsResponse", - "TailLogEntriesRequest", - "TailLogEntriesResponse", - "LogMetric", + "CreateLogMetricRequest", + "DeleteLogMetricRequest", + "GetLogMetricRequest", "ListLogMetricsRequest", "ListLogMetricsResponse", - "GetLogMetricRequest", - "CreateLogMetricRequest", + "LogMetric", "UpdateLogMetricRequest", - "DeleteLogMetricRequest", ) diff --git a/synth.metadata b/synth.metadata index d039bf176..dfee48d55 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,15 +4,15 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "867362e803e7c11677df39f600d69d9506b3f0f8" + "sha": "0f90a79d165314d261413cc369408e15f711129f" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "07932bb995e7dc91b43620ea8402c6668c7d102c", - "internalRef": "359562873" + "sha": "28a591963253d52ce3a25a918cafbdd9928de8cf", + "internalRef": "361662015" } }, { From df22c6c78f7287aa3533dab7730a835d7d3d17d9 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 12 Mar 2021 00:58:48 +0100 Subject: [PATCH 26/35] chore(deps): update dependency google-cloud-storage to v1.36.2 (#215) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 131f3bbee..d87af05fa 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 google-cloud-bigquery==2.10.0 -google-cloud-storage==1.36.1 +google-cloud-storage==1.36.2 google-cloud-pubsub==2.4.0 From 42bda27dd3eee2123cbe5b21dd75a587af7a910b Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 11 Mar 2021 15:59:25 -0800 Subject: [PATCH 27/35] chore: update gapic-generator-python to 0.40.11 (#214) PiperOrigin-RevId: 359562873 Source-Author: Google APIs Source-Date: Thu Feb 25 10:52:32 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 07932bb995e7dc91b43620ea8402c6668c7d102c Source-Link: https://github.com/googleapis/googleapis/commit/07932bb995e7dc91b43620ea8402c6668c7d102c --- logging-v2-py.tar.gz | 0 synth.metadata | 1 + 2 files changed, 1 insertion(+) create mode 100644 logging-v2-py.tar.gz diff --git a/logging-v2-py.tar.gz b/logging-v2-py.tar.gz new file mode 100644 index 000000000..e69de29bb diff --git a/synth.metadata b/synth.metadata index dfee48d55..611106822 100644 --- a/synth.metadata +++ b/synth.metadata @@ -132,6 +132,7 @@ "google/cloud/logging_v2/types/logging.py", "google/cloud/logging_v2/types/logging_config.py", "google/cloud/logging_v2/types/logging_metrics.py", + "logging-v2-py.tar.gz", "mypy.ini", "noxfile.py", "renovate.json", From e8c8e30fc4f618273dec1415c752eed203c75b67 Mon Sep 17 00:00:00 2001 From: ArthurYueh Date: Tue, 16 Mar 2021 00:33:27 +0800 Subject: [PATCH 28/35] =?UTF-8?q?feat:=20Add=20json=20setting=20to=20allow?= =?UTF-8?q?=20unicodes=20to=20show=20in=20log=20instead=20of=20ascii=20ch?= =?UTF-8?q?=E2=80=A6=20(#193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- google/cloud/logging_v2/handlers/_helpers.py | 2 +- tests/unit/handlers/test_container_engine.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/google/cloud/logging_v2/handlers/_helpers.py b/google/cloud/logging_v2/handlers/_helpers.py index fff1e9a89..88eba07a6 100644 --- a/google/cloud/logging_v2/handlers/_helpers.py +++ b/google/cloud/logging_v2/handlers/_helpers.py @@ -48,7 +48,7 @@ def format_stackdriver_json(record, message): "severity": record.levelname, } - return json.dumps(payload) + return json.dumps(payload, ensure_ascii=False) def get_request_data_from_flask(): diff --git a/tests/unit/handlers/test_container_engine.py b/tests/unit/handlers/test_container_engine.py index d2ae83881..280ab9cf0 100644 --- a/tests/unit/handlers/test_container_engine.py +++ b/tests/unit/handlers/test_container_engine.py @@ -40,7 +40,7 @@ def test_format(self): handler = self._make_one() logname = "loggername" - message = "hello world" + message = "hello world,嗨 世界" record = logging.LogRecord( logname, logging.INFO, None, None, message, None, None ) @@ -53,4 +53,4 @@ def test_format(self): } payload = handler.format(record) - self.assertEqual(payload, json.dumps(expected_payload)) + self.assertEqual(payload, json.dumps(expected_payload, ensure_ascii=False)) From 1d8bb42857c339ad67fbb3b9fc56be177549e160 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 15 Mar 2021 17:34:05 +0100 Subject: [PATCH 29/35] chore(deps): update dependency google-cloud-bigquery to v2.11.0 (#212) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index d87af05fa..cc757aabc 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-logging==2.2.0 -google-cloud-bigquery==2.10.0 +google-cloud-bigquery==2.11.0 google-cloud-storage==1.36.2 google-cloud-pubsub==2.4.0 From 37e6c8e90775ddc2fc454f5cb13cab04231c2222 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 15 Mar 2021 09:53:13 -0700 Subject: [PATCH 30/35] fix: no duplicate logs on GCF or GAE (#209) --- google/cloud/logging_v2/client.py | 1 + .../handlers/_monitored_resources.py | 2 +- google/cloud/logging_v2/handlers/handlers.py | 7 +++ tests/environment | 2 +- tests/unit/handlers/test_handlers.py | 49 +++++++++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/google/cloud/logging_v2/client.py b/google/cloud/logging_v2/client.py index f196f443a..17d853401 100644 --- a/google/cloud/logging_v2/client.py +++ b/google/cloud/logging_v2/client.py @@ -52,6 +52,7 @@ _GAE_RESOURCE_TYPE = "gae_app" _GKE_RESOURCE_TYPE = "k8s_container" +_GCF_RESOURCE_TYPE = "cloud_function" class Client(ClientWithProject): diff --git a/google/cloud/logging_v2/handlers/_monitored_resources.py b/google/cloud/logging_v2/handlers/_monitored_resources.py index 4bc30d4fb..bd05c2522 100644 --- a/google/cloud/logging_v2/handlers/_monitored_resources.py +++ b/google/cloud/logging_v2/handlers/_monitored_resources.py @@ -163,7 +163,7 @@ def _create_global_resource(project): return Resource(type="global", labels={"project_id": project}) -def detect_resource(project): +def detect_resource(project=""): """Return the default monitored resource based on the local environment. Args: project (str): The project ID to pass on to the resource diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index c2ad6f355..feeac9171 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -23,6 +23,8 @@ EXCLUDED_LOGGER_DEFAULTS = ("google.cloud", "google.auth", "google_auth_httplib2") +_CLEAR_HANDLER_RESOURCE_TYPES = ("gae_app", "cloud_function") + class CloudLoggingHandler(logging.StreamHandler): """Handler that directly makes Cloud Logging API calls. @@ -160,6 +162,11 @@ def setup_logging( """ all_excluded_loggers = set(excluded_loggers + EXCLUDED_LOGGER_DEFAULTS) logger = logging.getLogger() + + # remove built-in handlers on App Engine or Cloud Functions environments + if detect_resource().type in _CLEAR_HANDLER_RESOURCE_TYPES: + logger.handlers.clear() + logger.setLevel(log_level) logger.addHandler(handler) for logger_name in all_excluded_loggers: diff --git a/tests/environment b/tests/environment index a7dd02716..265a954c1 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit a7dd027166b8df1980881a94ba8473065497701d +Subproject commit 265a954c13ab30188adbf42a9e63d2bb7b9969ab diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index 0e7c63cc4..51e2f0703 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -14,8 +14,14 @@ import logging import unittest +from unittest.mock import patch import mock +from google.cloud.logging_v2.handlers._monitored_resources import ( + _FUNCTION_ENV_VARS, + _GAE_ENV_VARS, +) + class TestCloudLoggingHandler(unittest.TestCase): @@ -165,6 +171,49 @@ def test_setup_logging_excludes(self): self.assertNotIn(handler, excluded_logger.handlers) self.assertFalse(excluded_logger.propagate) + @patch.dict("os.environ", {envar: "1" for envar in _FUNCTION_ENV_VARS}) + def test_remove_handlers_gcf(self): + logger = logging.getLogger() + # add fake handler + added_handler = logging.StreamHandler() + logger.addHandler(added_handler) + + handler = _Handler(logging.INFO) + self._call_fut(handler) + self.assertNotIn(added_handler, logger.handlers) + # handler should be removed from logger + self.assertEqual(len(logger.handlers), 1) + + @patch.dict("os.environ", {envar: "1" for envar in _GAE_ENV_VARS}) + def test_remove_handlers_gae(self): + logger = logging.getLogger() + # add fake handler + added_handler = logging.StreamHandler() + logger.addHandler(added_handler) + + handler = _Handler(logging.INFO) + self._call_fut(handler) + self.assertNotIn(added_handler, logger.handlers) + # handler should be removed from logger + self.assertEqual(len(logger.handlers), 1) + + def test_keep_handlers_others(self): + # mock non-cloud environment + patch = mock.patch( + "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", + return_value=None, + ) + with patch: + # add fake handler + added_handler = logging.StreamHandler() + logger = logging.getLogger() + logger.addHandler(added_handler) + + handler = _Handler(logging.INFO) + self._call_fut(handler) + # added handler should remain in logger + self.assertIn(added_handler, logger.handlers) + def setUp(self): self._handlers_cache = logging.getLogger().handlers[:] From ac961f5cd8d0277157aa34b5204953224f677baf Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 15 Mar 2021 09:53:37 -0700 Subject: [PATCH 31/35] chore: rennovate bot pulls in submodule changes (#218) --- renovate.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 4fa949311..2b581d170 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,8 @@ { "extends": [ "config:base", ":preserveSemverRanges" - ] + ], + "git-submodules": { + "enabled": true + } } From f03197d23571bc6410aca457e2497721c0291c38 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 15 Mar 2021 17:54:00 +0100 Subject: [PATCH 32/35] chore(deps): update precommit hook pycqa/flake8 to v3.9.0 (#219) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9024b15d..32302e488 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,6 @@ repos: hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 From 94b4b9411d5791e2ade2d27eeacc62658b9bdeb0 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 15 Mar 2021 10:00:11 -0700 Subject: [PATCH 33/35] chore: Re-generated to pick up changes from self (#217) --- logging-v2-py.tar.gz | 0 synth.metadata | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 logging-v2-py.tar.gz diff --git a/logging-v2-py.tar.gz b/logging-v2-py.tar.gz deleted file mode 100644 index e69de29bb..000000000 diff --git a/synth.metadata b/synth.metadata index 611106822..5656e5bc6 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-logging.git", - "sha": "0f90a79d165314d261413cc369408e15f711129f" + "sha": "42bda27dd3eee2123cbe5b21dd75a587af7a910b" } }, { @@ -132,7 +132,6 @@ "google/cloud/logging_v2/types/logging.py", "google/cloud/logging_v2/types/logging_config.py", "google/cloud/logging_v2/types/logging_metrics.py", - "logging-v2-py.tar.gz", "mypy.ini", "noxfile.py", "renovate.json", From c813dee09f783ee06c5ec6832b48a10dbd03baa3 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 15 Mar 2021 10:21:40 -0700 Subject: [PATCH 34/35] test: check severity in all environments (#216) --- tests/environment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/environment b/tests/environment index 265a954c1..eb60e8239 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit 265a954c13ab30188adbf42a9e63d2bb7b9969ab +Subproject commit eb60e823924dabaaea62e2ec0b8243eb868c1826 From 7a4b0543f9bc9f17ef7de071fb4ec6f2da642e45 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 15 Mar 2021 10:46:55 -0700 Subject: [PATCH 35/35] chore: release 2.3.0 (#205) --- CHANGELOG.md | 19 +++++++++++++++++++ setup.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b89976db1..02a416d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ [1]: https://pypi.org/project/google-cloud-logging/#history +## [2.3.0](https://www.github.com/googleapis/python-logging/compare/v2.2.0...v2.3.0) (2021-03-15) + + +### Features + +* Add json setting to allow unicodes to show in log instead of ascii ch… ([#193](https://www.github.com/googleapis/python-logging/issues/193)) ([e8c8e30](https://www.github.com/googleapis/python-logging/commit/e8c8e30fc4f618273dec1415c752eed203c75b67)) +* detect monitored resources on all GCP environments ([#200](https://www.github.com/googleapis/python-logging/issues/200)) ([4eda681](https://www.github.com/googleapis/python-logging/commit/4eda6813d19df8a119f1dcd47ff79389310d4a6f)) + + +### Bug Fixes + +* logger uses default resource ([#207](https://www.github.com/googleapis/python-logging/issues/207)) ([0f90a79](https://www.github.com/googleapis/python-logging/commit/0f90a79d165314d261413cc369408e15f711129f)) +* no duplicate logs on GCF or GAE ([#209](https://www.github.com/googleapis/python-logging/issues/209)) ([37e6c8e](https://www.github.com/googleapis/python-logging/commit/37e6c8e90775ddc2fc454f5cb13cab04231c2222)) + + +### Documentation + +* add python std_logging to sample browser ([#173](https://www.github.com/googleapis/python-logging/issues/173)) ([7cc7275](https://www.github.com/googleapis/python-logging/commit/7cc727598c33e7e264ddbeef0a2604a3c215b260)) + ## [2.2.0](https://www.github.com/googleapis/python-logging/compare/v2.1.1...v2.2.0) (2021-01-27) diff --git a/setup.py b/setup.py index 8ede9877e..8885090fe 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ name = "google-cloud-logging" description = "Stackdriver Logging API client library" -version = "2.2.0" +version = "2.3.0" # Should be one of: # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta'