Skip to content

Commit ccbd95c

Browse files
authored
Capture server-generated 'writerIdentity' during 'LogSink.create' (#4707)
1 parent 1d88cdc commit ccbd95c

File tree

6 files changed

+87
-52
lines changed

6 files changed

+87
-52
lines changed

google/cloud/logging/_gax.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,17 @@ def sink_create(self, project, sink_name, filter_, destination,
217217
:param unique_writer_identity: (Optional) determines the kind of
218218
IAM identity returned as
219219
writer_identity in the new sink.
220+
221+
:rtype: dict
222+
:returns: The sink resource returned from the API (converted from a
223+
protobuf to a dictionary).
220224
"""
221225
options = None
222226
parent = 'projects/%s' % (project,)
223227
sink_pb = LogSink(name=sink_name, filter=filter_,
224228
destination=destination)
225229
try:
226-
self._gax_api.create_sink(
230+
created_pb = self._gax_api.create_sink(
227231
parent,
228232
sink_pb,
229233
unique_writer_identity=unique_writer_identity,
@@ -234,6 +238,7 @@ def sink_create(self, project, sink_name, filter_, destination,
234238
path = 'projects/%s/sinks/%s' % (project, sink_name)
235239
raise Conflict(path)
236240
raise
241+
return MessageToDict(created_pb)
237242

238243
def sink_get(self, project, sink_name):
239244
"""API call: retrieve a sink resource.

google/cloud/logging/_http.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ def sink_create(self, project, sink_name, filter_, destination,
254254
:param unique_writer_identity: (Optional) determines the kind of
255255
IAM identity returned as
256256
writer_identity in the new sink.
257+
258+
:rtype: dict
259+
:returns: The returned (created) resource.
257260
"""
258261
target = '/projects/%s/sinks' % (project,)
259262
data = {
@@ -262,7 +265,7 @@ def sink_create(self, project, sink_name, filter_, destination,
262265
'destination': destination,
263266
}
264267
query_params = {'uniqueWriterIdentity': unique_writer_identity}
265-
self.api_request(
268+
return self.api_request(
266269
method='POST',
267270
path=target,
268271
data=data,

google/cloud/logging/sink.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ def writer_identity(self):
7171
"""Identity used for exports via the sink"""
7272
return self._writer_identity
7373

74+
def _update_from_api_repr(self, resource):
75+
"""Helper for API methods returning sink resources."""
76+
self.destination = resource['destination']
77+
self.filter_ = resource.get('filter')
78+
self._writer_identity = resource.get('writerIdentity')
79+
7480
@classmethod
7581
def from_api_repr(cls, resource, client):
7682
"""Factory: construct a sink given its API representation
@@ -89,10 +95,8 @@ def from_api_repr(cls, resource, client):
8995
from the client.
9096
"""
9197
sink_name = resource['name']
92-
destination = resource['destination']
93-
filter_ = resource.get('filter')
94-
instance = cls(sink_name, filter_, destination, client=client)
95-
instance._writer_identity = resource.get('writerIdentity')
98+
instance = cls(sink_name, client=client)
99+
instance._update_from_api_repr(resource)
96100
return instance
97101

98102
def _require_client(self, client):
@@ -127,10 +131,11 @@ def create(self, client=None, unique_writer_identity=False):
127131
writer_identity in the new sink.
128132
"""
129133
client = self._require_client(client)
130-
client.sinks_api.sink_create(
134+
resource = client.sinks_api.sink_create(
131135
self.project, self.name, self.filter_, self.destination,
132136
unique_writer_identity=unique_writer_identity,
133137
)
138+
self._update_from_api_repr(resource)
134139

135140
def exists(self, client=None):
136141
"""API call: test for the existence of the sink via a GET request
@@ -168,9 +173,7 @@ def reload(self, client=None):
168173
"""
169174
client = self._require_client(client)
170175
resource = client.sinks_api.sink_get(self.project, self.name)
171-
self.destination = resource['destination']
172-
self.filter_ = resource.get('filter')
173-
self._writer_identity = resource.get('writerIdentity')
176+
self._update_from_api_repr(resource)
174177

175178
def update(self, client=None):
176179
"""API call: update sink configuration via a PUT request

tests/unit/test__gax.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ class Test_SinksAPI(_Base, unittest.TestCase):
619619
SINK_NAME = 'sink_name'
620620
SINK_PATH = 'projects/%s/sinks/%s' % (_Base.PROJECT, SINK_NAME)
621621
DESTINATION_URI = 'faux.googleapis.com/destination'
622+
SINK_WRITER_IDENTITY = 'serviceAccount:[email protected]'
622623

623624
@staticmethod
624625
def _get_target_class():
@@ -719,6 +720,7 @@ def test_sink_create_error(self):
719720

720721
def test_sink_create_conflict(self):
721722
from google.cloud.exceptions import Conflict
723+
from google.cloud.proto.logging.v2.logging_config_pb2 import LogSink
722724

723725
gax_api = _GAXSinksAPI(_create_sink_conflict=True)
724726
api = self._make_one(gax_api, None)
@@ -728,16 +730,7 @@ def test_sink_create_conflict(self):
728730
self.PROJECT, self.SINK_NAME, self.FILTER,
729731
self.DESTINATION_URI)
730732

731-
def test_sink_create_ok(self):
732-
from google.cloud.proto.logging.v2.logging_config_pb2 import LogSink
733-
734-
gax_api = _GAXSinksAPI()
735-
api = self._make_one(gax_api, None)
736-
737-
api.sink_create(
738-
self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI)
739-
740-
parent, sink, options, unique_writer_identity = (
733+
parent, sink, unique_writer_identity, options = (
741734
gax_api._create_sink_called_with)
742735
self.assertEqual(parent, self.PROJECT_PATH)
743736
self.assertIsInstance(sink, LogSink)
@@ -747,24 +740,36 @@ def test_sink_create_ok(self):
747740
self.assertIsNone(options)
748741
self.assertFalse(unique_writer_identity)
749742

750-
def test_sink_create_with_unique_writer_identity(self):
743+
def test_sink_create_ok(self):
751744
from google.cloud.proto.logging.v2.logging_config_pb2 import LogSink
752745

753746
gax_api = _GAXSinksAPI()
754747
api = self._make_one(gax_api, None)
755-
api.sink_create(
756-
self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI,
748+
749+
returned = api.sink_create(
750+
self.PROJECT,
751+
self.SINK_NAME,
752+
self.FILTER,
753+
self.DESTINATION_URI,
757754
unique_writer_identity=True,
758755
)
759-
parent, sink, options, unique_writer_identity = (
756+
757+
self.assertEqual(returned, {
758+
'name': self.SINK_NAME,
759+
'filter': self.FILTER,
760+
'destination': self.DESTINATION_URI,
761+
'writerIdentity': self.SINK_WRITER_IDENTITY,
762+
})
763+
764+
parent, sink, unique_writer_identity, options = (
760765
gax_api._create_sink_called_with)
761766
self.assertEqual(parent, self.PROJECT_PATH)
762767
self.assertIsInstance(sink, LogSink)
763768
self.assertEqual(sink.name, self.SINK_NAME)
764769
self.assertEqual(sink.filter, self.FILTER)
765770
self.assertEqual(sink.destination, self.DESTINATION_URI)
766-
self.assertIsNone(options)
767771
self.assertTrue(unique_writer_identity)
772+
self.assertIsNone(options)
768773

769774
def test_sink_get_error(self):
770775
from google.cloud.exceptions import NotFound
@@ -1482,14 +1487,22 @@ def list_sinks(self, parent, page_size, options):
14821487
self._list_sinks_called_with = parent, page_size, options
14831488
return self._list_sinks_response
14841489

1485-
def create_sink(self, parent, sink, options, unique_writer_identity=False):
1490+
def create_sink(self, parent, sink, unique_writer_identity, options):
14861491
from google.gax.errors import GaxError
1492+
from google.cloud.proto.logging.v2.logging_config_pb2 import LogSink
14871493

1488-
self._create_sink_called_with = parent, sink, options, unique_writer_identity
1494+
self._create_sink_called_with = (
1495+
parent, sink, unique_writer_identity, options)
14891496
if self._random_gax_error:
14901497
raise GaxError('error')
14911498
if self._create_sink_conflict:
14921499
raise GaxError('conflict', self._make_grpc_failed_precondition())
1500+
return LogSink(
1501+
name=sink.name,
1502+
destination=sink.destination,
1503+
filter=sink.filter,
1504+
writer_identity=Test_SinksAPI.SINK_WRITER_IDENTITY,
1505+
)
14931506

14941507
def get_sink(self, sink_name, options):
14951508
from google.gax.errors import GaxError

tests/unit/test__http.py

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ class Test_SinksAPI(unittest.TestCase):
334334
SINK_NAME = 'sink_name'
335335
SINK_PATH = 'projects/%s/sinks/%s' % (PROJECT, SINK_NAME)
336336
DESTINATION_URI = 'faux.googleapis.com/destination'
337+
WRITER_IDENTITY = 'serviceAccount:[email protected]'
337338

338339
@staticmethod
339340
def _get_target_class():
@@ -438,7 +439,7 @@ def test_list_sinks_w_paging(self):
438439
def test_sink_create_conflict(self):
439440
from google.cloud.exceptions import Conflict
440441

441-
SENT = {
442+
sent = {
442443
'name': self.SINK_NAME,
443444
'filter': self.FILTER,
444445
'destination': self.DESTINATION_URI,
@@ -453,47 +454,36 @@ def test_sink_create_conflict(self):
453454
self.PROJECT, self.SINK_NAME, self.FILTER,
454455
self.DESTINATION_URI)
455456

456-
self.assertEqual(conn._called_with['method'], 'POST')
457457
path = '/projects/%s/sinks' % (self.PROJECT,)
458-
self.assertEqual(conn._called_with['path'], path)
459-
self.assertEqual(conn._called_with['data'], SENT)
460-
461-
def test_sink_create_ok(self):
462-
SENT = {
463-
'name': self.SINK_NAME,
464-
'filter': self.FILTER,
465-
'destination': self.DESTINATION_URI,
458+
expected = {
459+
'method': 'POST',
460+
'path': path,
461+
'data': sent,
462+
'query_params': {'uniqueWriterIdentity': False},
466463
}
467-
conn = _Connection({})
468-
client = _Client(conn)
469-
api = self._make_one(client)
470-
471-
api.sink_create(
472-
self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI)
473-
474-
self.assertEqual(conn._called_with['method'], 'POST')
475-
path = '/projects/%s/sinks' % (self.PROJECT,)
476-
self.assertEqual(conn._called_with['path'], path)
477-
self.assertEqual(conn._called_with['data'], SENT)
464+
self.assertEqual(conn._called_with, expected)
478465

479-
def test_sink_create_unique_writer_identity(self):
466+
def test_sink_create_ok(self):
480467
sent = {
481468
'name': self.SINK_NAME,
482469
'filter': self.FILTER,
483470
'destination': self.DESTINATION_URI,
484471
}
485-
486-
conn = _Connection({})
472+
after_create = sent.copy()
473+
after_create['writerIdentity'] = self.WRITER_IDENTITY
474+
conn = _Connection(after_create)
487475
client = _Client(conn)
488476
api = self._make_one(client)
489477

490-
api.sink_create(
478+
returned = api.sink_create(
491479
self.PROJECT,
492480
self.SINK_NAME,
493481
self.FILTER,
494482
self.DESTINATION_URI,
495483
unique_writer_identity=True,
496484
)
485+
486+
self.assertEqual(returned, after_create)
497487
path = '/projects/%s/sinks' % (self.PROJECT,)
498488
expected = {
499489
'method': 'POST',

tests/unit/test_sink.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,22 @@ def test_from_api_repr_full(self):
9797
def test_create_w_bound_client(self):
9898
client = _Client(project=self.PROJECT)
9999
api = client.sinks_api = _DummySinksAPI()
100+
api._sink_create_response = {
101+
'name': self.SINK_NAME,
102+
'filter': self.FILTER,
103+
'destination': self.DESTINATION_URI,
104+
'writerIdentity': self.WRITER_IDENTITY,
105+
}
100106
sink = self._make_one(self.SINK_NAME, self.FILTER,
101107
self.DESTINATION_URI,
102108
client=client)
103109

104110
sink.create()
105111

112+
self.assertEqual(sink.name, self.SINK_NAME)
113+
self.assertEqual(sink.filter_, self.FILTER)
114+
self.assertEqual(sink.destination, self.DESTINATION_URI)
115+
self.assertEqual(sink.writer_identity, self.WRITER_IDENTITY)
106116
self.assertEqual(
107117
api._sink_create_called_with,
108118
(
@@ -121,9 +131,19 @@ def test_create_w_alternate_client(self):
121131
self.DESTINATION_URI,
122132
client=client1)
123133
api = client2.sinks_api = _DummySinksAPI()
134+
api._sink_create_response = {
135+
'name': self.SINK_NAME,
136+
'filter': self.FILTER,
137+
'destination': self.DESTINATION_URI,
138+
'writerIdentity': self.WRITER_IDENTITY,
139+
}
124140

125141
sink.create(client=client2, unique_writer_identity=True)
126142

143+
self.assertEqual(sink.name, self.SINK_NAME)
144+
self.assertEqual(sink.filter_, self.FILTER)
145+
self.assertEqual(sink.destination, self.DESTINATION_URI)
146+
self.assertEqual(sink.writer_identity, self.WRITER_IDENTITY)
127147
self.assertEqual(
128148
api._sink_create_called_with,
129149
(
@@ -273,6 +293,7 @@ def sink_create(self, project, sink_name, filter_, destination,
273293
unique_writer_identity=False):
274294
self._sink_create_called_with = (
275295
project, sink_name, filter_, destination, unique_writer_identity)
296+
return self._sink_create_response
276297

277298
def sink_get(self, project, sink_name):
278299
from google.cloud.exceptions import NotFound

0 commit comments

Comments
 (0)