From d35fb0b526b6ab4b66c66056fdb2a97105b3c24a Mon Sep 17 00:00:00 2001 From: Joshua Tan Date: Tue, 3 Feb 2026 07:28:36 +0000 Subject: [PATCH 1/2] docs(storage): add samples for generate signed urls Fixes #3081 Fixes #3082 --- src/storage/examples/src/lib.rs | 4 ++ src/storage/examples/src/objects.rs | 2 + .../src/objects/generate_signed_url_v4.rs | 47 +++++++++++++++++ .../objects/generate_upload_signed_url_v4.rs | 51 +++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 src/storage/examples/src/objects/generate_signed_url_v4.rs create mode 100644 src/storage/examples/src/objects/generate_upload_signed_url_v4.rs diff --git a/src/storage/examples/src/lib.rs b/src/storage/examples/src/lib.rs index 4197803429..f071113f07 100644 --- a/src/storage/examples/src/lib.rs +++ b/src/storage/examples/src/lib.rs @@ -544,6 +544,10 @@ pub async fn run_object_examples(buckets: &mut Vec) -> anyhow::Result<() objects::remove_file_owner::sample(&control, &id, &service_account).await?; tracing::info!("running set_object_retention_policy example"); objects::set_object_retention_policy::sample(&control, &id).await?; + tracing::info!("running generate_signed_url_v4 example"); + objects::generate_signed_url_v4::sample(&id, "object-to-read").await?; + tracing::info!("running generate_upload_signed_url_v4 example"); + objects::generate_upload_signed_url_v4::sample(&id, "object-to-upload").await?; Ok(()) } diff --git a/src/storage/examples/src/objects.rs b/src/storage/examples/src/objects.rs index ea09026d10..e55acb2ef8 100644 --- a/src/storage/examples/src/objects.rs +++ b/src/storage/examples/src/objects.rs @@ -27,6 +27,8 @@ pub mod download_public_file; pub mod file_download_into_memory; pub mod file_upload_from_memory; pub mod generate_encryption_key; +pub mod generate_signed_url_v4; +pub mod generate_upload_signed_url_v4; pub mod get_kms_key; pub mod get_metadata; pub mod get_object_contexts; diff --git a/src/storage/examples/src/objects/generate_signed_url_v4.rs b/src/storage/examples/src/objects/generate_signed_url_v4.rs new file mode 100644 index 0000000000..048ce5cec2 --- /dev/null +++ b/src/storage/examples/src/objects/generate_signed_url_v4.rs @@ -0,0 +1,47 @@ +// Copyright 2026 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. + +// [START storage_generate_signed_url_v4] +use google_cloud_storage::builder::storage::SignedUrlBuilder; +use google_cloud_storage::http::Method; +use std::time::Duration; + +pub async fn sample(bucket_name: &str, object_name: &str) -> anyhow::Result<()> { + let signer = google_cloud_auth::credentials::Builder::default().build_signer(); + let signer = match signer { + Ok(s) => s, + Err(err) if err.is_not_supported() => { + tracing::warn!("skipping run_storage_signed_urls: {err:?}"); + return Ok(()); + } + Err(err) => { + anyhow::bail!("error creating signer: {err:?}") + } + }; + + let signed_url = SignedUrlBuilder::for_object(bucket_name, object_name) + .with_method(Method::GET) + .with_expiration(Duration::from_secs(15 * 60)) // 15 minutes + .sign_with(&signer) + .await + .map_err(anyhow::Error::new)?; + + println!("Generated GET signed URL:"); + println!("{}", signed_url); + println!("You can use this URL with any user agent, for example:"); + println!("curl '{}'", signed_url); + + Ok(()) +} +// [END storage_generate_signed_url_v4] diff --git a/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs b/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs new file mode 100644 index 0000000000..ad2abd2ceb --- /dev/null +++ b/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs @@ -0,0 +1,51 @@ +// Copyright 2026 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. + +// [START storage_generate_upload_signed_url_v4] +use google_cloud_storage::builder::storage::SignedUrlBuilder; +use google_cloud_storage::http::Method; +use std::time::Duration; + +pub async fn sample(bucket_name: &str, object_name: &str) -> anyhow::Result<()> { + let signer = google_cloud_auth::credentials::Builder::default().build_signer(); + let signer = match signer { + Ok(s) => s, + Err(err) if err.is_not_supported() => { + tracing::warn!("skipping run_storage_signed_urls: {err:?}"); + return Ok(()); + } + Err(err) => { + anyhow::bail!("error creating signer: {err:?}") + } + }; + + let signed_url = SignedUrlBuilder::for_object(bucket_name, object_name) + .with_method(Method::PUT) + .with_expiration(Duration::from_secs(15 * 60)) // 15 minutes + .with_header("content-type", "application/octet-stream") + .sign_with(&signer) + .await + .map_err(anyhow::Error::new)?; + + println!("Generated PUT signed URL:"); + println!("{}", signed_url); + println!("You can use this URL with any user agent, for example:"); + println!( + "curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file my-file '{}'", + signed_url + ); + + Ok(()) +} +// [END storage_generate_upload_signed_url_v4] From abccd1962bb5b212b2a829b6f4c11cc7a319e86d Mon Sep 17 00:00:00 2001 From: Joshua Tan Date: Wed, 4 Feb 2026 06:57:13 +0000 Subject: [PATCH 2/2] address comments --- .../src/objects/generate_signed_url_v4.rs | 28 ++++++----------- .../objects/generate_upload_signed_url_v4.rs | 31 ++++++------------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/storage/examples/src/objects/generate_signed_url_v4.rs b/src/storage/examples/src/objects/generate_signed_url_v4.rs index 048ce5cec2..2b3183a0ab 100644 --- a/src/storage/examples/src/objects/generate_signed_url_v4.rs +++ b/src/storage/examples/src/objects/generate_signed_url_v4.rs @@ -18,29 +18,19 @@ use google_cloud_storage::http::Method; use std::time::Duration; pub async fn sample(bucket_name: &str, object_name: &str) -> anyhow::Result<()> { - let signer = google_cloud_auth::credentials::Builder::default().build_signer(); - let signer = match signer { - Ok(s) => s, - Err(err) if err.is_not_supported() => { - tracing::warn!("skipping run_storage_signed_urls: {err:?}"); - return Ok(()); - } - Err(err) => { - anyhow::bail!("error creating signer: {err:?}") - } - }; + let signer = google_cloud_auth::credentials::Builder::default().build_signer()?; - let signed_url = SignedUrlBuilder::for_object(bucket_name, object_name) - .with_method(Method::GET) - .with_expiration(Duration::from_secs(15 * 60)) // 15 minutes - .sign_with(&signer) - .await - .map_err(anyhow::Error::new)?; + let signed_url = + SignedUrlBuilder::for_object(format!("projects/_/buckets/{bucket_name}"), object_name) + .with_method(Method::GET) + .with_expiration(Duration::from_secs(15 * 60)) // 15 minutes + .sign_with(&signer) + .await?; println!("Generated GET signed URL:"); - println!("{}", signed_url); + println!("{signed_url}"); println!("You can use this URL with any user agent, for example:"); - println!("curl '{}'", signed_url); + println!("curl '{signed_url}'",); Ok(()) } diff --git a/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs b/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs index ad2abd2ceb..007e81994a 100644 --- a/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs +++ b/src/storage/examples/src/objects/generate_upload_signed_url_v4.rs @@ -18,32 +18,21 @@ use google_cloud_storage::http::Method; use std::time::Duration; pub async fn sample(bucket_name: &str, object_name: &str) -> anyhow::Result<()> { - let signer = google_cloud_auth::credentials::Builder::default().build_signer(); - let signer = match signer { - Ok(s) => s, - Err(err) if err.is_not_supported() => { - tracing::warn!("skipping run_storage_signed_urls: {err:?}"); - return Ok(()); - } - Err(err) => { - anyhow::bail!("error creating signer: {err:?}") - } - }; + let signer = google_cloud_auth::credentials::Builder::default().build_signer()?; - let signed_url = SignedUrlBuilder::for_object(bucket_name, object_name) - .with_method(Method::PUT) - .with_expiration(Duration::from_secs(15 * 60)) // 15 minutes - .with_header("content-type", "application/octet-stream") - .sign_with(&signer) - .await - .map_err(anyhow::Error::new)?; + let signed_url = + SignedUrlBuilder::for_object(format!("projects/_/buckets/{bucket_name}"), object_name) + .with_method(Method::PUT) + .with_expiration(Duration::from_secs(15 * 60)) // 15 minutes + .with_header("content-type", "application/octet-stream") + .sign_with(&signer) + .await?; println!("Generated PUT signed URL:"); - println!("{}", signed_url); + println!("{signed_url}"); println!("You can use this URL with any user agent, for example:"); println!( - "curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file my-file '{}'", - signed_url + "curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file my-file '{signed_url}'", ); Ok(())