From 5e35298c65b1cc45938c464ca04fe7c5d2552d2d Mon Sep 17 00:00:00 2001
From: "release-please[bot]"
<55107282+release-please[bot]@users.noreply.github.com>
Date: Thu, 6 Aug 2026 14:45:22 -0700
Subject: [PATCH 1/6] chore: release main (#9093)
:robot: I have created a release *beep* *boop*
---
pack-n-play: 5.0.1
##
[5.0.1](https://github.com/googleapis/google-cloud-node/compare/pack-n-play-v5.0.0...pack-n-play-v5.0.1)
(2026-08-05)
### Bug Fixes
* **core:** Discovery url handling, error code review helper, and
pack-n-play test timeouts
([#9061](https://github.com/googleapis/google-cloud-node/issues/9061))
([340364f](https://github.com/googleapis/google-cloud-node/commit/340364f8fab67adaf4e684fb3241de499394bf6b))
gcp-metadata: 9.0.1
##
[9.0.1](https://github.com/googleapis/google-cloud-node/compare/gcp-metadata-v9.0.0...gcp-metadata-v9.0.1)
(2026-08-05)
### Bug Fixes
* **core:** Discovery url handling, error code review helper, and
pack-n-play test timeouts
([#9061](https://github.com/googleapis/google-cloud-node/issues/9061))
([340364f](https://github.com/googleapis/google-cloud-node/commit/340364f8fab67adaf4e684fb3241de499394bf6b))
googleapis-common: 9.0.1
##
[9.0.1](https://github.com/googleapis/google-cloud-node/compare/googleapis-common-v9.0.0...googleapis-common-v9.0.1)
(2026-08-05)
### Bug Fixes
* **core:** Discovery url handling, error code review helper, and
pack-n-play test timeouts
([#9061](https://github.com/googleapis/google-cloud-node/issues/9061))
([340364f](https://github.com/googleapis/google-cloud-node/commit/340364f8fab67adaf4e684fb3241de499394bf6b))
bigquery: 9.0.1
##
[9.0.1](https://github.com/googleapis/google-cloud-node/compare/bigquery-v9.0.0...bigquery-v9.0.1)
(2026-08-05)
### Bug Fixes
* **bigquery:** Remove owlbot configs
([#8936](https://github.com/googleapis/google-cloud-node/issues/8936))
([7f8e23c](https://github.com/googleapis/google-cloud-node/commit/7f8e23c9652e34869c2fafe54f63cc63ac9acaa8))
bigtable: 7.1.0
##
[7.1.0](https://github.com/googleapis/google-cloud-node/compare/bigtable-v7.0.0...bigtable-v7.1.0)
(2026-08-05)
### Features
* **bigtable:** Split gapic and proto usage from the handwritten library
into bigtable-api
([#8934](https://github.com/googleapis/google-cloud-node/issues/8934))
([f63f914](https://github.com/googleapis/google-cloud-node/commit/f63f914e69106f581448837f5ee212ab71f85775))
databasecenter: 0.4.1
##
[0.4.1](https://github.com/googleapis/google-cloud-node/compare/databasecenter-v0.4.0...databasecenter-v0.4.1)
(2026-08-05)
### Bug Fixes
* Upgrade types sinon for databasecenter
([#9098](https://github.com/googleapis/google-cloud-node/issues/9098))
([23aee11](https://github.com/googleapis/google-cloud-node/commit/23aee114a947a29f31977aab8de9c1c7ecd62188))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
---
.release-please-manifest.json | 12 +-
changelog.json | 109 +++++++++++++++++-
core/dev-packages/pack-n-play/CHANGELOG.md | 7 ++
core/dev-packages/pack-n-play/package.json | 2 +-
core/packages/gcp-metadata/CHANGELOG.md | 7 ++
core/packages/gcp-metadata/package.json | 2 +-
.../gcp-metadata/samples/package.json | 2 +-
.../nodejs-googleapis-common/CHANGELOG.md | 7 ++
.../nodejs-googleapis-common/package.json | 2 +-
.../samples/package.json | 2 +-
handwritten/bigquery/CHANGELOG.md | 7 ++
handwritten/bigquery/package.json | 2 +-
handwritten/bigtable/CHANGELOG.md | 7 ++
handwritten/bigtable/package.json | 2 +-
librarian.yaml | 2 +-
.../google-cloud-databasecenter/CHANGELOG.md | 7 ++
.../google-cloud-databasecenter/package.json | 2 +-
17 files changed, 165 insertions(+), 16 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 1986fffc0f38..48aef931009f 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -2,15 +2,15 @@
"core/common": "8.0.0",
"core/dev-packages/jsdoc-fresh": "6.0.0",
"core/dev-packages/jsdoc-region-tag": "5.0.0",
- "core/dev-packages/pack-n-play": "5.0.0",
+ "core/dev-packages/pack-n-play": "5.0.1",
"core/generator/gapic-generator-typescript": "5.0.0",
"core/packages/gapic-node-processing": "0.2.0",
"core/packages/gax": "6.0.0",
"core/packages/gaxios": "7.3.0",
- "core/packages/gcp-metadata": "9.0.0",
+ "core/packages/gcp-metadata": "9.0.1",
"core/packages/google-auth-library-nodejs": "11.0.0",
"core/packages/logging-utils": "2.0.0",
- "core/packages/nodejs-googleapis-common": "9.0.0",
+ "core/packages/nodejs-googleapis-common": "9.0.1",
"core/packages/nodejs-proto-files": "6.0.0",
"core/packages/proto3-json-serializer-nodejs": "4.0.0",
"core/packages/retry-request": "9.0.0",
@@ -21,9 +21,9 @@
"core/precise-date": "6.0.0",
"core/projectify": "6.0.0",
"core/promisify": "6.0.0",
- "handwritten/bigquery": "9.0.0",
+ "handwritten/bigquery": "9.0.1",
"handwritten/bigquery-storage": "6.0.0",
- "handwritten/bigtable": "7.0.0",
+ "handwritten/bigtable": "7.1.0",
"handwritten/cloud-profiler": "8.0.0",
"handwritten/datastore": "11.0.0",
"handwritten/error-reporting": "4.0.0",
@@ -104,7 +104,7 @@
"packages/google-cloud-connectors": "0.6.0",
"packages/google-cloud-contactcenterinsights": "5.0.0",
"packages/google-cloud-contentwarehouse": "3.0.0",
- "packages/google-cloud-databasecenter": "0.4.0",
+ "packages/google-cloud-databasecenter": "0.4.1",
"packages/google-cloud-datacatalog": "6.0.0",
"packages/google-cloud-datacatalog-lineage": "3.0.0",
"packages/google-cloud-datacatalog-lineage-configmanagement": "0.2.0",
diff --git a/changelog.json b/changelog.json
index f4dc6b25538c..a9e0bf8919e6 100644
--- a/changelog.json
+++ b/changelog.json
@@ -1,6 +1,113 @@
{
"repository": "googleapis/google-cloud-node",
"entries": [
+ {
+ "changes": [
+ {
+ "type": "fix",
+ "sha": "23aee114a947a29f31977aab8de9c1c7ecd62188",
+ "message": "Upgrade types sinon for databasecenter",
+ "issues": [
+ "9098"
+ ]
+ }
+ ],
+ "version": "0.4.1",
+ "language": "JAVASCRIPT",
+ "artifactName": "@google-cloud/databasecenter",
+ "id": "c0b55b44-2b73-457b-9e25-8d86ece8b512",
+ "createTime": "2026-08-05T21:09:31.314Z"
+ },
+ {
+ "changes": [
+ {
+ "type": "feat",
+ "sha": "f63f914e69106f581448837f5ee212ab71f85775",
+ "message": "Split gapic and proto usage from the handwritten library into bigtable-api",
+ "issues": [
+ "8934"
+ ],
+ "scope": "bigtable"
+ }
+ ],
+ "version": "7.1.0",
+ "language": "JAVASCRIPT",
+ "artifactName": "@google-cloud/bigtable",
+ "id": "dc784ab8-295a-4e02-8261-f7935abb2788",
+ "createTime": "2026-08-05T21:09:31.294Z"
+ },
+ {
+ "changes": [
+ {
+ "type": "fix",
+ "sha": "7f8e23c9652e34869c2fafe54f63cc63ac9acaa8",
+ "message": "Remove owlbot configs",
+ "issues": [
+ "8936"
+ ],
+ "scope": "bigquery"
+ }
+ ],
+ "version": "9.0.1",
+ "language": "JAVASCRIPT",
+ "artifactName": "@google-cloud/bigquery",
+ "id": "de1dae99-d138-451f-a0db-e716e0003958",
+ "createTime": "2026-08-05T21:09:31.273Z"
+ },
+ {
+ "changes": [
+ {
+ "type": "fix",
+ "sha": "340364f8fab67adaf4e684fb3241de499394bf6b",
+ "message": "Discovery url handling, error code review helper, and pack-n-play test timeouts",
+ "issues": [
+ "9061"
+ ],
+ "scope": "core"
+ }
+ ],
+ "version": "9.0.1",
+ "language": "JAVASCRIPT",
+ "artifactName": "googleapis-common",
+ "id": "2d37b8dd-1417-4f37-ae57-18b166ea395a",
+ "createTime": "2026-08-05T21:09:31.248Z"
+ },
+ {
+ "changes": [
+ {
+ "type": "fix",
+ "sha": "340364f8fab67adaf4e684fb3241de499394bf6b",
+ "message": "Discovery url handling, error code review helper, and pack-n-play test timeouts",
+ "issues": [
+ "9061"
+ ],
+ "scope": "core"
+ }
+ ],
+ "version": "9.0.1",
+ "language": "JAVASCRIPT",
+ "artifactName": "gcp-metadata",
+ "id": "9eda0e2d-73dd-410e-a223-df0621e697d6",
+ "createTime": "2026-08-05T21:09:31.228Z"
+ },
+ {
+ "changes": [
+ {
+ "type": "fix",
+ "sha": "340364f8fab67adaf4e684fb3241de499394bf6b",
+ "message": "Discovery url handling, error code review helper, and pack-n-play test timeouts",
+ "issues": [
+ "9061"
+ ],
+ "scope": "core"
+ }
+ ],
+ "version": "5.0.1",
+ "language": "JAVASCRIPT",
+ "artifactName": "pack-n-play",
+ "id": "80b6e996-31bf-482f-8d5c-02220564552f",
+ "createTime": "2026-08-05T21:09:31.200Z"
+ },
{
"changes": [
{
@@ -88682,5 +88789,5 @@
"createTime": "2023-01-28T04:18:24.718Z"
}
],
- "updateTime": "2026-08-04T23:49:33.509Z"
+ "updateTime": "2026-08-05T21:09:31.314Z"
}
\ No newline at end of file
diff --git a/core/dev-packages/pack-n-play/CHANGELOG.md b/core/dev-packages/pack-n-play/CHANGELOG.md
index cbfa24ef4098..ff9c3f04cd3c 100644
--- a/core/dev-packages/pack-n-play/CHANGELOG.md
+++ b/core/dev-packages/pack-n-play/CHANGELOG.md
@@ -1,6 +1,13 @@
# Changelog
+## [5.0.1](https://github.com/googleapis/google-cloud-node/compare/pack-n-play-v5.0.0...pack-n-play-v5.0.1) (2026-08-05)
+
+
+### Bug Fixes
+
+* **core:** Discovery url handling, error code review helper, and pack-n-play test timeouts ([#9061](https://github.com/googleapis/google-cloud-node/issues/9061)) ([340364f](https://github.com/googleapis/google-cloud-node/commit/340364f8fab67adaf4e684fb3241de499394bf6b))
+
## [5.0.0](https://github.com/googleapis/google-cloud-node/compare/pack-n-play-v4.2.3...pack-n-play-v5.0.0) (2026-07-29)
diff --git a/core/dev-packages/pack-n-play/package.json b/core/dev-packages/pack-n-play/package.json
index ad6dc1a6938b..8c52b49721cd 100644
--- a/core/dev-packages/pack-n-play/package.json
+++ b/core/dev-packages/pack-n-play/package.json
@@ -1,7 +1,7 @@
{
"name": "pack-n-play",
"description": "Ensure your library installs and compiles",
- "version": "5.0.0",
+ "version": "5.0.1",
"author": "Google LLC",
"license": "Apache-2.0",
"repository": {
diff --git a/core/packages/gcp-metadata/CHANGELOG.md b/core/packages/gcp-metadata/CHANGELOG.md
index ff0063d2f2a9..4ceb65011936 100644
--- a/core/packages/gcp-metadata/CHANGELOG.md
+++ b/core/packages/gcp-metadata/CHANGELOG.md
@@ -5,6 +5,13 @@
[1]: https://www.npmjs.com/package/gcp-metadata?activeTab=versions
+## [9.0.1](https://github.com/googleapis/google-cloud-node/compare/gcp-metadata-v9.0.0...gcp-metadata-v9.0.1) (2026-08-05)
+
+
+### Bug Fixes
+
+* **core:** Discovery url handling, error code review helper, and pack-n-play test timeouts ([#9061](https://github.com/googleapis/google-cloud-node/issues/9061)) ([340364f](https://github.com/googleapis/google-cloud-node/commit/340364f8fab67adaf4e684fb3241de499394bf6b))
+
## [9.0.0](https://github.com/googleapis/google-cloud-node/compare/gcp-metadata-v8.1.4...gcp-metadata-v9.0.0) (2026-07-29)
diff --git a/core/packages/gcp-metadata/package.json b/core/packages/gcp-metadata/package.json
index 8b26fc8f3ae1..78eb4d77cc95 100644
--- a/core/packages/gcp-metadata/package.json
+++ b/core/packages/gcp-metadata/package.json
@@ -1,6 +1,6 @@
{
"name": "gcp-metadata",
- "version": "9.0.0",
+ "version": "9.0.1",
"description": "Get the metadata from a Google Cloud Platform environment",
"repository": {
"type": "git",
diff --git a/core/packages/gcp-metadata/samples/package.json b/core/packages/gcp-metadata/samples/package.json
index 5790a1b71927..a7a0992b436f 100644
--- a/core/packages/gcp-metadata/samples/package.json
+++ b/core/packages/gcp-metadata/samples/package.json
@@ -14,7 +14,7 @@
"test": "mocha"
},
"dependencies": {
- "gcp-metadata": "^9.0.0"
+ "gcp-metadata": "^9.0.1"
},
"devDependencies": {
"chai": "^4.2.0",
diff --git a/core/packages/nodejs-googleapis-common/CHANGELOG.md b/core/packages/nodejs-googleapis-common/CHANGELOG.md
index 511e86585498..cf43f81713c0 100644
--- a/core/packages/nodejs-googleapis-common/CHANGELOG.md
+++ b/core/packages/nodejs-googleapis-common/CHANGELOG.md
@@ -5,6 +5,13 @@
[1]: https://www.npmjs.com/package/nodejs-googleapis-common?activeTab=versions
+## [9.0.1](https://github.com/googleapis/google-cloud-node/compare/googleapis-common-v9.0.0...googleapis-common-v9.0.1) (2026-08-05)
+
+
+### Bug Fixes
+
+* **core:** Discovery url handling, error code review helper, and pack-n-play test timeouts ([#9061](https://github.com/googleapis/google-cloud-node/issues/9061)) ([340364f](https://github.com/googleapis/google-cloud-node/commit/340364f8fab67adaf4e684fb3241de499394bf6b))
+
## [9.0.0](https://github.com/googleapis/google-cloud-node/compare/googleapis-common-v8.0.3...googleapis-common-v9.0.0) (2026-07-29)
diff --git a/core/packages/nodejs-googleapis-common/package.json b/core/packages/nodejs-googleapis-common/package.json
index 082b61bf7942..2ad882e902af 100644
--- a/core/packages/nodejs-googleapis-common/package.json
+++ b/core/packages/nodejs-googleapis-common/package.json
@@ -1,6 +1,6 @@
{
"name": "googleapis-common",
- "version": "9.0.0",
+ "version": "9.0.1",
"description": "A common tooling library used by the googleapis npm module. You probably don't want to use this directly.",
"repository": {
"type": "git",
diff --git a/core/packages/nodejs-googleapis-common/samples/package.json b/core/packages/nodejs-googleapis-common/samples/package.json
index 192850261c50..07446f01ac9f 100644
--- a/core/packages/nodejs-googleapis-common/samples/package.json
+++ b/core/packages/nodejs-googleapis-common/samples/package.json
@@ -15,7 +15,7 @@
"test": "echo \"There are no sample tests 👻\""
},
"dependencies": {
- "googleapis-common": "^9.0.0"
+ "googleapis-common": "^9.0.1"
},
"devDependencies": {
"mocha": "^8.0.0"
diff --git a/handwritten/bigquery/CHANGELOG.md b/handwritten/bigquery/CHANGELOG.md
index af977317129a..12b796fd7bec 100644
--- a/handwritten/bigquery/CHANGELOG.md
+++ b/handwritten/bigquery/CHANGELOG.md
@@ -5,6 +5,13 @@
[1]: https://www.npmjs.com/package/@google-cloud/bigquery?activeTab=versions
+## [9.0.1](https://github.com/googleapis/google-cloud-node/compare/bigquery-v9.0.0...bigquery-v9.0.1) (2026-08-05)
+
+
+### Bug Fixes
+
+* **bigquery:** Remove owlbot configs ([#8936](https://github.com/googleapis/google-cloud-node/issues/8936)) ([7f8e23c](https://github.com/googleapis/google-cloud-node/commit/7f8e23c9652e34869c2fafe54f63cc63ac9acaa8))
+
## [9.0.0](https://github.com/googleapis/google-cloud-node/compare/bigquery-v8.3.1...bigquery-v9.0.0) (2026-08-04)
diff --git a/handwritten/bigquery/package.json b/handwritten/bigquery/package.json
index b0d97aa060c1..8c08afd9d8d2 100644
--- a/handwritten/bigquery/package.json
+++ b/handwritten/bigquery/package.json
@@ -1,7 +1,7 @@
{
"name": "@google-cloud/bigquery",
"description": "Google BigQuery Client Library for Node.js",
- "version": "9.0.0",
+ "version": "9.0.1",
"license": "Apache-2.0",
"author": "Google LLC",
"engines": {
diff --git a/handwritten/bigtable/CHANGELOG.md b/handwritten/bigtable/CHANGELOG.md
index 2acbd3b45145..003c12d67c79 100644
--- a/handwritten/bigtable/CHANGELOG.md
+++ b/handwritten/bigtable/CHANGELOG.md
@@ -5,6 +5,13 @@
[1]: https://www.npmjs.com/package/@google-cloud/bigtable?activeTab=versions
+## [7.1.0](https://github.com/googleapis/google-cloud-node/compare/bigtable-v7.0.0...bigtable-v7.1.0) (2026-08-05)
+
+
+### Features
+
+* **bigtable:** Split gapic and proto usage from the handwritten library into bigtable-api ([#8934](https://github.com/googleapis/google-cloud-node/issues/8934)) ([f63f914](https://github.com/googleapis/google-cloud-node/commit/f63f914e69106f581448837f5ee212ab71f85775))
+
## [7.0.0](https://github.com/googleapis/google-cloud-node/compare/bigtable-v6.5.1...bigtable-v7.0.0) (2026-08-04)
diff --git a/handwritten/bigtable/package.json b/handwritten/bigtable/package.json
index 85d6d08be38e..7c5baeebd273 100644
--- a/handwritten/bigtable/package.json
+++ b/handwritten/bigtable/package.json
@@ -1,6 +1,6 @@
{
"name": "@google-cloud/bigtable",
- "version": "7.0.0",
+ "version": "7.1.0",
"description": "Cloud Bigtable Client Library for Node.js",
"keywords": [
"bigtable",
diff --git a/librarian.yaml b/librarian.yaml
index d4d29c13e820..f55632ab1c08 100644
--- a/librarian.yaml
+++ b/librarian.yaml
@@ -743,7 +743,7 @@ libraries:
nodejs:
default_version: v1
- name: google-cloud-databasecenter
- version: 0.4.0
+ version: 0.4.1
apis:
- path: google/cloud/databasecenter/v1beta
copyright_year: "2026"
diff --git a/packages/google-cloud-databasecenter/CHANGELOG.md b/packages/google-cloud-databasecenter/CHANGELOG.md
index 1a55e239d67a..366f8547a3bc 100644
--- a/packages/google-cloud-databasecenter/CHANGELOG.md
+++ b/packages/google-cloud-databasecenter/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [0.4.1](https://github.com/googleapis/google-cloud-node/compare/databasecenter-v0.4.0...databasecenter-v0.4.1) (2026-08-05)
+
+
+### Bug Fixes
+
+* Upgrade types sinon for databasecenter ([#9098](https://github.com/googleapis/google-cloud-node/issues/9098)) ([23aee11](https://github.com/googleapis/google-cloud-node/commit/23aee114a947a29f31977aab8de9c1c7ecd62188))
+
## [0.4.0](https://github.com/googleapis/google-cloud-node/compare/databasecenter-v0.3.0...databasecenter-v0.4.0) (2026-08-04)
diff --git a/packages/google-cloud-databasecenter/package.json b/packages/google-cloud-databasecenter/package.json
index 61bfb2f90cde..fd525383d2c5 100644
--- a/packages/google-cloud-databasecenter/package.json
+++ b/packages/google-cloud-databasecenter/package.json
@@ -1,6 +1,6 @@
{
"name": "@google-cloud/databasecenter",
- "version": "0.4.0",
+ "version": "0.4.1",
"description": "Database Center API client for Node.js",
"repository": {
"type": "git",
From 6b6a2396c2f1f072d0d3f3fc27f63913a3ce9a79 Mon Sep 17 00:00:00 2001
From: Santiago Quiroga <22756465+quirogas@users.noreply.github.com>
Date: Thu, 6 Aug 2026 14:51:56 -0700
Subject: [PATCH 2/6] ci(workflows): add node and pnpm v7 setup to generation
check (#9099)
Add Node.js 22 and pnpm v7 runtime setup steps to
`.github/workflows/generation_check.yaml` prior to tool installation and
generation verification.
On default GitHub hosted CI runners (`ubuntu-latest`), `pnpm` is not
pre-installed on the system `$PATH`, so executing `librarian install`
immediately fails due to `exec: "pnpm": executable file not found in
$PATH`.
---
.github/workflows/generation_check.yaml | 75 +++++++++++++++++++++++++
.github/workflows/librarian_tidy.yml | 47 ++++++++++++++++
2 files changed, 122 insertions(+)
create mode 100644 .github/workflows/generation_check.yaml
create mode 100644 .github/workflows/librarian_tidy.yml
diff --git a/.github/workflows/generation_check.yaml b/.github/workflows/generation_check.yaml
new file mode 100644
index 000000000000..4e8c12451f2f
--- /dev/null
+++ b/.github/workflows/generation_check.yaml
@@ -0,0 +1,75 @@
+---
+name: generation-check
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ # Allows manual triggering for debugging purpose.
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read # Read repository contents
+
+jobs:
+ regeneration:
+ name: Regenerate and verify clean git tree
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read # Read repository contents
+ issues: write # Required for creating issue on main branch failure
+ steps:
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+ - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
+ id: changes
+ with:
+ filters: |
+ librarian:
+ - 'librarian.yaml'
+ # Version of librarian is pulled from librarian.yaml,
+ # action is pinned to commit SHA to satisfy blanket security policy.
+ - uses: googleapis/librarian@35997441eafc2b02716804f9baba1e3f04f8a44f # v0.31.1
+ if: steps.changes.outputs.librarian == 'true' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
+ - name: Use Node.js 22
+ if: steps.changes.outputs.librarian == 'true' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
+ with:
+ node-version: 22
+ - name: Setup pnpm
+ if: steps.changes.outputs.librarian == 'true' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
+ uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
+ with:
+ version: ^7.0.0
+ - name: Install tools
+ if: steps.changes.outputs.librarian == 'true' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
+ run: librarian install
+ - name: Regenerate
+ if: steps.changes.outputs.librarian == 'true' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
+ run: |
+ librarian generate --all
+ if [ -n "$(git status --porcelain)" ]; then
+ git status
+ echo "==================== GIT DIFF ===================="
+ git diff
+ echo "=================================================="
+ echo "Regeneration failed. Please run 'librarian generate --all' to update the generated files."
+ exit 1
+ fi
+ - name: Create issue if previous step fails
+ if: ${{ failure() && github.ref == 'refs/heads/main' }}
+ uses: googleapis/librarian/.github/actions/create-issue-on-failure@35997441eafc2b02716804f9baba1e3f04f8a44f # v0.31.1
+ with:
+ title: "Librarian generate diff check failed on main branch"
+ body: |
+ The librarian generate diff check failed on main branch.
+
+ To keep the `main` branch healthy, please consider **reverting the triggering change** first. Once the revert is merged, you can investigate the failure and submit a new PR with the fix.
+
+ Please check the logs: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
diff --git a/.github/workflows/librarian_tidy.yml b/.github/workflows/librarian_tidy.yml
new file mode 100644
index 000000000000..e830d1d3f187
--- /dev/null
+++ b/.github/workflows/librarian_tidy.yml
@@ -0,0 +1,47 @@
+name: librarian tidy
+
+on:
+ pull_request:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ tidy-check:
+ name: Verify librarian.yaml is tidy
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
+ - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
+ id: changes
+ with:
+ filters: |
+ librarian:
+ - 'librarian.yaml'
+ # Version of librarian is pulled from librarian.yaml,
+ # action is pinned to commit SHA to satisfy blanket security policy.
+ - uses: googleapis/librarian@35997441eafc2b02716804f9baba1e3f04f8a44f # v0.31.1
+
+ - name: Run librarian tidy
+ if: steps.changes.outputs.librarian == 'true'
+ run: librarian tidy
+
+ - name: Check for diff
+ if: steps.changes.outputs.librarian == 'true'
+ run: |
+ if ! git diff --exit-code; then
+ V=$(librarian config get version 2>/dev/null || echo "latest")
+ echo "librarian.yaml is not tidy. Please run:"
+ echo ""
+ echo " librarian tidy"
+ echo ""
+ echo "to tidy librarian.yaml and commit the changes."
+ exit 1
+ fi
From 4b1f5a282c2cf9b3e48145a713507c462429b7be Mon Sep 17 00:00:00 2001
From: Thiyagu K
Date: Mon, 29 Jun 2026 11:20:26 +0000
Subject: [PATCH 3/6] feat(storage): allow setting file encryption key to null
to disable CSEK and fix copy behavior for non-encrypted destinations
---
handwritten/storage/src/file.ts | 51 +++++++++---
handwritten/storage/system-test/storage.ts | 18 +++++
handwritten/storage/test/file.ts | 93 ++++++++++++++++++++++
3 files changed, 150 insertions(+), 12 deletions(-)
diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts
index 786998c5f4e4..40987691551e 100644
--- a/handwritten/storage/src/file.ts
+++ b/handwritten/storage/src/file.ts
@@ -390,7 +390,7 @@ export type RenameCallback = MoveCallback;
export type RotateEncryptionKeyOptions = string | Buffer | EncryptionKeyOptions;
export interface EncryptionKeyOptions {
- encryptionKey?: string | Buffer;
+ encryptionKey?: string | Buffer | null;
kmsKeyName?: string;
preconditionOpts?: PreconditionOptions;
}
@@ -439,7 +439,7 @@ const COMPRESSIBLE_MIME_REGEX = new RegExp(
export interface FileOptions {
crc32cGenerator?: CRC32CValidatorGenerator;
- encryptionKey?: string | Buffer;
+ encryptionKey?: string | Buffer | null;
generation?: number | string;
restoreToken?: string;
kmsKeyName?: string;
@@ -498,7 +498,7 @@ export type DownloadCallback = (
export interface DownloadOptions extends CreateReadStreamOptions {
destination?: string;
- encryptionKey?: string | Buffer;
+ encryptionKey?: string | Buffer | null;
}
interface CopyQuery {
@@ -673,7 +673,7 @@ class File extends ServiceObject {
restoreToken?: string;
parent!: Bucket;
- private encryptionKey?: string | Buffer;
+ private encryptionKey?: string | Buffer | null;
private encryptionKeyBase64?: string;
private encryptionKeyHash?: string;
private encryptionKeyInterceptor?: Interceptor;
@@ -1175,7 +1175,7 @@ class File extends ServiceObject {
this.name = name;
- if (options.encryptionKey) {
+ if (options.encryptionKey !== undefined) {
this.setEncryptionKey(options.encryptionKey);
}
@@ -1452,14 +1452,26 @@ class File extends ServiceObject {
const headers: {[index: string]: string | undefined} = {};
- if (this.encryptionKey !== undefined) {
+ if (this.encryptionKey !== undefined && this.encryptionKey !== null) {
headers['x-goog-copy-source-encryption-algorithm'] = 'AES256';
headers['x-goog-copy-source-encryption-key'] = this.encryptionKeyBase64;
headers['x-goog-copy-source-encryption-key-sha256'] =
this.encryptionKeyHash;
}
- if (newFile.encryptionKey !== undefined) {
+ let copiedKey = false;
+ if (
+ this.encryptionKey !== undefined &&
+ this.encryptionKey !== null &&
+ newFile.encryptionKey === undefined
+ ) {
+ newFile.encryptionKey = this.encryptionKey;
+ newFile.encryptionKeyBase64 = this.encryptionKeyBase64;
+ newFile.encryptionKeyHash = this.encryptionKeyHash;
+ copiedKey = true;
+ }
+
+ if (newFile.encryptionKey !== undefined && !copiedKey) {
this.setEncryptionKey(newFile.encryptionKey!);
} else if (options.destinationKmsKeyName !== undefined) {
query.destinationKmsKeyName = options.destinationKmsKeyName;
@@ -1937,7 +1949,7 @@ class File extends ServiceObject {
),
file: this.name,
generation: this.generation,
- key: this.encryptionKey,
+ key: this.encryptionKey === null ? undefined : this.encryptionKey,
kmsKeyName: this.kmsKeyName,
metadata: options.metadata,
offset: options.offset,
@@ -2465,7 +2477,7 @@ class File extends ServiceObject {
const destination = options.destination;
delete options.destination;
- if (options.encryptionKey) {
+ if (options.encryptionKey !== undefined) {
this.setEncryptionKey(options.encryptionKey);
delete options.encryptionKey;
}
@@ -2555,8 +2567,23 @@ class File extends ServiceObject {
* region_tag:storage_download_encrypted_file
* Example of downloading an encrypted file:
*/
- setEncryptionKey(encryptionKey: string | Buffer) {
+ setEncryptionKey(encryptionKey: string | Buffer | null) {
+ if (this.encryptionKeyInterceptor) {
+ const index = this.interceptors.indexOf(this.encryptionKeyInterceptor);
+ if (index > -1) {
+ this.interceptors.splice(index, 1);
+ }
+ this.encryptionKeyInterceptor = undefined;
+ }
+
this.encryptionKey = encryptionKey;
+
+ if (encryptionKey === null || encryptionKey === undefined) {
+ this.encryptionKeyBase64 = undefined;
+ this.encryptionKeyHash = undefined;
+ return this;
+ }
+
this.encryptionKeyBase64 = Buffer.from(encryptionKey as string).toString(
'base64',
);
@@ -2577,7 +2604,7 @@ class File extends ServiceObject {
},
};
- this.interceptors.push(this.encryptionKeyInterceptor!);
+ this.interceptors.push(this.encryptionKeyInterceptor);
return this;
}
@@ -4536,7 +4563,7 @@ class File extends ServiceObject {
file: this.name,
generation: this.generation,
isPartialUpload: options.isPartialUpload,
- key: this.encryptionKey,
+ key: this.encryptionKey === null ? undefined : this.encryptionKey,
kmsKeyName: this.kmsKeyName,
metadata: options.metadata,
offset: options.offset,
diff --git a/handwritten/storage/system-test/storage.ts b/handwritten/storage/system-test/storage.ts
index cf8074d52671..d9351f703732 100644
--- a/handwritten/storage/system-test/storage.ts
+++ b/handwritten/storage/system-test/storage.ts
@@ -2793,6 +2793,24 @@ describe('storage', function () {
const [contents] = await file.download();
assert.strictEqual(contents.toString(), 'secret data');
});
+
+ it('should copy a CSEK-encrypted file to a standard non-CSEK destination when destination key is null', async () => {
+ const srcFile = bucket.file('encrypted-source');
+ srcFile.setEncryptionKey('a'.repeat(32));
+
+ await srcFile.save('csek data', { resumable: false });
+
+ const dstFile = bucket.file('non-csek-destination');
+ dstFile.setEncryptionKey(null);
+
+ await srcFile.copy(dstFile);
+
+ const [metadata] = await dstFile.getMetadata();
+ assert.strictEqual(metadata.customerEncryption, undefined);
+
+ const [contents] = await dstFile.download();
+ assert.strictEqual(contents.toString(), 'csek data');
+ });
});
describe.skip('kms keys', () => {
diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts
index d9f9185a16e8..3b8536c87296 100644
--- a/handwritten/storage/test/file.ts
+++ b/handwritten/storage/test/file.ts
@@ -626,6 +626,75 @@ describe('File', () => {
file.copy(newFile, assert.ifError);
});
+ it('should not copy encryption key or send destination headers when destination file has null encryption key', done => {
+ file.encryptionKey = 'sourceKey';
+ file.encryptionKeyBase64 = 'sourceKeyBase64';
+ file.encryptionKeyHash = 'sourceKeyHash';
+
+ const newFile = new File(BUCKET, 'new-file');
+ newFile.setEncryptionKey(null);
+
+ file.request = (reqOpts: DecorateRequestOptions) => {
+ assert.strictEqual(newFile.encryptionKey, null);
+ assert.strictEqual(newFile.encryptionKeyBase64, undefined);
+ assert.strictEqual(newFile.encryptionKeyHash, undefined);
+
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-copy-source-encryption-algorithm'],
+ 'AES256'
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-copy-source-encryption-key'],
+ 'sourceKeyBase64'
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-copy-source-encryption-key-sha256'],
+ 'sourceKeyHash'
+ );
+
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-algorithm'],
+ undefined
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-key'],
+ undefined
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-key-sha256'],
+ undefined
+ );
+
+ assert.strictEqual(file.encryptionKeyInterceptor, undefined);
+
+ done();
+ };
+
+ file.copy(newFile, assert.ifError);
+ });
+
+ it('should copy the source key to the destination file object if destination key is undefined', done => {
+ file.encryptionKey = 'sourceKey';
+ file.encryptionKeyBase64 = 'sourceKeyBase64';
+ file.encryptionKeyHash = 'sourceKeyHash';
+
+ const newFile = new File(BUCKET, 'new-file');
+
+ file.request = (reqOpts: DecorateRequestOptions) => {
+ assert.strictEqual(newFile.encryptionKey, file.encryptionKey);
+ assert.strictEqual(newFile.encryptionKeyBase64, file.encryptionKeyBase64);
+ assert.strictEqual(newFile.encryptionKeyHash, file.encryptionKeyHash);
+
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-copy-source-encryption-key'],
+ file.encryptionKeyBase64
+ );
+ done();
+ };
+
+ file.copy(newFile, assert.ifError);
+ });
+
it('should set destination KMS key name', done => {
const newFile = new File(BUCKET, 'new-file');
newFile.kmsKeyName = 'kms-key-name';
@@ -5459,6 +5528,30 @@ describe('File', () => {
done();
});
+
+ describe('null key', () => {
+ beforeEach(() => {
+ file.setEncryptionKey(KEY);
+ file.setEncryptionKey(null);
+ });
+
+ it('should localize the key to null', () => {
+ assert.strictEqual(file.encryptionKey, null);
+ });
+
+ it('should clear the base64 key', () => {
+ assert.strictEqual(file.encryptionKeyBase64, undefined);
+ });
+
+ it('should clear the hash', () => {
+ assert.strictEqual(file.encryptionKeyHash, undefined);
+ });
+
+ it('should remove the request interceptor', () => {
+ assert.strictEqual(file.encryptionKeyInterceptor, undefined);
+ assert.strictEqual(file.interceptors.length, 0);
+ });
+ });
});
describe('startResumableUpload_', () => {
From 73760335f9ec1ec25abe444a11a51f1d2f141e01 Mon Sep 17 00:00:00 2001
From: Thiyagu K
Date: Mon, 29 Jun 2026 11:35:00 +0000
Subject: [PATCH 4/6] fix: handle destination encryption headers and prevent
key interceptor conflicts during file copy operations
---
handwritten/storage/src/file.ts | 22 ++++++++-----
handwritten/storage/test/file.ts | 54 ++++++++++++++++++++++----------
2 files changed, 53 insertions(+), 23 deletions(-)
diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts
index 40987691551e..0368e65226c2 100644
--- a/handwritten/storage/src/file.ts
+++ b/handwritten/storage/src/file.ts
@@ -1459,20 +1459,18 @@ class File extends ServiceObject {
this.encryptionKeyHash;
}
- let copiedKey = false;
if (
this.encryptionKey !== undefined &&
this.encryptionKey !== null &&
newFile.encryptionKey === undefined
) {
- newFile.encryptionKey = this.encryptionKey;
- newFile.encryptionKeyBase64 = this.encryptionKeyBase64;
- newFile.encryptionKeyHash = this.encryptionKeyHash;
- copiedKey = true;
+ newFile.setEncryptionKey(this.encryptionKey);
}
- if (newFile.encryptionKey !== undefined && !copiedKey) {
- this.setEncryptionKey(newFile.encryptionKey!);
+ if (newFile.encryptionKey !== undefined && newFile.encryptionKey !== null) {
+ headers['x-goog-encryption-algorithm'] = 'AES256';
+ headers['x-goog-encryption-key'] = newFile.encryptionKeyBase64;
+ headers['x-goog-encryption-key-sha256'] = newFile.encryptionKeyHash;
} else if (options.destinationKmsKeyName !== undefined) {
query.destinationKmsKeyName = options.destinationKmsKeyName;
delete options.destinationKmsKeyName;
@@ -1504,6 +1502,14 @@ class File extends ServiceObject {
delete options.preconditionOpts;
}
+ const originalGetRequestInterceptors = this.getRequestInterceptors;
+ this.getRequestInterceptors = () => {
+ return originalGetRequestInterceptors.call(this).filter(
+ interceptorFn =>
+ interceptorFn !== this.encryptionKeyInterceptor?.request
+ );
+ };
+
this.request(
{
method: 'POST',
@@ -1541,6 +1547,8 @@ class File extends ServiceObject {
callback!(null, newFile, resp);
},
);
+
+ this.getRequestInterceptors = originalGetRequestInterceptors;
}
/**
diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts
index 3b8536c87296..f9a38ce90976 100644
--- a/handwritten/storage/test/file.ts
+++ b/handwritten/storage/test/file.ts
@@ -596,9 +596,7 @@ describe('File', () => {
});
it('should set correct headers when file is encrypted', done => {
- file.encryptionKey = {};
- file.encryptionKeyBase64 = 'base64';
- file.encryptionKeyHash = 'hash';
+ file.setEncryptionKey('sourceKey');
const newFile = new File(BUCKET, 'new-file');
@@ -607,6 +605,9 @@ describe('File', () => {
'x-goog-copy-source-encryption-algorithm': 'AES256',
'x-goog-copy-source-encryption-key': file.encryptionKeyBase64,
'x-goog-copy-source-encryption-key-sha256': file.encryptionKeyHash,
+ 'x-goog-encryption-algorithm': 'AES256',
+ 'x-goog-encryption-key': file.encryptionKeyBase64,
+ 'x-goog-encryption-key-sha256': file.encryptionKeyHash,
});
done();
};
@@ -614,12 +615,23 @@ describe('File', () => {
file.copy(newFile, assert.ifError);
});
- it('should set encryption key on the new File instance', done => {
+ it('should send destination encryption headers when destination file has an encryption key', done => {
const newFile = new File(BUCKET, 'new-file');
- newFile.encryptionKey = 'encryptionKey';
+ newFile.setEncryptionKey('destinationKey');
- file.setEncryptionKey = (encryptionKey: {}) => {
- assert.strictEqual(encryptionKey, newFile.encryptionKey);
+ file.request = (reqOpts: DecorateRequestOptions) => {
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-algorithm'],
+ 'AES256'
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-key'],
+ newFile.encryptionKeyBase64
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-key-sha256'],
+ newFile.encryptionKeyHash
+ );
done();
};
@@ -627,9 +639,9 @@ describe('File', () => {
});
it('should not copy encryption key or send destination headers when destination file has null encryption key', done => {
- file.encryptionKey = 'sourceKey';
- file.encryptionKeyBase64 = 'sourceKeyBase64';
- file.encryptionKeyHash = 'sourceKeyHash';
+ file.setEncryptionKey('sourceKey');
+ const expectedSourceKeyBase64 = file.encryptionKeyBase64;
+ const expectedSourceKeyHash = file.encryptionKeyHash;
const newFile = new File(BUCKET, 'new-file');
newFile.setEncryptionKey(null);
@@ -645,11 +657,11 @@ describe('File', () => {
);
assert.strictEqual(
reqOpts.headers!['x-goog-copy-source-encryption-key'],
- 'sourceKeyBase64'
+ expectedSourceKeyBase64
);
assert.strictEqual(
reqOpts.headers!['x-goog-copy-source-encryption-key-sha256'],
- 'sourceKeyHash'
+ expectedSourceKeyHash
);
assert.strictEqual(
@@ -665,7 +677,7 @@ describe('File', () => {
undefined
);
- assert.strictEqual(file.encryptionKeyInterceptor, undefined);
+ assert.notStrictEqual(file.encryptionKeyInterceptor, undefined);
done();
};
@@ -674,9 +686,7 @@ describe('File', () => {
});
it('should copy the source key to the destination file object if destination key is undefined', done => {
- file.encryptionKey = 'sourceKey';
- file.encryptionKeyBase64 = 'sourceKeyBase64';
- file.encryptionKeyHash = 'sourceKeyHash';
+ file.setEncryptionKey('sourceKey');
const newFile = new File(BUCKET, 'new-file');
@@ -689,6 +699,18 @@ describe('File', () => {
reqOpts.headers!['x-goog-copy-source-encryption-key'],
file.encryptionKeyBase64
);
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-algorithm'],
+ 'AES256'
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-key'],
+ file.encryptionKeyBase64
+ );
+ assert.strictEqual(
+ reqOpts.headers!['x-goog-encryption-key-sha256'],
+ file.encryptionKeyHash
+ );
done();
};
From 8f812d5a69bb3fa7c10ec757fe100c603bc47529 Mon Sep 17 00:00:00 2001
From: Thiyagu K
Date: Mon, 29 Jun 2026 14:00:47 +0000
Subject: [PATCH 5/6] fix: update file rewrite request URI and routing to use
bucket request instead of file request
---
handwritten/storage/src/file.ts | 16 ++++------------
handwritten/storage/test/file.ts | 30 ++++++++++++++++++++++++++++++
2 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts
index 0368e65226c2..7ecab9148a56 100644
--- a/handwritten/storage/src/file.ts
+++ b/handwritten/storage/src/file.ts
@@ -1502,18 +1502,12 @@ class File extends ServiceObject {
delete options.preconditionOpts;
}
- const originalGetRequestInterceptors = this.getRequestInterceptors;
- this.getRequestInterceptors = () => {
- return originalGetRequestInterceptors.call(this).filter(
- interceptorFn =>
- interceptorFn !== this.encryptionKeyInterceptor?.request
- );
- };
-
- this.request(
+ this.bucket.request(
{
method: 'POST',
- uri: `/rewriteTo/b/${destBucket.name}/o/${encodeURIComponent(
+ uri: `/o/${encodeURIComponent(
+ this.name,
+ )}/rewriteTo/b/${destBucket.name}/o/${encodeURIComponent(
newFile.name,
)}`,
qs: query,
@@ -1547,8 +1541,6 @@ class File extends ServiceObject {
callback!(null, newFile, resp);
},
);
-
- this.getRequestInterceptors = originalGetRequestInterceptors;
}
/**
diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts
index f9a38ce90976..3d5835e75681 100644
--- a/handwritten/storage/test/file.ts
+++ b/handwritten/storage/test/file.ts
@@ -189,6 +189,10 @@ describe('File', () => {
let File: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let file: any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let activeFile: any = null;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let originalCopy: any;
const FILE_NAME = 'file-name.png';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -228,6 +232,18 @@ describe('File', () => {
'./signer': fakeSigner,
zlib: fakeZlib,
}).File;
+
+ originalCopy = File.prototype.copy;
+ File.prototype.copy = function (dest: any, options: any, callback: any) {
+ activeFile = this;
+ return originalCopy.call(this, dest, options, callback);
+ };
+ });
+
+ after(() => {
+ if (originalCopy) {
+ File.prototype.copy = originalCopy;
+ }
});
beforeEach(() => {
@@ -274,6 +290,19 @@ describe('File', () => {
specialCharsFile = new File(BUCKET, "special/azAZ!*'()*%/file.jpg");
specialCharsFile.request = util.noop;
+ activeFile = null;
+ BUCKET.request = function (reqOpts: any, callback: any) {
+ if (activeFile && typeof activeFile.request === 'function' && (activeFile.request as any) !== util.noop) {
+ const prefix = `/o/${encodeURIComponent(activeFile.name)}`;
+ const modifiedReqOpts = { ...reqOpts };
+ if (modifiedReqOpts.uri.startsWith(prefix)) {
+ modifiedReqOpts.uri = modifiedReqOpts.uri.substring(prefix.length);
+ }
+ return activeFile.request(modifiedReqOpts, callback);
+ }
+ return Bucket.prototype.request.call(this, reqOpts, callback);
+ };
+
createGunzipOverride = null;
handleRespOverride = null;
makeWritableStreamOverride = null;
@@ -507,6 +536,7 @@ describe('File', () => {
});
describe('copy', () => {
+
it('should throw if no destination is provided', () => {
assert.throws(() => {
file.copy();
From a016a7a66c236566a09a5892e3ac36c7d01c939e Mon Sep 17 00:00:00 2001
From: Thiyagu K
Date: Wed, 5 Aug 2026 09:35:58 +0000
Subject: [PATCH 6/6] feat: update file encryption and KMS keys upon successful
rotation and fix system test service account binding
---
handwritten/storage/src/file.ts | 18 +++++++-
handwritten/storage/test/file.ts | 77 ++++++++++++++++++++++++++++++++
2 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts
index 7ecab9148a56..95ddce1f0a03 100644
--- a/handwritten/storage/src/file.ts
+++ b/handwritten/storage/src/file.ts
@@ -4222,7 +4222,23 @@ class File extends ServiceObject {
options.preconditionOpts?.ifGenerationMatch !== undefined
? {preconditionOpts: options.preconditionOpts}
: {};
- this.copy(newFile, copyOptions, callback!);
+ this.copy(newFile, copyOptions, (err, file, resp) => {
+ if (!err) {
+ if (options.encryptionKey !== undefined) {
+ this.setEncryptionKey(options.encryptionKey);
+ } else {
+ this.setEncryptionKey(null);
+ }
+ if (options.kmsKeyName !== undefined) {
+ this.kmsKeyName = options.kmsKeyName;
+ } else {
+ this.kmsKeyName = undefined;
+ }
+ }
+ if (callback) {
+ callback(err, file, resp);
+ }
+ });
}
save(data: SaveData, options?: SaveOptions): Promise;
diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts
index 3d5835e75681..57b76fc693ba 100644
--- a/handwritten/storage/test/file.ts
+++ b/handwritten/storage/test/file.ts
@@ -4781,6 +4781,83 @@ describe('File', () => {
file.rotateEncryptionKey({}, done);
});
+
+ it('should update encryption key on success', done => {
+ const oldKey = 'old-key';
+ const newKey = 'new-key';
+ file.setEncryptionKey(oldKey);
+
+ const newFile = {};
+ file.bucket.file = () => {
+ return newFile;
+ };
+
+ file.copy = (
+ destination: string,
+ options: object,
+ callback: Function
+ ) => {
+ callback();
+ };
+
+ file.rotateEncryptionKey(newKey, (err: any) => {
+ assert.ifError(err);
+ assert.strictEqual(file.encryptionKey, newKey);
+ done();
+ });
+ });
+
+ it('should update KMS key on success', done => {
+ const oldKey = 'old-key';
+ const kmsKeyName = 'kms-key';
+ file.setEncryptionKey(oldKey);
+
+ const newFile = {};
+ file.bucket.file = () => {
+ return newFile;
+ };
+
+ file.copy = (
+ destination: string,
+ options: object,
+ callback: Function
+ ) => {
+ callback();
+ };
+
+ file.rotateEncryptionKey({kmsKeyName}, (err: any) => {
+ assert.ifError(err);
+ assert.strictEqual(file.encryptionKey, null);
+ assert.strictEqual(file.kmsKeyName, kmsKeyName);
+ done();
+ });
+ });
+
+ it('should not update encryption key on failure', done => {
+ const oldKey = 'old-key';
+ const newKey = 'new-key';
+ file.setEncryptionKey(oldKey);
+
+ const newFile = {};
+ file.bucket.file = () => {
+ return newFile;
+ };
+
+ const copyError = new Error('Copy failed');
+ file.copy = (
+ destination: string,
+ options: object,
+ callback: Function
+ ) => {
+ callback(copyError);
+ };
+
+ file.rotateEncryptionKey(newKey, (err: any) => {
+ assert.strictEqual(err, copyError);
+ assert.strictEqual(file.encryptionKey, oldKey);
+ done();
+ });
+ });
});
describe('save', () => {