diff --git a/docs/pubsub-usage.rst b/docs/pubsub-usage.rst index 28a21c27ad6d..d3dbba1a94ff 100644 --- a/docs/pubsub-usage.rst +++ b/docs/pubsub-usage.rst @@ -329,3 +329,15 @@ Fetch the IAM policy for a subscription ['systemAccount:abc-1234@systemaccounts.example.com'] >>> policy.readers ['domain:example.com'] + +Update the IAM policy for a subscription: + +.. doctest:: + + >>> from gcloud import pubsub + >>> client = pubsub.Client() + >>> topic = client.topic('topic_name') + >>> subscription = topic.subscription('subscription_name') + >>> policy = subscription.get_iam_policy() # API request + >>> policy.writers.add(policy.group('editors-list@example.com')) + >>> subscription.set_iam_policy(policy) # API request diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 5802266488b7..56fb0d337f49 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -283,3 +283,28 @@ def get_iam_policy(self, client=None): path = '%s:getIamPolicy' % (self.path,) resp = client.connection.api_request(method='GET', path=path) return Policy.from_api_repr(resp) + + def set_iam_policy(self, policy, client=None): + """Update the IAM policy for the subscription. + + See: + https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/setIamPolicy + + :type policy: :class:`gcloud.pubsub.iam.Policy` + :param policy: the new policy, typically fetched via + :meth:`get_iam_policy` and updated in place. + + :type client: :class:`gcloud.pubsub.client.Client` or ``NoneType`` + :param client: the client to use. If not passed, falls back to the + ``client`` stored on the current subscription's topic. + + :rtype: :class:`gcloud.pubsub.iam.Policy` + :returns: updated policy created from the resource returned by the + ``setIamPolicy`` API request. + """ + client = self._require_client(client) + path = '%s:setIamPolicy' % (self.path,) + resource = policy.to_api_repr() + resp = client.connection.api_request( + method='POST', path=path, data=resource) + return Policy.from_api_repr(resp) diff --git a/gcloud/pubsub/test_subscription.py b/gcloud/pubsub/test_subscription.py index 72b3d80753ce..9d18d28eb3fe 100644 --- a/gcloud/pubsub/test_subscription.py +++ b/gcloud/pubsub/test_subscription.py @@ -485,6 +485,7 @@ def test_delete_w_alternate_client(self): self.assertEqual(req['path'], '/%s' % SUB_PATH) def test_get_iam_policy_w_bound_client(self): + from gcloud.pubsub.iam import _OWNER_ROLE, _WRITER_ROLE, _READER_ROLE OWNER1 = 'user:phred@example.com' OWNER2 = 'group:cloud-logs@google.com' WRITER1 = 'domain:google.com' @@ -495,9 +496,9 @@ def test_get_iam_policy_w_bound_client(self): 'etag': 'DEADBEEF', 'version': 17, 'bindings': [ - {'role': 'roles/owner', 'members': [OWNER1, OWNER2]}, - {'role': 'roles/writer', 'members': [WRITER1, WRITER2]}, - {'role': 'roles/reader', 'members': [READER1, READER2]}, + {'role': _OWNER_ROLE, 'members': [OWNER1, OWNER2]}, + {'role': _WRITER_ROLE, 'members': [WRITER1, WRITER2]}, + {'role': _READER_ROLE, 'members': [READER1, READER2]}, ], } PROJECT = 'PROJECT' @@ -555,6 +556,91 @@ def test_get_iam_policy_w_alternate_client(self): self.assertEqual(req['method'], 'GET') self.assertEqual(req['path'], '/%s' % PATH) + def test_set_iam_policy_w_bound_client(self): + from gcloud.pubsub.iam import _OWNER_ROLE, _WRITER_ROLE, _READER_ROLE + from gcloud.pubsub.iam import Policy + OWNER1 = 'group:cloud-logs@google.com' + OWNER2 = 'user:phred@example.com' + WRITER1 = 'domain:google.com' + WRITER2 = 'user:phred@example.com' + READER1 = 'serviceAccount:1234-abcdef@service.example.com' + READER2 = 'user:phred@example.com' + POLICY = { + 'etag': 'DEADBEEF', + 'version': 17, + 'bindings': [ + {'role': _OWNER_ROLE, 'members': [OWNER1, OWNER2]}, + {'role': _WRITER_ROLE, 'members': [WRITER1, WRITER2]}, + {'role': _READER_ROLE, 'members': [READER1, READER2]}, + ], + } + RESPONSE = POLICY.copy() + RESPONSE['etag'] = 'ABACABAF' + RESPONSE['version'] = 18 + PROJECT = 'PROJECT' + TOPIC_NAME = 'topic_name' + SUB_NAME = 'sub_name' + PATH = 'projects/%s/subscriptions/%s:setIamPolicy' % ( + PROJECT, SUB_NAME) + + conn = _Connection(RESPONSE) + CLIENT = _Client(project=PROJECT, connection=conn) + topic = _Topic(TOPIC_NAME, client=CLIENT) + subscription = self._makeOne(SUB_NAME, topic) + policy = Policy('DEADBEEF', 17) + policy.owners.add(OWNER1) + policy.owners.add(OWNER2) + policy.writers.add(WRITER1) + policy.writers.add(WRITER2) + policy.readers.add(READER1) + policy.readers.add(READER2) + + new_policy = subscription.set_iam_policy(policy) + + self.assertEqual(new_policy.etag, 'ABACABAF') + self.assertEqual(new_policy.version, 18) + self.assertEqual(sorted(new_policy.owners), [OWNER1, OWNER2]) + self.assertEqual(sorted(new_policy.writers), [WRITER1, WRITER2]) + self.assertEqual(sorted(new_policy.readers), [READER1, READER2]) + + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['data'], POLICY) + + def test_set_iam_policy_w_alternate_client(self): + from gcloud.pubsub.iam import Policy + RESPONSE = {'etag': 'ACAB'} + PROJECT = 'PROJECT' + TOPIC_NAME = 'topic_name' + SUB_NAME = 'sub_name' + PATH = 'projects/%s/subscriptions/%s:setIamPolicy' % ( + PROJECT, SUB_NAME) + + conn1 = _Connection() + conn2 = _Connection(RESPONSE) + CLIENT1 = _Client(project=PROJECT, connection=conn1) + CLIENT2 = _Client(project=PROJECT, connection=conn2) + topic = _Topic(TOPIC_NAME, client=CLIENT1) + subscription = self._makeOne(SUB_NAME, topic) + + policy = Policy() + new_policy = subscription.set_iam_policy(policy, client=CLIENT2) + + self.assertEqual(new_policy.etag, 'ACAB') + self.assertEqual(new_policy.version, None) + self.assertEqual(sorted(new_policy.owners), []) + self.assertEqual(sorted(new_policy.writers), []) + self.assertEqual(sorted(new_policy.readers), []) + + self.assertEqual(len(conn1._requested), 0) + self.assertEqual(len(conn2._requested), 1) + req = conn2._requested[0] + self.assertEqual(req['method'], 'POST') + self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['data'], {}) + class _Connection(object): diff --git a/gcloud/pubsub/test_topic.py b/gcloud/pubsub/test_topic.py index bb63a0c46834..fc9684c5f829 100644 --- a/gcloud/pubsub/test_topic.py +++ b/gcloud/pubsub/test_topic.py @@ -602,11 +602,12 @@ def test_set_iam_policy_w_alternate_client(self): self.assertEqual(req['data'], {}) def test_test_iam_permissions_w_bound_client(self): + from gcloud.pubsub.iam import _OWNER_ROLE, _WRITER_ROLE, _READER_ROLE TOPIC_NAME = 'topic_name' PROJECT = 'PROJECT' PATH = 'projects/%s/topics/%s:testIamPermissions' % ( PROJECT, TOPIC_NAME) - ROLES = ['roles/reader', 'roles/writer', 'roles/owner'] + ROLES = [_READER_ROLE, _WRITER_ROLE, _OWNER_ROLE] REQUESTED = { 'permissions': ROLES, } @@ -627,11 +628,12 @@ def test_test_iam_permissions_w_bound_client(self): self.assertEqual(req['data'], REQUESTED) def test_test_iam_permissions_w_alternate_client(self): + from gcloud.pubsub.iam import _OWNER_ROLE, _WRITER_ROLE, _READER_ROLE TOPIC_NAME = 'topic_name' PROJECT = 'PROJECT' PATH = 'projects/%s/topics/%s:testIamPermissions' % ( PROJECT, TOPIC_NAME) - ROLES = ['roles/reader', 'roles/writer', 'roles/owner'] + ROLES = [_READER_ROLE, _WRITER_ROLE, _OWNER_ROLE] REQUESTED = { 'permissions': ROLES, } diff --git a/gcloud/pubsub/topic.py b/gcloud/pubsub/topic.py index e4795b20a96e..9f1834bbd43f 100644 --- a/gcloud/pubsub/topic.py +++ b/gcloud/pubsub/topic.py @@ -286,7 +286,7 @@ def set_iam_policy(self, policy, client=None): :type policy: :class:`gcloud.pubsub.iam.Policy` :param policy: the new policy, typically fetched via - :meth:`getIamPolicy` and updated in place. + :meth:`get_iam_policy` and updated in place. :type client: :class:`gcloud.pubsub.client.Client` or ``NoneType`` :param client: the client to use. If not passed, falls back to the