Skip to content

Commit 7222e36

Browse files
authored
fix: Clean up Python sample at cloud-sql/mysql/client-side-encryption (GoogleCloudPlatform#10028)
## Description Fixes b/280880978 Note: Before submitting a pull request, please open an issue for discussion if you are not associated with Google. ## Checklist - [ ] I have followed [Sample Guidelines from AUTHORING_GUIDE.MD](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md) - [ ] README is updated to include [all relevant information](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md#readme-file) - [ ] **Tests** pass: `nox -s py-3.9` (see [Test Environment Setup](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md#test-environment-setup)) - [ ] **Lint** pass: `nox -s lint` (see [Test Environment Setup](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md#test-environment-setup)) - [ ] These samples need a new **API enabled** in testing projects to pass (let us know which ones) - [ ] These samples need a new/updated **env vars** in testing projects set to pass (let us know which ones) - [ ] This sample adds a new sample directory, and I updated the [CODEOWNERS file](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/.github/CODEOWNERS) with the codeowners for this sample - [ ] This sample adds a new **Product API**, and I updated the [Blunderbuss issue/PR auto-assigner](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/.github/blunderbuss.yml) with the codeowners for this sample - [ ] Please **merge** this PR for me once it is approved
1 parent 568d3bd commit 7222e36

8 files changed

Lines changed: 67 additions & 110 deletions

cloud-sql/mysql/client-side-encryption/snippets/cloud_kms_env_aead.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
logger = logging.getLogger(__name__)
2323

2424

25-
def init_tink_env_aead(
26-
key_uri: str,
27-
credentials: str) -> tink.aead.KmsEnvelopeAead:
25+
def init_tink_env_aead(key_uri: str, credentials: str) -> tink.aead.KmsEnvelopeAead:
26+
"""
27+
Initiates the Envelope AEAD object using the KMS credentials.
28+
"""
2829
aead.register()
2930

3031
try:

cloud-sql/mysql/client-side-encryption/snippets/cloud_kms_env_aead_test.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# Copyright 2021 Google LLC
16-
#
17-
# Licensed under the Apache License, Version 2.0 (the "License");
18-
# you may not use this file except in compliance with the License.
19-
# You may obtain a copy of the License at
20-
#
21-
# http://www.apache.org/licenses/LICENSE-2.0
22-
#
23-
# Unless required by applicable law or agreed to in writing, software
24-
# distributed under the License is distributed on an "AS IS" BASIS,
25-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26-
# See the License for the specific language governing permissions and
27-
# limitations under the License.
28-
2915
import os
3016

3117
import pytest
18+
from tink.aead import KmsEnvelopeAead
3219

3320
from snippets.cloud_kms_env_aead import init_tink_env_aead
3421

@@ -40,12 +27,11 @@ def setup() -> str:
4027
yield kms_uri
4128

4229

43-
def test_cloud_kms_env_aead(
44-
capsys: pytest.CaptureFixture, kms_uri: str) -> None:
30+
def test_cloud_kms_env_aead(kms_uri: str) -> None:
4531
credentials = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "")
4632

4733
# Create env_aead primitive
48-
init_tink_env_aead(kms_uri, credentials)
34+
envelope = init_tink_env_aead(kms_uri, credentials)
4935

50-
captured = capsys.readouterr().out
51-
assert f"Created envelope AEAD Primitive using KMS URI: {kms_uri}" in captured
36+
assert isinstance(envelope, KmsEnvelopeAead)
37+
assert envelope.key_template == kms_uri

