| Index: gslib/commands/notification.py
|
| ===================================================================
|
| --- gslib/commands/notification.py (revision 33376)
|
| +++ gslib/commands/notification.py (working copy)
|
| @@ -1,3 +1,4 @@
|
| +# -*- coding: utf-8 -*-
|
| # Copyright 2013 Google Inc. All Rights Reserved.
|
| #
|
| # Licensed under the Apache License, Version 2.0 (the "License");
|
| @@ -13,36 +14,22 @@
|
| # limitations under the License.
|
| """This module provides the notification command to gsutil."""
|
|
|
| +from __future__ import absolute_import
|
| +
|
| import getopt
|
| import uuid
|
|
|
| -from apiclient import discovery
|
| -from apiclient import errors as apiclient_errors
|
| -import boto
|
| -
|
| +from gslib.cloud_api import AccessDeniedException
|
| from gslib.command import Command
|
| -from gslib.command import COMMAND_NAME
|
| -from gslib.command import COMMAND_NAME_ALIASES
|
| -from gslib.command import FILE_URIS_OK
|
| -from gslib.command import MAX_ARGS
|
| -from gslib.command import MIN_ARGS
|
| from gslib.command import NO_MAX
|
| -from gslib.command import PROVIDER_URIS_OK
|
| -from gslib.command import SUPPORTED_SUB_ARGS
|
| -from gslib.command import URIS_START_ARG
|
| +from gslib.cs_api_map import ApiSelector
|
| from gslib.exception import CommandException
|
| from gslib.help_provider import CreateHelpText
|
| -from gslib.help_provider import HELP_NAME
|
| -from gslib.help_provider import HELP_NAME_ALIASES
|
| -from gslib.help_provider import HELP_ONE_LINE_SUMMARY
|
| -from gslib.help_provider import HELP_TEXT
|
| -from gslib.help_provider import HELP_TYPE
|
| -from gslib.help_provider import HelpType
|
| -from gslib.help_provider import SUBCOMMAND_HELP_TEXT
|
| +from gslib.storage_url import StorageUrlFromString
|
|
|
|
|
| _WATCHBUCKET_SYNOPSIS = """
|
| - gsutil notification watchbucket [-i id] [-t token] app_url bucket_uri...
|
| + gsutil notification watchbucket [-i id] [-t token] app_url bucket_url...
|
| """
|
|
|
| _STOPCHANNEL_SYNOPSIS = """
|
| @@ -53,8 +40,8 @@
|
|
|
| _WATCHBUCKET_DESCRIPTION = """
|
| <B>WATCHBUCKET</B>
|
| - The watchbucket sub-command can be used to watch a bucket for object
|
| - changes.
|
| + The watchbucket sub-command can be used to watch a bucket for object changes.
|
| + A service account must be used when running this command.
|
|
|
| The app_url parameter must be an HTTPS URL to an application that will be
|
| notified of changes to any object in the bucket. The URL endpoint must be
|
| @@ -132,68 +119,56 @@
|
|
|
| You attempted to watch a bucket with an application URL of:
|
|
|
| - {watch_uri}
|
| + {watch_url}
|
|
|
| -which is not authorized for your project. Notification endpoint URLs must be
|
| +which is not authorized for your project. Please ensure that you are using
|
| +Service Account authentication and that the Service Account's project is
|
| +authorized for the application URL. Notification endpoint URLs must also be
|
| whitelisted in your Cloud Console project. To do that, the domain must also be
|
| verified using Google Webmaster Tools. For instructions, please see:
|
|
|
| https://developers.google.com/storage/docs/object-change-notification#_Authorization
|
| """
|
|
|
| -_detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
|
| +_DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
|
|
|
| _watchbucket_help_text = (
|
| CreateHelpText(_WATCHBUCKET_SYNOPSIS, _WATCHBUCKET_DESCRIPTION))
|
| _stopchannel_help_text = (
|
| CreateHelpText(_STOPCHANNEL_SYNOPSIS, _STOPCHANNEL_DESCRIPTION))
|
|
|
| -DISCOVERY_SERVICE_URL = boto.config.get_value(
|
| - 'GSUtil', 'discovery_service_url', None)
|
| -JSON_API_VERSION = boto.config.get_value(
|
| - 'GSUtil', 'json_api_version', 'v1beta2')
|
|
|
| -
|
| class NotificationCommand(Command):
|
| """Implementation of gsutil notification command."""
|
|
|
| - # Command specification (processed by parent class).
|
| - command_spec = {
|
| - # Name of command.
|
| - COMMAND_NAME: 'notification',
|
| - # List of command name aliases.
|
| - COMMAND_NAME_ALIASES: ['notify', 'notifyconfig', 'notifications',
|
| - 'notif'],
|
| - # Min number of args required by this command.
|
| - MIN_ARGS: 3,
|
| - # Max number of args required by this command, or NO_MAX.
|
| - MAX_ARGS: NO_MAX,
|
| - # Getopt-style string specifying acceptable sub args.
|
| - SUPPORTED_SUB_ARGS: 'i:t:',
|
| - # True if file URIs acceptable for this command.
|
| - FILE_URIS_OK: True,
|
| - # True if provider-only URIs acceptable for this command.
|
| - PROVIDER_URIS_OK: False,
|
| - # Index in args of first URI arg.
|
| - URIS_START_ARG: 1,
|
| - }
|
| - help_spec = {
|
| - # Name of command or auxiliary help info for which this help applies.
|
| - HELP_NAME: 'notification',
|
| - # List of help name aliases.
|
| - HELP_NAME_ALIASES: ['watchbucket', 'stopchannel', 'notifyconfig'],
|
| - # Type of help:
|
| - HELP_TYPE: HelpType.COMMAND_HELP,
|
| - # One line summary of this help.
|
| - HELP_ONE_LINE_SUMMARY: 'Configure object change notification',
|
| - # The full help text.
|
| - HELP_TEXT: _detailed_help_text,
|
| - # Help text for sub-commands.
|
| - SUBCOMMAND_HELP_TEXT : {'watchbucket' : _watchbucket_help_text,
|
| - 'stopchannel' : _stopchannel_help_text},
|
| - }
|
| + # Command specification. See base class for documentation.
|
| + command_spec = Command.CreateCommandSpec(
|
| + 'notification',
|
| + command_name_aliases=[
|
| + 'notify', 'notifyconfig', 'notifications', 'notif'],
|
| + min_args=3,
|
| + max_args=NO_MAX,
|
| + supported_sub_args='i:t:',
|
| + file_url_ok=False,
|
| + provider_url_ok=False,
|
| + urls_start_arg=1,
|
| + gs_api_support=[ApiSelector.JSON],
|
| + gs_default_api=ApiSelector.JSON,
|
| + )
|
| + # Help specification. See help_provider.py for documentation.
|
| + help_spec = Command.HelpSpec(
|
| + help_name='notification',
|
| + help_name_aliases=['watchbucket', 'stopchannel', 'notifyconfig'],
|
| + help_type='command_help',
|
| + help_one_line_summary='Configure object change notification',
|
| + help_text=_DETAILED_HELP_TEXT,
|
| + subcommand_help_text={'watchbucket': _watchbucket_help_text,
|
| + 'stopchannel': _stopchannel_help_text},
|
| + )
|
|
|
| def _WatchBucket(self):
|
| + """Creates a watch on a bucket given in self.args."""
|
| + self.CheckArguments()
|
| identifier = None
|
| client_token = None
|
| if self.sub_opts:
|
| @@ -204,59 +179,36 @@
|
| client_token = a
|
|
|
| identifier = identifier or str(uuid.uuid4())
|
| - watch_uri = self.args[0]
|
| + watch_url = self.args[0]
|
| bucket_arg = self.args[-1]
|
|
|
| - if not watch_uri.lower().startswith('https://'):
|
| + if not watch_url.lower().startswith('https://'):
|
| raise CommandException('The application URL must be an https:// URL.')
|
|
|
| - bucket_uri = self.suri_builder.StorageUri(bucket_arg)
|
| - if bucket_uri.get_provider().name != 'google':
|
| + bucket_url = StorageUrlFromString(bucket_arg)
|
| + if not (bucket_url.IsBucket() and bucket_url.scheme == 'gs'):
|
| raise CommandException(
|
| - 'The %s command can only be used with gs:// bucket URIs.' %
|
| + 'The %s command can only be used with gs:// bucket URLs.' %
|
| self.command_name)
|
| - if not bucket_uri.names_bucket():
|
| - raise CommandException('URI must name a bucket for the %s command.' %
|
| + if not bucket_url.IsBucket():
|
| + raise CommandException('URL must name a bucket for the %s command.' %
|
| self.command_name)
|
|
|
| self.logger.info('Watching bucket %s with application URL %s ...',
|
| - bucket_uri, watch_uri)
|
| + bucket_url, watch_url)
|
|
|
| - bucket = bucket_uri.get_bucket()
|
| - auth_handler = bucket.connection._auth_handler
|
| - oauth2_client = getattr(auth_handler, 'oauth2_client', None)
|
| - if not oauth2_client:
|
| - raise CommandException(
|
| - 'The %s command requires using OAuth credentials.' %
|
| - self.command_name)
|
| -
|
| - http = oauth2_client.CreateHttpRequest()
|
| - kwargs = {'http': http}
|
| - if DISCOVERY_SERVICE_URL:
|
| - kwargs['discoveryServiceUrl'] = DISCOVERY_SERVICE_URL
|
| - service = discovery.build(
|
| - 'storage', JSON_API_VERSION, **kwargs)
|
| -
|
| - body = {'type': 'WEB_HOOK',
|
| - 'address': watch_uri,
|
| - 'id': identifier}
|
| - if client_token:
|
| - body['token'] = client_token
|
| - request = service.objects().watchAll(body=body, bucket=bucket.name)
|
| - request.headers['authorization'] = oauth2_client.GetAuthorizationHeader()
|
| try:
|
| - response = request.execute()
|
| - except apiclient_errors.HttpError, e:
|
| - if e.resp.status == 401 and 'Unauthorized' in str(e):
|
| - self.logger.warn(NOTIFICATION_AUTHORIZATION_FAILED_MESSAGE.format(
|
| - watch_error=str(e), watch_uri=watch_uri))
|
| - return 1
|
| - else:
|
| - raise
|
| + channel = self.gsutil_api.WatchBucket(
|
| + bucket_url.bucket_name, watch_url, identifier, token=client_token,
|
| + provider=bucket_url.scheme)
|
| + except AccessDeniedException, e:
|
| + self.logger.warn(NOTIFICATION_AUTHORIZATION_FAILED_MESSAGE.format(
|
| + watch_error=str(e), watch_url=watch_url))
|
| + raise
|
|
|
| - channel_id = response['id']
|
| - resource_id = response['resourceId']
|
| - client_token = response.get('token', '')
|
| + channel_id = channel.id
|
| + resource_id = channel.resourceId
|
| + client_token = channel.token
|
| self.logger.info('Successfully created watch notification channel.')
|
| self.logger.info('Watch channel identifier: %s', channel_id)
|
| self.logger.info('Canonicalized resource identifier: %s', resource_id)
|
| @@ -268,29 +220,9 @@
|
| channel_id = self.args[0]
|
| resource_id = self.args[1]
|
|
|
| - uri = self.suri_builder.StorageUri('gs://')
|
| self.logger.info('Removing channel %s with resource identifier %s ...',
|
| channel_id, resource_id)
|
| -
|
| - auth_handler = uri.connect()._auth_handler
|
| - oauth2_client = getattr(auth_handler, 'oauth2_client', None)
|
| - if not oauth2_client:
|
| - raise CommandException(
|
| - 'The %s command requires using OAuth credentials.' %
|
| - self.command_name)
|
| -
|
| - http = oauth2_client.CreateHttpRequest()
|
| - kwargs = {'http': http}
|
| - if DISCOVERY_SERVICE_URL:
|
| - kwargs['discoveryServiceUrl'] = DISCOVERY_SERVICE_URL
|
| - service = discovery.build(
|
| - 'storage', JSON_API_VERSION, **kwargs)
|
| -
|
| - body = {'id': channel_id,
|
| - 'resourceId': resource_id}
|
| - request = service.channels().stop(body=body)
|
| - request.headers['authorization'] = oauth2_client.GetAuthorizationHeader()
|
| - request.execute()
|
| + self.gsutil_api.StopChannel(channel_id, resource_id, provider='gs')
|
| self.logger.info('Succesfully removed channel.')
|
|
|
| return 0
|
| @@ -298,14 +230,16 @@
|
| def _RunSubCommand(self, func):
|
| try:
|
| (self.sub_opts, self.args) = getopt.getopt(
|
| - self.args, self.command_spec[SUPPORTED_SUB_ARGS])
|
| + self.args, self.command_spec.supported_sub_args)
|
| return func()
|
| except getopt.GetoptError, e:
|
| raise CommandException('%s for "%s" command.' % (e.msg,
|
| self.command_name))
|
|
|
| def RunCommand(self):
|
| + """Command entry point for the notification command."""
|
| subcommand = self.args.pop(0)
|
| +
|
| if subcommand == 'watchbucket':
|
| return self._RunSubCommand(self._WatchBucket)
|
| elif subcommand == 'stopchannel':
|
|
|