Skip to content

Commit b4b6816

Browse files
authored
feat(storage): Add snippets for v4 signed URLs (#2142)
* feat(storage): Add snippets for v4 signed URLs * lint * fix .format() * add v4 command to switch statement * fix region tag * change if => elif to try to make func less complex * move main to a function
1 parent b50ebac commit b4b6816

File tree

3 files changed

+103
-7
lines changed

3 files changed

+103
-7
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
google-cloud-pubsub==0.39.1
2-
google-cloud-storage==1.14.0
2+
google-cloud-storage==1.15.0

storage/cloud-client/snippets.py

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def make_blob_public(bucket_name, blob_name):
249249

250250

251251
def generate_signed_url(bucket_name, blob_name):
252-
"""Generates a signed URL for a blob.
252+
"""Generates a v2 signed URL for downloading a blob.
253253
254254
Note that this method requires a service account key file. You can not use
255255
this if you are using Application Default Credentials from Google Compute
@@ -269,6 +269,62 @@ def generate_signed_url(bucket_name, blob_name):
269269
return url
270270

271271

272+
# [START storage_generate_signed_url_v4]
273+
def generate_download_signed_url_v4(bucket_name, blob_name):
274+
"""Generates a v4 signed URL for downloading a blob.
275+
276+
Note that this method requires a service account key file. You can not use
277+
this if you are using Application Default Credentials from Google Compute
278+
Engine or from the Google Cloud SDK.
279+
"""
280+
storage_client = storage.Client()
281+
bucket = storage_client.get_bucket(bucket_name)
282+
blob = bucket.blob(blob_name)
283+
284+
url = blob.generate_signed_url(
285+
version='v4',
286+
# This URL is valid for 15 minutes
287+
expiration=datetime.timedelta(minutes=15),
288+
# Allow GET requests using this URL.
289+
method='GET')
290+
291+
print('Generated GET signed URL:')
292+
print(url)
293+
print('You can use this URL with any user agent, for example:')
294+
print('curl \'{}\''.format(url))
295+
return url
296+
# [END storage_generate_signed_url_v4]
297+
298+
299+
# [START storage_generate_upload_signed_url_v4]
300+
def generate_upload_signed_url_v4(bucket_name, blob_name):
301+
"""Generates a v4 signed URL for uploading a blob using HTTP PUT.
302+
303+
Note that this method requires a service account key file. You can not use
304+
this if you are using Application Default Credentials from Google Compute
305+
Engine or from the Google Cloud SDK.
306+
"""
307+
storage_client = storage.Client()
308+
bucket = storage_client.get_bucket(bucket_name)
309+
blob = bucket.blob(blob_name)
310+
311+
url = blob.generate_signed_url(
312+
version='v4',
313+
# This URL is valid for 15 minutes
314+
expiration=datetime.timedelta(minutes=15),
315+
# Allow GET requests using this URL.
316+
method='PUT',
317+
content_type='application/octet-stream')
318+
319+
print('Generated PUT signed URL:')
320+
print(url)
321+
print('You can use this URL with any user agent, for example:')
322+
print("curl -X PUT -H 'Content-Type: application/octet-stream' "
323+
"--upload-file my-file '{}'".format(url))
324+
return url
325+
# [END storage_generate_upload_signed_url_v4]
326+
327+
272328
def rename_blob(bucket_name, blob_name, new_name):
273329
"""Renames a blob."""
274330
storage_client = storage.Client()
@@ -296,7 +352,7 @@ def copy_blob(bucket_name, blob_name, new_bucket_name, new_blob_name):
296352
destination_bucket.name))
297353

298354

299-
if __name__ == '__main__':
355+
def main():
300356
parser = argparse.ArgumentParser(
301357
description=__doc__,
302358
formatter_class=argparse.RawDescriptionHelpFormatter)
@@ -350,6 +406,14 @@ def copy_blob(bucket_name, blob_name, new_bucket_name, new_blob_name):
350406
'signed-url', help=generate_signed_url.__doc__)
351407
signed_url_parser.add_argument('blob_name')
352408