cloud-sql/mysql/client-side-encryption/snippets/cloud_sql_connection_pool.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
def init_tcp_connection_engine(
2020
db_user: str, db_pass: str, db_name: str, db_host: str
2121
) -> sqlalchemy.engine.base.Engine:
22+
"""
23+
Creates a connection to the database using tcp socket.
24+
"""
2225
# Remember - storing secrets in plaintext is potentially unsafe. Consider using
2326
# something like https://cloud.google.com/secret-manager/docs/overview to help keep
2427
# secrets secret.
@@ -50,6 +53,9 @@ def init_unix_connection_engine(
5053
instance_connection_name: str,
5154
db_socket_dir: str,
5255
) -> sqlalchemy.engine.base.Engine:
56+
"""
57+
Creates a connection to the database using unix socket.
58+
"""
5359
# Remember - storing secrets in plaintext is potentially unsafe. Consider using
5460
# something like https://cloud.google.com/secret-manager/docs/overview to help keep
5561
# secrets secret.
@@ -78,7 +84,7 @@ def init_db(
7884
db_socket_dir: str = None,
7985
db_host: str = None,
8086
) -> sqlalchemy.engine.base.Engine:
81-
87+
"""Starts a connection to the database and creates voting table if it doesn't exist."""
8288
if db_host:
8389
db = init_tcp_connection_engine(db_user, db_pass, db_name, db_host)
8490
else:

cloud-sql/mysql/client-side-encryption/snippets/cloud_sql_connection_pool_test.py

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,17 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# Copyright 2021 Google LLC
16-
#
17-
# Licensed under the Apache License, Version 2.0 (the "License");
18-
# you may not use this file except in compliance with the License.
19-
# You may obtain a copy of the License at
20-
#
21-
# http://www.apache.org/licenses/LICENSE-2.0
22-
#
23-
# Unless required by applicable law or agreed to in writing, software
24-
# distributed under the License is distributed on an "AS IS" BASIS,
25-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26-
# See the License for the specific language governing permissions and
27-
# limitations under the License.
28-
2915
from __future__ import annotations
3016

3117
import os
3218
import uuid
3319

3420
import pytest
21+
import sqlalchemy
3522

36-
from snippets.cloud_sql_connection_pool import (
37-
init_db,
38-
init_tcp_connection_engine,
39-
init_unix_connection_engine
40-
)
23+
from snippets.cloud_sql_connection_pool import (init_db,
24+
init_tcp_connection_engine,
25+
init_unix_connection_engine)
4126

4227

4328
@pytest.fixture(name="conn_vars")
@@ -60,50 +45,46 @@ def setup() -> dict[str, str]:
6045
yield conn_vars
6146

6247

