Skip to content

Commit cd7d9da

Browse files
msampathkumarpartheam-strzelczyk
authored
feat: add cme code samples (GoogleCloudPlatform#7866)
* Add Media-CDN code samples * update folder name as media-cdn * Update media-cdn snippet requirements * Add media-cdn CODEOWNERS details * Update copyright year to 2022 * Fix: linter suggestions & region tag prefix * For Media-CDN correct github username * Minor code changes - using return instead of print statments - updated snippet test file & re-ran tests - nox's lint and py-3.6 tests also passed * Multiple review updates - Update cookie prefix as Edge-Cache-Cookie - Update hash signature to have tailling `=` - Update script docs - Update snippets & snippet test * spell check * Clearing CLI execution * update imports * Update readme * Update folder-name & Codeowners * fix:lint error * Remove trailling = for sign_cookie fns * Update media_cdn/snippets.py Co-authored-by: Maciej Strzelczyk <strzelczyk@google.com> * Add type annotations * Add type annotations * empty commit to run PR Tests Co-authored-by: Anthonios Partheniou <partheniou@google.com> Co-authored-by: Maciej Strzelczyk <strzelczyk@google.com>
1 parent 5a22a79 commit cd7d9da

File tree

8 files changed

+312
-1
lines changed

8 files changed

+312
-1
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
/jobs/**/* @GoogleCloudPlatform/python-samples-reviewers
4545
/kubernetes_engine/**/* @GoogleCloudPlatform/python-samples-reviewers
4646
/kubernetes_engine/django_tutorial/**/* @glasnt @GoogleCloudPlatform/python-samples-reviewers
47+
/media_cdn/**/* @justin-mp @msampathkumar @GoogleCloudPlatform/python-samples-reviewers
4748
/memorystore/**/* @GoogleCloudPlatform/python-samples-reviewers
4849
/ml_engine/**/* @ivanmkc @GoogleCloudPlatform/python-samples-reviewers
4950
/monitoring/**/* @GoogleCloudPlatform/python-samples-reviewers

cdn/snippets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def sign_cookie(url_prefix, key_name, base64_key, expiration_time):
204204
sign_cookie_parser.add_argument(
205205
'expiration_time',
206206
type=lambda d: datetime.datetime.utcfromtimestamp(float(d)),
207-
help='Expiration time expessed as seconds since the epoch.')
207+
help='Expiration time expressed as seconds since the epoch.')
208208

209209
args = parser.parse_args()
210210

media_cdn/README.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.. This file is automatically generated. Do not edit this file directly.
2+
3+
Google Cloud Media CDN Python Samples
4+
===============================================================================
5+
6+
.. image:: https://gstatic.com/cloudssh/images/open-btn.png
7+
:target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=cdn/README.rst
8+
9+
10+
This directory contains samples for Google Cloud Media CDN. 'Google Cloud Media CDN'_ Media CDN is a global edge network for streaming media, backed by Google's global network of edge caches in thousands of locations.
11+
12+
13+
14+
15+
.. _Google Cloud Media CDN: https://cloud.google.com/media-cdn/docs
16+
17+
18+
Samples
19+
-------------------------------------------------------------------------------
20+
21+
Snippets
22+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
23+
24+
.. image:: https://gstatic.com/cloudssh/images/open-btn.png
25+
:target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=media-cdn/snippets.py,media-cdn/README.rst
26+
27+
28+
.. _Google Cloud SDK: https://cloud.google.com/sdk/

media_cdn/README.rst.in

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Used to generate README.rst
2+
3+
product:
4+
name: Google Cloud Media CDN
5+
short_name: Cloud Media CDN
6+
url: https://cloud.google.com/media-cdn/docs
7+
description: >
8+
'Google Cloud Media CDN'_ Media CDN is a global edge network for streaming media, backed by
9+
Google's global network of edge caches in thousands of locations.
10+
11+
12+
samples:
13+
- name: Snippets
14+
file: snippets.py
15+
show_help: true
16+
17+
folder: media-cdn