409+
signed_url_download_v4_parser = subparsers.add_parser(
410+
'signed-url-download-v4', help=generate_download_signed_url_v4.__doc__)
411+
signed_url_download_v4_parser.add_argument('blob_name')
412+
413+
signed_url_upload_v4_parser = subparsers.add_parser(
414+
'signed-url-upload-v4', help=generate_upload_signed_url_v4.__doc__)
415+
signed_url_upload_v4_parser.add_argument('blob_name')
416+
353417
rename_parser = subparsers.add_parser('rename', help=rename_blob.__doc__)
354418
rename_parser.add_argument('blob_name')
355419
rename_parser.add_argument('new_name')
@@ -363,15 +427,15 @@ def copy_blob(bucket_name, blob_name, new_bucket_name, new_blob_name):
363427

364428
if args.command == 'create-bucket':
365429
create_bucket(args.bucket_name)
366-
if args.command == 'enable-default-kms-key':
430+
elif args.command == 'enable-default-kms-key':
367431
enable_default_kms_key(args.bucket_name, args.kms_key_name)
368432
elif args.command == 'delete-bucket':
369433
delete_bucket(args.bucket_name)
370-
if args.command == 'get-bucket-labels':
434+
elif args.command == 'get-bucket-labels':
371435
get_bucket_labels(args.bucket_name)
372-
if args.command == 'add-bucket-label':
436+
elif args.command == 'add-bucket-label':
373437
add_bucket_label(args.bucket_name)
374-
if args.command == 'remove-bucket-label':
438+
elif args.command == 'remove-bucket-label':
375439
remove_bucket_label(args.bucket_name)
376440
elif args.command == 'list':
377441
list_blobs(args.bucket_name)
@@ -401,6 +465,10 @@ def copy_blob(bucket_name, blob_name, new_bucket_name, new_blob_name):
401465
make_blob_public(args.bucket_name, args.blob_name)
402466
elif args.command == 'signed-url':
403467
generate_signed_url(args.bucket_name, args.blob_name)
468+
elif args.command == 'signed-url-download-v4':
469+
generate_download_signed_url_v4(args.bucket_name, args.blob_name)
470+
elif args.command == 'signed-url-upload-v4':
471+
generate_upload_signed_url_v4(args.bucket_name, args.blob_name)
404472
elif args.command == 'rename':
405473
rename_blob(args.bucket_name, args.blob_name, args.new_name)
406474
elif args.command == 'copy':
@@ -409,3 +477,7 @@ def copy_blob(bucket_name, blob_name, new_bucket_name, new_blob_name):
409477
args.blob_name,
410478
args.new_bucket_name,
411479
args.new_blob_name)
480+
481+
482+
if __name__ == '__main__':
483+
main()

storage/cloud-client/snippets_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,30 @@ def test_generate_signed_url(test_blob, capsys):
145145
assert r.text == 'Hello, is it me you\'re looking for?'
146146

147147

148+
def test_generate_download_signed_url_v4(test_blob, capsys):
149+
url = snippets.generate_download_signed_url_v4(
150+
BUCKET,
151+
test_blob.name)
152+
153+
r = requests.get(url)
154+
assert r.text == 'Hello, is it me you\'re looking for?'
155+
156+
157+
def test_generate_upload_signed_url_v4(capsys):
158+
blob_name = 'storage_snippets_test_upload'
159+
content = b'Uploaded via v4 signed url'
160+
url = snippets.generate_upload_signed_url_v4(
161+
BUCKET,
162+
blob_name)
163+
164+
requests.put(url, data=content, headers={
165+
'content-type': 'application/octet-stream'})
166+
167+
bucket = storage.Client().bucket(BUCKET)
168+
blob = bucket.blob(blob_name)
169+
assert blob.download_as_string() == content
170+
171+
148172
def test_rename_blob(test_blob):
149173
bucket = storage.Client().bucket(BUCKET)
150174

0 commit comments

Comments
 (0)