63-
def test_init_tcp_connection_engine(
64-
capsys: pytest.CaptureFixture,
65-
conn_vars: dict[str, str]) -> None:
66-
67-
init_tcp_connection_engine(
48+
def test_init_tcp_connection_engine(conn_vars: dict[str, str]) -> None:
49+
engine = init_tcp_connection_engine(
6850
db_user=conn_vars["db_user"],
6951
db_name=conn_vars["db_name"],
7052
db_pass=conn_vars["db_pass"],
7153
db_host=conn_vars["db_host"],
7254
)
7355

74-
captured = capsys.readouterr().out
75-
assert "Created TCP connection pool" in captured
56+
assert isinstance(engine, sqlalchemy.engine.base.Engine)
57+
assert conn_vars["db_name"] in engine.url
7658

7759

78-
def test_init_unix_connection_engine(
79-
capsys: pytest.CaptureFixture,
80-
conn_vars: dict[str, str]) -> None:
81-
82-
init_unix_connection_engine(
60+
def test_init_unix_connection_engine(conn_vars: dict[str, str]) -> None:
61+
engine = init_unix_connection_engine(
8362
db_user=conn_vars["db_user"],
8463
db_name=conn_vars["db_name"],
8564
db_pass=conn_vars["db_pass"],
8665
instance_connection_name=conn_vars["instance_conn_name"],
8766
db_socket_dir=conn_vars["db_socket_dir"],
8867
)
8968

90-
captured = capsys.readouterr().out
91-
assert "Created Unix socket connection pool" in captured
92-
69+
assert isinstance(engine, sqlalchemy.engine.base.Engine)
70+
assert conn_vars["db_name"] in engine.url
9371

94-
def test_init_db(
95-
capsys: pytest.CaptureFixture,
96-
conn_vars: dict[str, str]) -> None:
9772

73+
def test_init_db(conn_vars: dict[str, str]) -> None:
9874
table_name = f"votes_{uuid.uuid4().hex}"
9975

100-
init_db(
76+
engine = init_db(
10177
db_user=conn_vars["db_user"],
10278
db_name=conn_vars["db_name"],
10379
db_pass=conn_vars["db_pass"],
10480
table_name=table_name,
10581
db_host=conn_vars["db_host"],
10682
)
10783

108-
captured = capsys.readouterr().out
109-
assert f"Created table {table_name} in db {conn_vars['db_name']}" in captured
84+
assert isinstance(engine, sqlalchemy.engine.base.Engine)
85+
86+
try:
87+
with engine.connect() as conn:
88+
conn.execute(f"SELECT count(*) FROM {table_name}").all()
89+
except Exception as error:
90+
pytest.fail(f"Database wasn't initialized properly: {error}")

cloud-sql/mysql/client-side-encryption/snippets/encrypt_and_insert_data.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
from .cloud_kms_env_aead import init_tink_env_aead
2424
from .cloud_sql_connection_pool import init_db
2525

26-
2726
logger = logging.getLogger(__name__)
2827

2928

3029
def main() -> None:
30+
"""
31+
Connects to the database, encrypts and inserts some data.
32+
"""
3133
db_user = os.environ["DB_USER"] # e.g. "root", "mysql"
3234
db_pass = os.environ["DB_PASS"] # e.g. "mysupersecretpassword"
3335
db_name = os.environ["DB_NAME"] # e.g. "votes_db"
@@ -73,6 +75,10 @@ def encrypt_and_insert_data(
7375
team: str,
7476
email: str,
7577
) -> None:
78+
"""
79+
Inserts a vote into the database with email address previously encrypted using
80+
a KmsEnvelopeAead object.
81+
"""
7682
time_cast = datetime.datetime.now(tz=datetime.timezone.utc)
7783
# Use the envelope AEAD primitive to encrypt the email, using the team name as
7884
# associated data. Encryption with associated data ensures authenticity
@@ -93,11 +99,7 @@ def encrypt_and_insert_data(
9399
# Using a with statement ensures that the connection is always released
94100
# back into the pool at the end of statement (even if an error occurs)
95101
with db.connect() as conn:
96-
conn.execute(
97-
stmt,
98-
time_cast=time_cast,
99-
team=team,
100-
voter_email=encrypted_email)
102+
conn.execute(stmt, time_cast=time_cast, team=team, voter_email=encrypted_email)
101103
print(f"Vote successfully cast for '{team}' at time {time_cast}!")
102104

103105

cloud-sql/mysql/client-side-encryption/snippets/encrypt_and_insert_data_test.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from snippets.cloud_sql_connection_pool import init_db
2424
from snippets.encrypt_and_insert_data import encrypt_and_insert_data
2525

26-
2726
table_name = f"votes_{uuid.uuid4().hex}"
2827

2928

@@ -65,17 +64,10 @@ def setup_key() -> tink.aead.KmsEnvelopeAead:
6564

6665

6766
def test_encrypt_and_insert_data(
68-
capsys: pytest.CaptureFixture,
6967
pool: sqlalchemy.engine.Engine,
70-
env_aead: tink.aead.KmsEnvelopeAead
68+
env_aead: tink.aead.KmsEnvelopeAead,
7169
) -> None:
72-
encrypt_and_insert_data(
73-
pool,
74-
env_aead,
75-
table_name,
76-
"SPACES",
77-
"hello@example.com")
78-
captured = capsys.readouterr()
70+
encrypt_and_insert_data(pool, env_aead, table_name, "SPACES", "hello@example.com")
7971

8072
decrypted_emails = []
8173
with pool.connect() as conn:
@@ -89,5 +81,4 @@ def test_encrypt_and_insert_data(
8981
email = env_aead.decrypt(row[2], team.encode()).decode()
9082
decrypted_emails.append(email)
9183

92-
assert "Vote successfully cast for 'SPACES'" in captured.out
9384
assert "hello@example.com" in decrypted_emails

cloud-sql/mysql/client-side-encryption/snippets/query_and_decrypt_data.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525

2626
def main() -> None:
27+
"""
28+
Connects to the database, inserts encrypted data and retrieves encrypted data.
29+
"""
2730
db_user = os.environ["DB_USER"] # e.g. "root", "mysql"
2831
db_pass = os.environ["DB_PASS"] # e.g. "mysupersecretpassword"
2932
db_name = os.environ["DB_NAME"] # e.g. "votes_db"
@@ -67,7 +70,10 @@ def query_and_decrypt_data(
6770
db: sqlalchemy.engine.base.Engine,
6871
env_aead: tink.aead.KmsEnvelopeAead,
6972
table_name: str,
70-
) -> None:
73+
) -> list[tuple[str]]:
74+
"""
75+
Retrieves data from the database and decrypts it using the KmsEnvelopeAead object.
76+
"""
7177
with db.connect() as conn:
7278
# Execute the query and fetch all results
7379
recent_votes = conn.execute(
@@ -76,6 +82,7 @@ def query_and_decrypt_data(
7682
).fetchall()
7783

7884
print("Team\tEmail\tTime Cast")
85+
output = []
7986

8087
for row in recent_votes:
8188
team = row[0]
@@ -88,6 +95,8 @@ def query_and_decrypt_data(
8895

8996
# Print recent votes
9097
print(f"{team}\t{email}\t{time_cast}")
98+
output.append((team, email, time_cast))
99+
return output
91100

92101

93102
# [END cloud_sql_mysql_cse_query]

cloud-sql/mysql/client-side-encryption/snippets/query_and_decrypt_data_test.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# Copyright 2021 Google LLC
16-
#
17-
# Licensed under the Apache License, Version 2.0 (the "License");
18-
# you may not use this file except in compliance with the License.
19-
# You may obtain a copy of the License at
20-
#
21-
# http://www.apache.org/licenses/LICENSE-2.0
22-
#
23-
# Unless required by applicable law or agreed to in writing, software
24-
# distributed under the License is distributed on an "AS IS" BASIS,
25-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26-
# See the License for the specific language governing permissions and
27-
# limitations under the License.
28-
2915
import os
3016
import uuid
3117

@@ -79,21 +65,16 @@ def setup_key() -> tink.aead.KmsEnvelopeAead:
7965

8066

8167
def test_query_and_decrypt_data(
82-
capsys: pytest.CaptureFixture,
8368
pool: sqlalchemy.engine.Engine,
84-
env_aead: tink.aead.KmsEnvelopeAead
69+
env_aead: tink.aead.KmsEnvelopeAead,
8570
) -> None:
86-
8771
# Insert data into table before testing
88-
encrypt_and_insert_data(
89-
pool,
90-
env_aead,
91-
table_name,
92-
"SPACES",
93-
"hello@example.com")
94-
95-
query_and_decrypt_data(pool, env_aead, table_name)
96-
97-
captured = capsys.readouterr()
98-
assert "Team\tEmail\tTime Cast" in captured.out
99-
assert "hello@example.com" in captured.out
72+
encrypt_and_insert_data(pool, env_aead, table_name, "SPACES", "hello@example.com")
73+
74+
output = query_and_decrypt_data(pool, env_aead, table_name)
75+
76+
for row in output:
77+
if row[1] == "hello@example.com":
78+
break
79+
else:
80+
pytest.fail("Failed to find vote in the decrypted data.")

0 commit comments

Comments
 (0)