media_cdn/requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==7.0.1

media_cdn/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
six==1.16.0
2+
cryptography==36.0.2

media_cdn/snippets.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright 2022 Google, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""This application demonstrates how to perform operations on data (content)
18+
when using Google Cloud Media CDN.
19+
20+
For more information, see the README.md under /media-cdn and the documentation
21+
at https://cloud.google.com/media-cdn/docs.
22+
"""
23+
24+
# [START mediacdn_sign_url]
25+
# [START mediacdn_sign_cookie]
26+
import base64
27+
import datetime
28+
29+
import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519
30+
31+
32+
from six.moves import urllib
33+
34+
# [END mediacdn_sign_cookie]
35+
# [END mediacdn_sign_url]
36+
37+
38+
# [START mediacdn_sign_url]
39+
def sign_url(url: str, key_name: str, base64_key: str, expiration_time: datetime.datetime) -> str:
40+
"""Gets the Signed URL string for the specified URL and configuration.
41+
42+
Args:
43+
url: URL to sign as a string.
44+
key_name: name of the signing key as a string.
45+
base64_key: signing key as a base64 encoded byte string.
46+
expiration_time: expiration time as a UTC datetime object.
47+
48+
Returns:
49+
Returns the Signed URL appended with the query parameters based on the
50+
specified configuration.
51+
"""
52+
stripped_url = url.strip()
53+
parsed_url = urllib.parse.urlsplit(stripped_url)
54+
query_params = urllib.parse.parse_qs(
55+
parsed_url.query, keep_blank_values=True)
56+
epoch = datetime.datetime.utcfromtimestamp(0)
57+
expiration_timestamp = int((expiration_time - epoch).total_seconds())
58+
decoded_key = base64.urlsafe_b64decode(base64_key)
59+
60+
url_pattern = u'{url}{separator}Expires={expires}&KeyName={key_name}'
61+
62+
url_to_sign = url_pattern.format(
63+
url=stripped_url,
64+
separator='&' if query_params else '?',
65+
expires=expiration_timestamp,
66+
key_name=key_name)
67+
68+
digest = ed25519.Ed25519PrivateKey.from_private_bytes(
69+
decoded_key).sign(url_to_sign.encode('utf-8'))
70+
signature = base64.urlsafe_b64encode(digest).decode('utf-8')
71+
signed_url = u'{url}&Signature={signature}'.format(
72+
url=url_to_sign, signature=signature)
73+
74+
return signed_url
75+
76+
77+
def sign_url_prefix(url: str, url_prefix, key_name: str, base64_key: str, expiration_time: datetime.datetime) -> str:
78+
"""Gets the Signed URL string for the specified URL prefix and configuration.
79+
80+
Args:
81+
url: URL of request.
82+
url_prefix: URL prefix to sign as a string.
83+
key_name: name of the signing key as a string.
84+
base64_key: signing key as a base64 encoded string.
85+
expiration_time: expiration time as a UTC datetime object.
86+
87+
Returns:
88+
Returns the Signed URL appended with the query parameters based on the
89+
specified URL prefix and configuration.
90+
"""
91+
stripped_url = url.strip()
92+
parsed_url = urllib.parse.urlsplit(stripped_url)
93+
query_params = urllib.parse.parse_qs(
94+
parsed_url.query, keep_blank_values=True)
95+
encoded_url_prefix = base64.urlsafe_b64encode(
96+
url_prefix.strip().encode('utf-8')).decode('utf-8')
97+
epoch = datetime.datetime.utcfromtimestamp(0)
98+
expiration_timestamp = int((expiration_time - epoch).total_seconds())
99+
decoded_key = base64.urlsafe_b64decode(base64_key)
100+
101+
policy_pattern = u'URLPrefix={encoded_url_prefix}&Expires={expires}&KeyName={key_name}'
102+
policy = policy_pattern.format(
103+
encoded_url_prefix=encoded_url_prefix,
104+
expires=expiration_timestamp,
105+
key_name=key_name)
106+
107+
digest = ed25519.Ed25519PrivateKey.from_private_bytes(
108+
decoded_key).sign(policy.encode('utf-8'))
109+
signature = base64.urlsafe_b64encode(digest).decode('utf-8')
110+
signed_url = u'{url}{separator}{policy}&Signature={signature}'.format(
111+
url=stripped_url,
112+
separator='&' if query_params else '?',
113+
policy=policy,
114+
signature=signature)
115+
return signed_url
116+
# [END mediacdn_sign_url]
117+
118+
119+
# [START mediacdn_sign_cookie]
120+
def sign_cookie(url_prefix: str, key_name: str, base64_key: str, expiration_time: datetime.datetime) -> str:
121+
"""Gets the Signed cookie value for the specified URL prefix and configuration.
122+
123+
Args:
124+
url_prefix: URL prefix to sign as a string.
125+
key_name: name of the signing key as a string.
126+
base64_key: signing key as a base64 encoded string.
127+
expiration_time: expiration time as a UTC datetime object.
128+
129+
Returns:
130+
Returns the Edge-Cache-Cookie value based on the specified configuration.
131+
"""
132+
encoded_url_prefix = base64.urlsafe_b64encode(
133+
url_prefix.strip().encode('utf-8')).decode('utf-8')
134+
epoch = datetime.datetime.utcfromtimestamp(0)
135+
expiration_timestamp = int((expiration_time - epoch).total_seconds())
136+
decoded_key = base64.urlsafe_b64decode(base64_key)
137+
138+
policy_pattern = u'URLPrefix={encoded_url_prefix}:Expires={expires}:KeyName={key_name}'
139+
policy = policy_pattern.format(
140+
encoded_url_prefix=encoded_url_prefix,
141+
expires=expiration_timestamp,
142+
key_name=key_name)
143+
144+
digest = ed25519.Ed25519PrivateKey.from_private_bytes(
145+
decoded_key).sign(policy.encode('utf-8'))
146+
signature = base64.urlsafe_b64encode(digest).decode('utf-8')
147+
148+
signed_policy = u'Edge-Cache-Cookie={policy}:Signature={signature}'.format(
149+
policy=policy, signature=signature)
150+
return signed_policy
151+
# [END mediacdn_sign_cookie]

media_cdn/snippets_test.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright 2022 Google, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Tests for snippets."""
18+
19+
import datetime
20+
21+
import pytest
22+
23+
import snippets
24+
25+
26+
EPOCH_TIME = 1650848400
27+
28+
29+
def test_sign_url(capsys: pytest.LogCaptureFixture) -> None:
30+
results = []
31+
results.append(snippets.sign_url(
32+
'http://35.186.234.33/index.html',
33+
'my-key',
34+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
35+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
36+
results.append(snippets.sign_url(
37+
'http://www.example.com/',
38+
'my-key',
39+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
40+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
41+
results.append(snippets.sign_url(
42+
'http://www.example.com/some/path?some=query&another=param',
43+
'my-key',
44+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
45+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
46+
assert results[0] == (
47+
'http://35.186.234.33/index.html?Expires=1650848400&KeyName=my-key&'
48+
'Signature=16-oE9GZ5U9S_LYrW8RplZhvMfI7RGtGRY0C-Ahh6YAwiJ0UaEi6rQuPxfm6R-cBPfs8MwRGiu2fAoS1JOoKCA==')
49+
assert results[1] == (
50+
'http://www.example.com/?Expires=1650848400&KeyName=my-key&'
51+
'Signature=QhWcq48iCRTJFayWexw929QRxjOxE8ZPSQ38ybTxLhu77hmS_JB6GSougMu_-ejS_ZiGguqxT-HfgSFuy3f5DQ==')
52+
assert results[2] == (
53+
'http://www.example.com/some/path?some=query&another=param&Expires='
54+
'1650848400&KeyName=my-key&Signature=Li_D6rxUh1Kj54JbmUuAms2wmjJHJUcMXJHgYxjL4LqYH02wSX-4gCayXgklNSDpfBfSHnbdC_wvcdyXvADGDw==')
55+
56+
57+
def test_sign_url_prefix(capsys: pytest.LogCaptureFixture) -> None:
58+
results = []
59+
results.append(snippets.sign_url_prefix(
60+
'http://35.186.234.33/index.html',
61+
'http://35.186.234.33/',
62+
'my-key',
63+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
64+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
65+
results.append(snippets.sign_url_prefix(
66+
'http://www.example.com/',
67+
'http://www.example.com/',
68+
'my-key',
69+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
70+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
71+
results.append(snippets.sign_url_prefix(
72+
'http://www.example.com/some/path?some=query&another=param',
73+
'http://www.example.com/some/',
74+
'my-key',
75+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
76+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
77+
assert results[0] == (
78+
'http://35.186.234.33/index.html?URLPrefix=aHR0cDovLzM1LjE4Ni4yMzQuMzMv&'
79+
'Expires=1650848400&KeyName=my-key&'
80+
'Signature=mR4jNsWVn39ofSC5425SXwZVzHAAixemdcRGHgPuO1V1Fl7lJs2Ws5aPOGp-MhbDinFYUkutHh-I9c5Du4jtAA==')
81+
assert results[1] == (
82+
'http://www.example.com/?URLPrefix=aHR0cDovL3d3dy5leGFtcGxlLmNvbS8=&'
83+
'Expires=1650848400&KeyName=my-key&'
84+
'Signature=gFGKa4T8Fn1GiTMp1VBd6sSfjRKcPEgTB1k8mn48yXyzg4-Dfbrk-HJeYFGFznZvkF_eSPg1K03hqbMDkFTiAg==')
85+
assert results[2] == (
86+
'http://www.example.com/some/path?some=query&another=param&'
87+
'URLPrefix=aHR0cDovL3d3dy5leGFtcGxlLmNvbS9zb21lLw==&'
88+
'Expires=1650848400&KeyName=my-key&'
89+
'Signature=pVN8HKc6Be-PDczd9NjqSui3HHaoCLUN5iDv6JhQ77uKigsCih6z_cMTGjeXhgGASh1zr-ZPrOnxWJxxGWxsBg==')
90+
91+
92+
def test_sign_cookie(capsys: pytest.LogCaptureFixture) -> None:
93+
results = []
94+
results.append(snippets.sign_cookie(
95+
'http://35.186.234.33/index.html',
96+
'my-key',
97+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
98+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
99+
results.append(snippets.sign_cookie(
100+
'http://www.example.com/foo/',
101+
'my-key',
102+
'BxwXXNjeGaoWqjr7GHEymRJkP4SaOC12dTGixk7Yr8I=',
103+
datetime.datetime.utcfromtimestamp(EPOCH_TIME)))
104+
assert results[0] == (
105+
'Edge-Cache-Cookie=URLPrefix=aHR0cDovLzM1LjE4Ni4yMzQuMzMvaW5kZXguaHRtbA==:'
106+
'Expires=1650848400:KeyName=my-key:'
107+
'Signature=kTJ4QVEax5TZmxypq8pnIkjky-s_UtKGPSCd-nxqMYfwqr5HunAy-7XumWc3asRCHI2_ikVQXs7IDXJ9gV28Dg==')
108+
assert results[1] == (
109+
'Edge-Cache-Cookie=URLPrefix=aHR0cDovL3d3dy5leGFtcGxlLmNvbS9mb28v:'
110+
'Expires=1650848400:KeyName=my-key:'
111+
'Signature=I0BnupL1tKbXklf1rK50nlC9JMh4HBLogTKByatOFRvALofT159BegB26Z2WmrI-ZAgAp8Q-1__bWtFdMAqCAA==')

0 commit comments

Comments
 (0)