Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: gslib/commands/notification.py

Issue 698893003: Update checked in version of gsutil to version 4.6 (Closed) Base URL: http://dart.googlecode.com/svn/third_party/gsutil/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « gslib/commands/mv.py ('k') | gslib/commands/perfdiag.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # -*- coding: utf-8 -*-
1 # Copyright 2013 Google Inc. All Rights Reserved. 2 # Copyright 2013 Google Inc. All Rights Reserved.
2 # 3 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 5 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 6 # You may obtain a copy of the License at
6 # 7 #
7 # http://www.apache.org/licenses/LICENSE-2.0 8 # http://www.apache.org/licenses/LICENSE-2.0
8 # 9 #
9 # Unless required by applicable law or agreed to in writing, software 10 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and 13 # See the License for the specific language governing permissions and
13 # limitations under the License. 14 # limitations under the License.
14 """This module provides the notification command to gsutil.""" 15 """This module provides the notification command to gsutil."""
15 16
17 from __future__ import absolute_import
18
16 import getopt 19 import getopt
17 import uuid 20 import uuid
18 21
19 from apiclient import discovery 22 from gslib.cloud_api import AccessDeniedException
20 from apiclient import errors as apiclient_errors
21 import boto
22
23 from gslib.command import Command 23 from gslib.command import Command
24 from gslib.command import COMMAND_NAME
25 from gslib.command import COMMAND_NAME_ALIASES
26 from gslib.command import FILE_URIS_OK
27 from gslib.command import MAX_ARGS
28 from gslib.command import MIN_ARGS
29 from gslib.command import NO_MAX 24 from gslib.command import NO_MAX
30 from gslib.command import PROVIDER_URIS_OK 25 from gslib.cs_api_map import ApiSelector
31 from gslib.command import SUPPORTED_SUB_ARGS
32 from gslib.command import URIS_START_ARG
33 from gslib.exception import CommandException 26 from gslib.exception import CommandException
34 from gslib.help_provider import CreateHelpText 27 from gslib.help_provider import CreateHelpText
35 from gslib.help_provider import HELP_NAME 28 from gslib.storage_url import StorageUrlFromString
36 from gslib.help_provider import HELP_NAME_ALIASES
37 from gslib.help_provider import HELP_ONE_LINE_SUMMARY
38 from gslib.help_provider import HELP_TEXT
39 from gslib.help_provider import HELP_TYPE
40 from gslib.help_provider import HelpType
41 from gslib.help_provider import SUBCOMMAND_HELP_TEXT
42 29
43 30
44 _WATCHBUCKET_SYNOPSIS = """ 31 _WATCHBUCKET_SYNOPSIS = """
45 gsutil notification watchbucket [-i id] [-t token] app_url bucket_uri... 32 gsutil notification watchbucket [-i id] [-t token] app_url bucket_url...
46 """ 33 """
47 34
48 _STOPCHANNEL_SYNOPSIS = """ 35 _STOPCHANNEL_SYNOPSIS = """
49 gsutil notification stopchannel channel_id resource_id 36 gsutil notification stopchannel channel_id resource_id
50 """ 37 """
51 38
52 _SYNOPSIS = _WATCHBUCKET_SYNOPSIS + _STOPCHANNEL_SYNOPSIS.lstrip('\n') 39 _SYNOPSIS = _WATCHBUCKET_SYNOPSIS + _STOPCHANNEL_SYNOPSIS.lstrip('\n')
53 40
54 _WATCHBUCKET_DESCRIPTION = """ 41 _WATCHBUCKET_DESCRIPTION = """
55 <B>WATCHBUCKET</B> 42 <B>WATCHBUCKET</B>
56 The watchbucket sub-command can be used to watch a bucket for object 43 The watchbucket sub-command can be used to watch a bucket for object changes.
57 changes. 44 A service account must be used when running this command.
58 45
59 The app_url parameter must be an HTTPS URL to an application that will be 46 The app_url parameter must be an HTTPS URL to an application that will be
60 notified of changes to any object in the bucket. The URL endpoint must be 47 notified of changes to any object in the bucket. The URL endpoint must be
61 a verified domain on your project. See 48 a verified domain on your project. See
62 `Notification Authorization <https://developers.google.com/storage/docs/object -change-notification#_Authorization>`_ 49 `Notification Authorization <https://developers.google.com/storage/docs/object -change-notification#_Authorization>`_
63 for details. 50 for details.
64 51
65 The optional id parameter can be used to assign a unique identifier to the 52 The optional id parameter can be used to assign a unique identifier to the
66 created notification channel. If not provided, a random UUID string will be 53 created notification channel. If not provided, a random UUID string will be
67 generated. 54 generated.
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 boto config file. 112 boto config file.
126 113
127 """ 114 """
128 115
129 NOTIFICATION_AUTHORIZATION_FAILED_MESSAGE = """ 116 NOTIFICATION_AUTHORIZATION_FAILED_MESSAGE = """
130 Watch bucket attempt failed: 117 Watch bucket attempt failed:
131 {watch_error} 118 {watch_error}
132 119
133 You attempted to watch a bucket with an application URL of: 120 You attempted to watch a bucket with an application URL of:
134 121
135 {watch_uri} 122 {watch_url}
136 123
137 which is not authorized for your project. Notification endpoint URLs must be 124 which is not authorized for your project. Please ensure that you are using
125 Service Account authentication and that the Service Account's project is
126 authorized for the application URL. Notification endpoint URLs must also be
138 whitelisted in your Cloud Console project. To do that, the domain must also be 127 whitelisted in your Cloud Console project. To do that, the domain must also be
139 verified using Google Webmaster Tools. For instructions, please see: 128 verified using Google Webmaster Tools. For instructions, please see:
140 129
141 https://developers.google.com/storage/docs/object-change-notification#_Authori zation 130 https://developers.google.com/storage/docs/object-change-notification#_Authori zation
142 """ 131 """
143 132
144 _detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION) 133 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
145 134
146 _watchbucket_help_text = ( 135 _watchbucket_help_text = (
147 CreateHelpText(_WATCHBUCKET_SYNOPSIS, _WATCHBUCKET_DESCRIPTION)) 136 CreateHelpText(_WATCHBUCKET_SYNOPSIS, _WATCHBUCKET_DESCRIPTION))
148 _stopchannel_help_text = ( 137 _stopchannel_help_text = (
149 CreateHelpText(_STOPCHANNEL_SYNOPSIS, _STOPCHANNEL_DESCRIPTION)) 138 CreateHelpText(_STOPCHANNEL_SYNOPSIS, _STOPCHANNEL_DESCRIPTION))
150 139
151 DISCOVERY_SERVICE_URL = boto.config.get_value(
152 'GSUtil', 'discovery_service_url', None)
153 JSON_API_VERSION = boto.config.get_value(
154 'GSUtil', 'json_api_version', 'v1beta2')
155
156 140
157 class NotificationCommand(Command): 141 class NotificationCommand(Command):
158 """Implementation of gsutil notification command.""" 142 """Implementation of gsutil notification command."""
159 143
160 # Command specification (processed by parent class). 144 # Command specification. See base class for documentation.
161 command_spec = { 145 command_spec = Command.CreateCommandSpec(
162 # Name of command. 146 'notification',
163 COMMAND_NAME: 'notification', 147 command_name_aliases=[
164 # List of command name aliases. 148 'notify', 'notifyconfig', 'notifications', 'notif'],
165 COMMAND_NAME_ALIASES: ['notify', 'notifyconfig', 'notifications', 149 min_args=3,
166 'notif'], 150 max_args=NO_MAX,
167 # Min number of args required by this command. 151 supported_sub_args='i:t:',
168 MIN_ARGS: 3, 152 file_url_ok=False,
169 # Max number of args required by this command, or NO_MAX. 153 provider_url_ok=False,
170 MAX_ARGS: NO_MAX, 154 urls_start_arg=1,
171 # Getopt-style string specifying acceptable sub args. 155 gs_api_support=[ApiSelector.JSON],
172 SUPPORTED_SUB_ARGS: 'i:t:', 156 gs_default_api=ApiSelector.JSON,
173 # True if file URIs acceptable for this command. 157 )
174 FILE_URIS_OK: True, 158 # Help specification. See help_provider.py for documentation.
175 # True if provider-only URIs acceptable for this command. 159 help_spec = Command.HelpSpec(
176 PROVIDER_URIS_OK: False, 160 help_name='notification',
177 # Index in args of first URI arg. 161 help_name_aliases=['watchbucket', 'stopchannel', 'notifyconfig'],
178 URIS_START_ARG: 1, 162 help_type='command_help',
179 } 163 help_one_line_summary='Configure object change notification',
180 help_spec = { 164 help_text=_DETAILED_HELP_TEXT,
181 # Name of command or auxiliary help info for which this help applies. 165 subcommand_help_text={'watchbucket': _watchbucket_help_text,
182 HELP_NAME: 'notification', 166 'stopchannel': _stopchannel_help_text},
183 # List of help name aliases. 167 )
184 HELP_NAME_ALIASES: ['watchbucket', 'stopchannel', 'notifyconfig'],
185 # Type of help:
186 HELP_TYPE: HelpType.COMMAND_HELP,
187 # One line summary of this help.
188 HELP_ONE_LINE_SUMMARY: 'Configure object change notification',
189 # The full help text.
190 HELP_TEXT: _detailed_help_text,
191 # Help text for sub-commands.
192 SUBCOMMAND_HELP_TEXT : {'watchbucket' : _watchbucket_help_text,
193 'stopchannel' : _stopchannel_help_text},
194 }
195 168
196 def _WatchBucket(self): 169 def _WatchBucket(self):
170 """Creates a watch on a bucket given in self.args."""
171 self.CheckArguments()
197 identifier = None 172 identifier = None
198 client_token = None 173 client_token = None
199 if self.sub_opts: 174 if self.sub_opts:
200 for o, a in self.sub_opts: 175 for o, a in self.sub_opts:
201 if o == '-i': 176 if o == '-i':
202 identifier = a 177 identifier = a
203 if o == '-t': 178 if o == '-t':
204 client_token = a 179 client_token = a
205 180
206 identifier = identifier or str(uuid.uuid4()) 181 identifier = identifier or str(uuid.uuid4())
207 watch_uri = self.args[0] 182 watch_url = self.args[0]
208 bucket_arg = self.args[-1] 183 bucket_arg = self.args[-1]
209 184
210 if not watch_uri.lower().startswith('https://'): 185 if not watch_url.lower().startswith('https://'):
211 raise CommandException('The application URL must be an https:// URL.') 186 raise CommandException('The application URL must be an https:// URL.')
212 187
213 bucket_uri = self.suri_builder.StorageUri(bucket_arg) 188 bucket_url = StorageUrlFromString(bucket_arg)
214 if bucket_uri.get_provider().name != 'google': 189 if not (bucket_url.IsBucket() and bucket_url.scheme == 'gs'):
215 raise CommandException( 190 raise CommandException(
216 'The %s command can only be used with gs:// bucket URIs.' % 191 'The %s command can only be used with gs:// bucket URLs.' %
217 self.command_name) 192 self.command_name)
218 if not bucket_uri.names_bucket(): 193 if not bucket_url.IsBucket():
219 raise CommandException('URI must name a bucket for the %s command.' % 194 raise CommandException('URL must name a bucket for the %s command.' %
220 self.command_name) 195 self.command_name)
221 196
222 self.logger.info('Watching bucket %s with application URL %s ...', 197 self.logger.info('Watching bucket %s with application URL %s ...',
223 bucket_uri, watch_uri) 198 bucket_url, watch_url)
224 199
225 bucket = bucket_uri.get_bucket() 200 try:
226 auth_handler = bucket.connection._auth_handler 201 channel = self.gsutil_api.WatchBucket(
227 oauth2_client = getattr(auth_handler, 'oauth2_client', None) 202 bucket_url.bucket_name, watch_url, identifier, token=client_token,
228 if not oauth2_client: 203 provider=bucket_url.scheme)
229 raise CommandException( 204 except AccessDeniedException, e:
230 'The %s command requires using OAuth credentials.' % 205 self.logger.warn(NOTIFICATION_AUTHORIZATION_FAILED_MESSAGE.format(
231 self.command_name) 206 watch_error=str(e), watch_url=watch_url))
207 raise
232 208
233 http = oauth2_client.CreateHttpRequest() 209 channel_id = channel.id
234 kwargs = {'http': http} 210 resource_id = channel.resourceId
235 if DISCOVERY_SERVICE_URL: 211 client_token = channel.token
236 kwargs['discoveryServiceUrl'] = DISCOVERY_SERVICE_URL
237 service = discovery.build(
238 'storage', JSON_API_VERSION, **kwargs)
239
240 body = {'type': 'WEB_HOOK',
241 'address': watch_uri,
242 'id': identifier}
243 if client_token:
244 body['token'] = client_token
245 request = service.objects().watchAll(body=body, bucket=bucket.name)
246 request.headers['authorization'] = oauth2_client.GetAuthorizationHeader()
247 try:
248 response = request.execute()
249 except apiclient_errors.HttpError, e:
250 if e.resp.status == 401 and 'Unauthorized' in str(e):
251 self.logger.warn(NOTIFICATION_AUTHORIZATION_FAILED_MESSAGE.format(
252 watch_error=str(e), watch_uri=watch_uri))
253 return 1
254 else:
255 raise
256
257 channel_id = response['id']
258 resource_id = response['resourceId']
259 client_token = response.get('token', '')
260 self.logger.info('Successfully created watch notification channel.') 212 self.logger.info('Successfully created watch notification channel.')
261 self.logger.info('Watch channel identifier: %s', channel_id) 213 self.logger.info('Watch channel identifier: %s', channel_id)
262 self.logger.info('Canonicalized resource identifier: %s', resource_id) 214 self.logger.info('Canonicalized resource identifier: %s', resource_id)
263 self.logger.info('Client state token: %s', client_token) 215 self.logger.info('Client state token: %s', client_token)
264 216
265 return 0 217 return 0
266 218
267 def _StopChannel(self): 219 def _StopChannel(self):
268 channel_id = self.args[0] 220 channel_id = self.args[0]
269 resource_id = self.args[1] 221 resource_id = self.args[1]
270 222
271 uri = self.suri_builder.StorageUri('gs://')
272 self.logger.info('Removing channel %s with resource identifier %s ...', 223 self.logger.info('Removing channel %s with resource identifier %s ...',
273 channel_id, resource_id) 224 channel_id, resource_id)
274 225 self.gsutil_api.StopChannel(channel_id, resource_id, provider='gs')
275 auth_handler = uri.connect()._auth_handler
276 oauth2_client = getattr(auth_handler, 'oauth2_client', None)
277 if not oauth2_client:
278 raise CommandException(
279 'The %s command requires using OAuth credentials.' %
280 self.command_name)
281
282 http = oauth2_client.CreateHttpRequest()
283 kwargs = {'http': http}
284 if DISCOVERY_SERVICE_URL:
285 kwargs['discoveryServiceUrl'] = DISCOVERY_SERVICE_URL
286 service = discovery.build(
287 'storage', JSON_API_VERSION, **kwargs)
288
289 body = {'id': channel_id,
290 'resourceId': resource_id}
291 request = service.channels().stop(body=body)
292 request.headers['authorization'] = oauth2_client.GetAuthorizationHeader()
293 request.execute()
294 self.logger.info('Succesfully removed channel.') 226 self.logger.info('Succesfully removed channel.')
295 227
296 return 0 228 return 0
297 229
298 def _RunSubCommand(self, func): 230 def _RunSubCommand(self, func):
299 try: 231 try:
300 (self.sub_opts, self.args) = getopt.getopt( 232 (self.sub_opts, self.args) = getopt.getopt(
301 self.args, self.command_spec[SUPPORTED_SUB_ARGS]) 233 self.args, self.command_spec.supported_sub_args)
302 return func() 234 return func()
303 except getopt.GetoptError, e: 235 except getopt.GetoptError, e:
304 raise CommandException('%s for "%s" command.' % (e.msg, 236 raise CommandException('%s for "%s" command.' % (e.msg,
305 self.command_name)) 237 self.command_name))
306 238
307 def RunCommand(self): 239 def RunCommand(self):
240 """Command entry point for the notification command."""
308 subcommand = self.args.pop(0) 241 subcommand = self.args.pop(0)
242
309 if subcommand == 'watchbucket': 243 if subcommand == 'watchbucket':
310 return self._RunSubCommand(self._WatchBucket) 244 return self._RunSubCommand(self._WatchBucket)
311 elif subcommand == 'stopchannel': 245 elif subcommand == 'stopchannel':
312 return self._RunSubCommand(self._StopChannel) 246 return self._RunSubCommand(self._StopChannel)
313 else: 247 else:
314 raise CommandException('Invalid subcommand "%s" for the %s command.' % 248 raise CommandException('Invalid subcommand "%s" for the %s command.' %
315 (subcommand, self.command_name)) 249 (subcommand, self.command_name))
OLDNEW
« no previous file with comments | « gslib/commands/mv.py ('k') | gslib/commands/perfdiag.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698