Skip to content

Commit a36ceb7

Browse files
committed
Add 'list_hmac_keys' / 'create_hmac_key' methods to client. (#8043)
Toward #7851.
1 parent 66a5d44 commit a36ceb7

File tree

2 files changed

+215
-2
lines changed

2 files changed

+215
-2
lines changed

storage/google/cloud/storage/client.py

+78
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from google.cloud.storage.batch import Batch
2727
from google.cloud.storage.bucket import Bucket
2828
from google.cloud.storage.blob import Blob
29+
from google.cloud.storage.hmac_key import HMACKeyMetadata
2930

3031

3132
_marker = object()
@@ -561,6 +562,66 @@ def list_buckets(
561562
extra_params=extra_params,
562563
)
563564

565+
def create_hmac_key(self, service_account_email):
566+
"""Create an HMAC key for a service account.
567+
568+
:type service_account_email: str
569+
:param service_account_email: e-mail address of the service account
570+
571+
:rtype:
572+
Tuple[:class:`~google.cloud.storage.hmac_key.HMACKeyMetadata`, str]
573+
:returns: metadata for the created key, plus the bytes of the key's secret, which is an 40-character base64-encoded string.
574+
"""
575+
path = "/projects/%s/hmacKeys".format(self.project)
576+
qs_params = {"serviceAccountEmail": service_account_email}
577+
api_response = self._connection.api_request(
578+
method="POST", path=path, query_params=qs_params
579+
)
580+
metadata = HMACKeyMetadata(self)
581+
metadata._properties = api_response["metadata"]
582+
secret = api_response["secret"]
583+
return metadata, secret
584+
585+
def list_hmac_keys(
586+
self, max_results=None, service_account_email=None, show_deleted_keys=None
587+
):
588+
"""List HMAC keys for a project.
589+
590+
:type max_results: int
591+
:param max_results:
592+
(Optional) max number of keys to return in a given page.
593+
594+
:type service_account_email: str
595+
:param service_account_email:
596+
(Optional) limit keys to those created by the given service account.
597+
598+
:type show_deleted_keys: bool
599+
:param show_deleted_keys:
600+
(Optional) included deleted keys in the list. Default is to
601+
exclude them.
602+
603+
:rtype:
604+
Tuple[:class:`~google.cloud.storage.hmac_key.HMACKeyMetadata`, str]
605+
:returns: metadata for the created key, plus the bytes of the key's secret, which is an 40-character base64-encoded string.
606+
"""
607+
path = "/projects/%s/hmacKeys".format(self.project)
608+
extra_params = {}
609+
610+
if service_account_email is not None:
611+
extra_params["serviceAccountEmail"] = service_account_email
612+
613+
if show_deleted_keys is not None:
614+
extra_params["showDeletedKeys"] = show_deleted_keys
615+
616+
return page_iterator.HTTPIterator(
617+
client=self,
618+
api_request=self._connection.api_request,
619+
path=path,
620+
item_to_value=_item_to_hmac_key_metadata,
621+
max_results=max_results,
622+
extra_params=extra_params,
623+
)
624+
564625

565626
def _item_to_bucket(iterator, item):
566627
"""Convert a JSON bucket to the native object.
@@ -578,3 +639,20 @@ def _item_to_bucket(iterator, item):
578639
bucket = Bucket(iterator.client, name)
579640
bucket._set_properties(item)
580641
return bucket
642+
643+
644+
def _item_to_hmac_key_metadata(iterator, item):
645+
"""Convert a JSON key metadata resource to the native object.
646+
647+
:type iterator: :class:`~google.api_core.page_iterator.Iterator`
648+
:param iterator: The iterator that has retrieved the item.
649+
650+
:type item: dict
651+
:param item: An item to be converted to a key metadata instance.
652+
653+
:rtype: :class:`~google.cloud.storage.hmac_key.HMACKeyMetadata`
654+
:returns: The next key metadata instance in the page.
655+
"""
656+
metadata = HMACKeyMetadata(iterator.client)
657+
metadata._properties = item
658+
return metadata

storage/tests/unit/test_client.py

+137-2
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ def test_list_buckets_all_arguments(self):
874874
uri_parts = urlparse(requested_url)
875875
self.assertEqual(parse_qs(uri_parts.query), expected_query)
876876

877-
def test_page_empty_response(self):
877+
def test_list_buckets_page_empty_response(self):
878878
from google.api_core import page_iterator
879879

880880
project = "PROJECT"
@@ -885,7 +885,7 @@ def test_page_empty_response(self):
885885
iterator._page = page
886886
self.assertEqual(list(page), [])
887887

888-
def test_page_non_empty_response(self):
888+
def test_list_buckets_page_non_empty_response(self):
889889
import six
890890
from google.cloud.storage.bucket import Bucket
891891

@@ -908,3 +908,138 @@ def dummy_response():
908908
self.assertEqual(page.remaining, 0)
909909
self.assertIsInstance(bucket, Bucket)
910910
self.assertEqual(bucket.name, blob_name)
911+
912+
def test_create_hmac_key(self):
913+
import datetime
914+
from pytz import UTC
915+
from six.moves.urllib.parse import urlencode
916+
from google.cloud.storage.hmac_key import HMACKeyMetadata
917+
918+
PROJECT = "PROJECT"
919+
ACCESS_ID = "ACCESS-ID"
920+
CREDENTIALS = _make_credentials()
921+
922+
SECRET = "a" * 40
923+
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
924+
now_stamp = "{}Z".format(now.isoformat())
925+
RESOURCE = {
926+
"kind": "storage#hmacKey",
927+
"metadata": {
928+
"accessId": ACCESS_ID,
929+
"etag": "ETAG",
930+
"id": "projects/{}/hmacKeys/{}".format(PROJECT, ACCESS_ID),
931+
"project": PROJECT,
932+
"state": "ACTIVE",
933+
"serviceAccountEmail": EMAIL,
934+
"timeCreated": now_stamp,
935+
"updated": now_stamp,
936+
},
937+
"secret": SECRET,
938+
}
939+
940+
client = self._make_one(project=PROJECT, credentials=CREDENTIALS)
941+
http = _make_requests_session([_make_json_response(RESOURCE)])
942+
client._http_internal = http
943+
944+
metadata, secret = client.create_hmac_key(service_account_email=EMAIL)
945+
946+
self.assertIsInstance(metadata, HMACKeyMetadata)
947+
self.assertIs(metadata._client, client)
948+
self.assertEqual(metadata._properties, RESOURCE["metadata"])
949+
self.assertEqual(secret, RESOURCE["secret"])
950+
951+
URI = "/".join(
952+
[
953+
client._connection.API_BASE_URL,
954+
"storage",
955+
client._connection.API_VERSION,
956+
"projects/%s/hmacKeys",
957+
]
958+
)
959+
QS_PARAMS = {"serviceAccountEmail": EMAIL}
960+
FULL_URI = "{}?{}".format(URI, urlencode(QS_PARAMS))
961+
http.request.assert_called_once_with(
962+
method="POST", url=FULL_URI, data=None, headers=mock.ANY
963+
)
964+
965+
def test_list_hmac_keys_defaults_empty(self):
966+
PROJECT = "PROJECT"
967+
CREDENTIALS = _make_credentials()
968+
client = self._make_one(project=PROJECT, credentials=CREDENTIALS)
969+
970+
http = _make_requests_session([_make_json_response({})])
971+
client._http_internal = http
972+
973+
metadatas = list(client.list_hmac_keys())
974+
975+
self.assertEqual(len(metadatas), 0)
976+
977+
URI = "/".join(
978+
[
979+
client._connection.API_BASE_URL,
980+
"storage",
981+
client._connection.API_VERSION,
982+
"projects/%s/hmacKeys",
983+
]
984+
)
985+
http.request.assert_called_once_with(
986+
method="GET", url=URI, data=None, headers=mock.ANY
987+
)
988+
989+
def test_list_hmac_keys_explicit_non_empty(self):
990+
from six.moves.urllib.parse import urlencode
991+
from google.cloud.storage.hmac_key import HMACKeyMetadata
992+
993+
PROJECT = "PROJECT"
994+
MAX_RESULTS = 3
995+
996+
ACCESS_ID = "ACCESS-ID"
997+
CREDENTIALS = _make_credentials()
998+
client = self._make_one(project=PROJECT, credentials=CREDENTIALS)
999+
1000+
response = {
1001+
"kind": "storage#hmacKeysMetadata",
1002+
"items": [
1003+
{
1004+
"kind": "storage#hmacKeyMetadata",
1005+
"accessId": ACCESS_ID,
1006+
"serviceAccountEmail": EMAIL,
1007+
}
1008+
],
1009+
}
1010+
1011+
http = _make_requests_session([_make_json_response(response)])
1012+
client._http_internal = http
1013+
1014+
metadatas = list(
1015+
client.list_hmac_keys(
1016+
max_results=MAX_RESULTS,
1017+
service_account_email=EMAIL,
1018+
show_deleted_keys=True,
1019+
)
1020+
)
1021+
1022+
self.assertEqual(len(metadatas), len(response["items"]))
1023+
1024+
for metadata, resource in zip(metadatas, response["items"]):
1025+
self.assertIsInstance(metadata, HMACKeyMetadata)
1026+
self.assertIs(metadata._client, client)
1027+
self.assertEqual(metadata._properties, resource)
1028+
1029+
URI = "/".join(
1030+
[
1031+
client._connection.API_BASE_URL,
1032+
"storage",
1033+
client._connection.API_VERSION,
1034+
"projects/%s/hmacKeys",
1035+
]
1036+
)
1037+
QS_PARAMS = {
1038+
"maxResults": MAX_RESULTS,
1039+
"serviceAccountEmail": EMAIL,
1040+
"showDeletedKeys": True,
1041+
}
1042+
FULL_URI = "{}?{}".format(URI, urlencode(QS_PARAMS))
1043+
http.request.assert_called_once_with(
1044+
method="GET", url=FULL_URI, data=None, headers=mock.ANY
1045+
)

0 commit comments

Comments
 (0)