| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2011 Google Inc. All Rights Reserved. | 2 # Copyright 2011 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. |
| 15 """Implementation of logging configuration command for buckets.""" |
| 16 |
| 17 from __future__ import absolute_import |
| 14 | 18 |
| 15 import getopt | 19 import getopt |
| 16 import sys | 20 import sys |
| 17 import textwrap | |
| 18 | 21 |
| 19 from gslib.command import Command | 22 from gslib.command import Command |
| 20 from gslib.command import COMMAND_NAME | 23 from gslib.cs_api_map import ApiSelector |
| 21 from gslib.command import COMMAND_NAME_ALIASES | |
| 22 from gslib.command import FILE_URIS_OK | |
| 23 from gslib.command import MAX_ARGS | |
| 24 from gslib.command import MIN_ARGS | |
| 25 from gslib.command import PROVIDER_URIS_OK | |
| 26 from gslib.command import SUPPORTED_SUB_ARGS | |
| 27 from gslib.command import URIS_START_ARG | |
| 28 from gslib.exception import CommandException | 24 from gslib.exception import CommandException |
| 29 from gslib.help_provider import CreateHelpText | 25 from gslib.help_provider import CreateHelpText |
| 30 from gslib.help_provider import HELP_NAME | 26 from gslib.storage_url import StorageUrlFromString |
| 31 from gslib.help_provider import HELP_NAME_ALIASES | 27 from gslib.third_party.storage_apitools import encoding as encoding |
| 32 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | 28 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 33 from gslib.help_provider import HELP_TEXT | |
| 34 from gslib.help_provider import HelpType | |
| 35 from gslib.help_provider import HELP_TYPE | |
| 36 from gslib.help_provider import SUBCOMMAND_HELP_TEXT | |
| 37 from gslib.util import NO_MAX | 29 from gslib.util import NO_MAX |
| 38 from gslib.util import UnaryDictToXml | 30 from gslib.util import UrlsAreForSingleProvider |
| 39 from xml.dom.minidom import parseString as XmlParseString | |
| 40 | 31 |
| 41 _SET_SYNOPSIS = """ | 32 _SET_SYNOPSIS = """ |
| 42 gsutil logging set on -b logging_bucket [-o log_object_prefix] uri... | 33 gsutil logging set on -b logging_bucket [-o log_object_prefix] url... |
| 43 gsutil logging set off uri... | 34 gsutil logging set off url... |
| 44 """ | 35 """ |
| 45 | 36 |
| 46 _GET_SYNOPSIS = """ | 37 _GET_SYNOPSIS = """ |
| 47 gsutil logging get uri | 38 gsutil logging get url |
| 48 """ | 39 """ |
| 49 | 40 |
| 50 _SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + '\n' | 41 _SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + '\n' |
| 51 | 42 |
| 52 _SET_DESCRIPTION = """ | 43 _SET_DESCRIPTION = """ |
| 53 <B>SET</B> | 44 <B>SET</B> |
| 54 The set sub-command has two sub-commands: | 45 The set sub-command has two sub-commands: |
| 55 | 46 |
| 56 <B>ON</B> | 47 <B>ON</B> |
| 57 The "gsutil set on" command will enable access logging of the | 48 The "gsutil set on" command will enable access logging of the |
| 58 buckets named by the specified uris, outputting log files in the specified | 49 buckets named by the specified URLs, outputting log files in the specified |
| 59 logging_bucket. logging_bucket must already exist, and all URIs must name | 50 logging_bucket. logging_bucket must already exist, and all URLs must name |
| 60 buckets (e.g., gs://bucket). The required bucket parameter specifies the | 51 buckets (e.g., gs://bucket). The required bucket parameter specifies the |
| 61 bucket to which the logs are written, and the optional log_object_prefix | 52 bucket to which the logs are written, and the optional log_object_prefix |
| 62 parameter specifies the prefix for log object names. The default prefix | 53 parameter specifies the prefix for log object names. The default prefix |
| 63 is the bucket name. For example, the command: | 54 is the bucket name. For example, the command: |
| 64 | 55 |
| 65 gsutil logging set on -b gs://my_logging_bucket -o AccessLog \\ | 56 gsutil logging set on -b gs://my_logging_bucket -o AccessLog \\ |
| 66 gs://my_bucket1 gs://my_bucket2 | 57 gs://my_bucket1 gs://my_bucket2 |
| 67 | 58 |
| 68 will cause all read and write activity to objects in gs://mybucket1 and | 59 will cause all read and write activity to objects in gs://mybucket1 and |
| 69 gs://mybucket2 to be logged to objects prefixed with the name "AccessLog", | 60 gs://mybucket2 to be logged to objects prefixed with the name "AccessLog", |
| 70 with those log objects written to the bucket gs://my_logging_bucket. | 61 with those log objects written to the bucket gs://my_logging_bucket. |
| 71 | 62 |
| 72 Next, you need to grant cloud-storage-analytics@google.com write access to | 63 Next, you need to grant cloud-storage-analytics@google.com write access to |
| 73 the log bucket, using this command: | 64 the log bucket, using this command: |
| 74 | 65 |
| 75 acl ch -g cloud-storage-analytics@google.com:W gs://my_logging_bucket | 66 acl ch -g cloud-storage-analytics@google.com:W gs://my_logging_bucket |
| 76 | 67 |
| 77 Note that log data may contain sensitive information, so you should make | 68 Note that log data may contain sensitive information, so you should make |
| 78 sure to set an appropriate default bucket ACL to protect that data. (See | 69 sure to set an appropriate default bucket ACL to protect that data. (See |
| 79 "gsutil help defacl".) | 70 "gsutil help defacl".) |
| 80 | 71 |
| 81 <B>OFF</B> | 72 <B>OFF</B> |
| 82 This command will disable access logging of the buckets named by the | 73 This command will disable access logging of the buckets named by the |
| 83 specified uris. All URIs must name buckets (e.g., gs://bucket). | 74 specified URLs. All URLs must name buckets (e.g., gs://bucket). |
| 84 | 75 |
| 85 No logging data is removed from the log buckets when you disable logging, | 76 No logging data is removed from the log buckets when you disable logging, |
| 86 but Google Cloud Storage will stop delivering new logs once you have | 77 but Google Cloud Storage will stop delivering new logs once you have |
| 87 run this command. | 78 run this command. |
| 88 | 79 |
| 89 """ | 80 """ |
| 90 | 81 |
| 91 _GET_DESCRIPTION = """ | 82 _GET_DESCRIPTION = """ |
| 92 <B>GET</B> | 83 <B>GET</B> |
| 93 If logging is enabled for the specified bucket uri, the server responds | 84 If logging is enabled for the specified bucket url, the server responds |
| 94 with a <Logging> XML element that looks something like this: | 85 with a JSON document that looks something like this: |
| 95 | 86 |
| 96 <?xml version="1.0" ?> | 87 { |
| 97 <Logging> | 88 "logObjectPrefix": "AccessLog", |
| 98 <LogBucket> | 89 "logBucket": "my_logging_bucket" |
| 99 logs-bucket | 90 } |
| 100 </LogBucket> | |
| 101 <LogObjectPrefix> | |
| 102 my-logs-enabled-bucket | |
| 103 </LogObjectPrefix> | |
| 104 </Logging> | |
| 105 | |
| 106 If logging is not enabled, an empty <Logging> element is returned. | |
| 107 | 91 |
| 108 You can download log data from your log bucket using the gsutil cp command. | 92 You can download log data from your log bucket using the gsutil cp command. |
| 109 | 93 |
| 110 """ | 94 """ |
| 111 | 95 |
| 112 _DESCRIPTION = """ | 96 _DESCRIPTION = """ |
| 113 Google Cloud Storage offers access logs and storage data in the form of | 97 Google Cloud Storage offers access logs and storage data in the form of |
| 114 CSV files that you can download and view. Access logs provide information | 98 CSV files that you can download and view. Access logs provide information |
| 115 for all of the requests made on a specified bucket in the last 24 hours, | 99 for all of the requests made on a specified bucket in the last 24 hours, |
| 116 while the storage logs provide information about the storage consumption of | 100 while the storage logs provide information about the storage consumption of |
| 117 that bucket for the last 24 hour period. The logs and storage data files | 101 that bucket for the last 24 hour period. The logs and storage data files |
| 118 are automatically created as new objects in a bucket that you specify, in | 102 are automatically created as new objects in a bucket that you specify, in |
| 119 24 hour intervals. | 103 24 hour intervals. |
| 120 | 104 |
| 121 The logging command has two sub-commands: | 105 The logging command has two sub-commands: |
| 122 """ + _SET_DESCRIPTION + _GET_DESCRIPTION + """ | 106 """ + _SET_DESCRIPTION + _GET_DESCRIPTION + """ |
| 123 | 107 |
| 124 <B>ACCESS LOG AND STORAGE DATA FIELDS</B> | 108 <B>ACCESS LOG AND STORAGE DATA FIELDS</B> |
| 125 For a complete list of access log fields and storage data fields, see: | 109 For a complete list of access log fields and storage data fields, see: |
| 126 https://developers.google.com/storage/docs/accesslogs#reviewing | 110 https://developers.google.com/storage/docs/accesslogs#reviewing |
| 127 """ | 111 """ |
| 128 | 112 |
| 129 _detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION) | 113 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) |
| 130 | 114 |
| 131 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) | 115 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) |
| 132 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) | 116 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) |
| 133 | 117 |
| 134 | 118 |
| 135 class LoggingCommand(Command): | 119 class LoggingCommand(Command): |
| 136 """Implementation of gsutil logging command.""" | 120 """Implementation of gsutil logging command.""" |
| 137 | 121 |
| 138 # Command specification (processed by parent class). | 122 # Command specification. See base class for documentation. |
| 139 command_spec = { | 123 command_spec = Command.CreateCommandSpec( |
| 140 # Name of command. | 124 'logging', |
| 141 COMMAND_NAME : 'logging', | 125 command_name_aliases=['disablelogging', 'enablelogging', 'getlogging'], |
| 142 # List of command name aliases. | 126 min_args=2, |
| 143 COMMAND_NAME_ALIASES : ['disablelogging', 'enablelogging', 'getlogging'], | 127 max_args=NO_MAX, |
| 144 # Min number of args required by this command. | 128 supported_sub_args='b:o:', |
| 145 MIN_ARGS : 2, | 129 file_url_ok=False, |
| 146 # Max number of args required by this command, or NO_MAX. | 130 provider_url_ok=False, |
| 147 MAX_ARGS : NO_MAX, | 131 urls_start_arg=0, |
| 148 # Getopt-style string specifying acceptable sub args. | 132 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 149 SUPPORTED_SUB_ARGS : 'b:o:', | 133 gs_default_api=ApiSelector.JSON, |
| 150 # True if file URIs acceptable for this command. | 134 ) |
| 151 FILE_URIS_OK : False, | 135 # Help specification. See help_provider.py for documentation. |
| 152 # True if provider-only URIs acceptable for this command. | 136 help_spec = Command.HelpSpec( |
| 153 PROVIDER_URIS_OK : False, | 137 help_name='logging', |
| 154 # Index in args of first URI arg. | 138 help_name_aliases=['loggingconfig', 'logs', 'log', 'getlogging', |
| 155 URIS_START_ARG : 0, | |
| 156 } | |
| 157 help_spec = { | |
| 158 # Name of command or auxiliary help info for which this help applies. | |
| 159 HELP_NAME : 'logging', | |
| 160 # List of help name aliases. | |
| 161 HELP_NAME_ALIASES : ['loggingconfig', 'logs', 'log', 'getlogging', | |
| 162 'enablelogging', 'disablelogging'], | 139 'enablelogging', 'disablelogging'], |
| 163 # Type of help: | 140 help_type='command_help', |
| 164 HELP_TYPE : HelpType.COMMAND_HELP, | 141 help_one_line_summary='Configure or retrieve logging on buckets', |
| 165 # One line summary of this help. | 142 help_text=_DETAILED_HELP_TEXT, |
| 166 HELP_ONE_LINE_SUMMARY : 'Configure or retrieve logging on buckets', | 143 subcommand_help_text={'get': _get_help_text, 'set': _set_help_text}, |
| 167 # The full help text. | 144 ) |
| 168 HELP_TEXT : _detailed_help_text, | |
| 169 # Help text for sub-commands. | |
| 170 SUBCOMMAND_HELP_TEXT : {'get' : _get_help_text, | |
| 171 'set' : _set_help_text}, | |
| 172 } | |
| 173 | 145 |
| 174 def _Get(self): | 146 def _Get(self): |
| 175 uri_args = self.args | 147 """Gets logging configuration for a bucket.""" |
| 148 bucket_url, bucket_metadata = self.GetSingleBucketUrlFromArg( |
| 149 self.args[0], bucket_fields=['logging']) |
| 176 | 150 |
| 177 # Iterate over URIs, expanding wildcards, and getting the website | 151 if bucket_url.scheme == 's3': |
| 178 # configuration on each. | 152 sys.stdout.write(self.gsutil_api.XmlPassThroughGetLogging( |
| 179 some_matched = False | 153 bucket_url, provider=bucket_url.scheme)) |
| 180 for uri_str in uri_args: | 154 else: |
| 181 for blr in self.WildcardIterator(uri_str): | 155 if (bucket_metadata.logging and bucket_metadata.logging.logBucket and |
| 182 uri = blr.GetUri() | 156 bucket_metadata.logging.logObjectPrefix): |
| 183 if not uri.names_bucket(): | 157 sys.stdout.write(str(encoding.MessageToJson( |
| 184 raise CommandException('URI %s must name a bucket for the %s command' | 158 bucket_metadata.logging)) + '\n') |
| 185 % (uri, self.command_name)) | 159 else: |
| 186 some_matched = True | 160 sys.stdout.write('%s has no logging configuration.\n' % bucket_url) |
| 187 sys.stderr.write('Getting logging config on %s...\n' % uri) | 161 return 0 |
| 188 logging_config_xml = UnaryDictToXml(uri.get_logging_config()) | |
| 189 sys.stdout.write(XmlParseString(logging_config_xml).toprettyxml()) | |
| 190 if not some_matched: | |
| 191 raise CommandException('No URIs matched') | |
| 192 | 162 |
| 193 def _Enable(self): | 163 def _Enable(self): |
| 164 """Enables logging configuration for a bucket.""" |
| 194 # Disallow multi-provider 'logging set on' calls, because the schemas | 165 # Disallow multi-provider 'logging set on' calls, because the schemas |
| 195 # differ. | 166 # differ. |
| 196 storage_uri = self.UrisAreForSingleProvider(self.args) | 167 if not UrlsAreForSingleProvider(self.args): |
| 197 if not storage_uri: | |
| 198 raise CommandException('"logging set on" command spanning providers not ' | 168 raise CommandException('"logging set on" command spanning providers not ' |
| 199 'allowed.') | 169 'allowed.') |
| 200 target_bucket_uri = None | 170 target_bucket_url = None |
| 201 target_prefix = None | 171 target_prefix = None |
| 202 for opt, opt_arg in self.sub_opts: | 172 for opt, opt_arg in self.sub_opts: |
| 203 if opt == '-b': | 173 if opt == '-b': |
| 204 target_bucket_uri = self.suri_builder.StorageUri(opt_arg) | 174 target_bucket_url = StorageUrlFromString(opt_arg) |
| 205 if opt == '-o': | 175 if opt == '-o': |
| 206 target_prefix = opt_arg | 176 target_prefix = opt_arg |
| 207 | 177 |
| 208 if not target_bucket_uri: | 178 if not target_bucket_url: |
| 209 raise CommandException('"logging set on" requires \'-b <log_bucket>\' ' | 179 raise CommandException('"logging set on" requires \'-b <log_bucket>\' ' |
| 210 'option') | 180 'option') |
| 211 if not target_bucket_uri.names_bucket(): | 181 if not target_bucket_url.IsBucket(): |
| 212 raise CommandException('-b option must specify a bucket uri') | 182 raise CommandException('-b option must specify a bucket URL.') |
| 213 | 183 |
| 214 did_some_work = False | 184 # Iterate over URLs, expanding wildcards and setting logging on each. |
| 215 for uri_str in self.args: | 185 some_matched = False |
| 216 for uri in self.WildcardIterator(uri_str).IterUris(): | 186 for url_str in self.args: |
| 217 if uri.names_object(): | 187 bucket_iter = self.GetBucketUrlIterFromArg(url_str, bucket_fields=['id']) |
| 218 raise CommandException('logging cannot be applied to objects') | 188 for blr in bucket_iter: |
| 219 did_some_work = True | 189 url = blr.storage_url |
| 220 self.logger.info('Enabling logging on %s...', uri) | 190 some_matched = True |
| 221 self.proj_id_handler.FillInProjectHeaderIfNeeded( | 191 self.logger.info('Enabling logging on %s...', blr) |
| 222 'enablelogging', storage_uri, self.headers) | 192 logging = apitools_messages.Bucket.LoggingValue( |
| 223 uri.enable_logging(target_bucket_uri.bucket_name, target_prefix, False, | 193 logBucket=target_bucket_url.bucket_name, |
| 224 self.headers) | 194 logObjectPrefix=target_prefix or url.bucket_name) |
| 225 if not did_some_work: | 195 |
| 226 raise CommandException('No URIs matched') | 196 bucket_metadata = apitools_messages.Bucket(logging=logging) |
| 197 self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 198 provider=url.scheme, fields=['id']) |
| 199 if not some_matched: |
| 200 raise CommandException('No URLs matched') |
| 201 return 0 |
| 227 | 202 |
| 228 def _Disable(self): | 203 def _Disable(self): |
| 229 did_some_work = False | 204 """Disables logging configuration for a bucket.""" |
| 230 for uri_str in self.args: | 205 # Iterate over URLs, expanding wildcards, and disabling logging on each. |
| 231 for uri in self.WildcardIterator(uri_str).IterUris(): | 206 some_matched = False |
| 232 if uri.names_object(): | 207 for url_str in self.args: |
| 233 raise CommandException('logging cannot be applied to objects') | 208 bucket_iter = self.GetBucketUrlIterFromArg(url_str, bucket_fields=['id']) |
| 234 did_some_work = True | 209 for blr in bucket_iter: |
| 235 self.logger.info('Disabling logging on %s...', uri) | 210 url = blr.storage_url |
| 236 self.proj_id_handler.FillInProjectHeaderIfNeeded('disablelogging', | 211 some_matched = True |
| 237 uri, self.headers) | 212 self.logger.info('Disabling logging on %s...', blr) |
| 238 uri.disable_logging(False, self.headers) | 213 logging = apitools_messages.Bucket.LoggingValue() |
| 239 if not did_some_work: | |
| 240 raise CommandException('No URIs matched') | |
| 241 | 214 |
| 242 # Command entry point. | 215 bucket_metadata = apitools_messages.Bucket(logging=logging) |
| 216 self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 217 provider=url.scheme, fields=['id']) |
| 218 if not some_matched: |
| 219 raise CommandException('No URLs matched') |
| 220 return 0 |
| 221 |
| 243 def RunCommand(self): | 222 def RunCommand(self): |
| 223 """Command entry point for the logging command.""" |
| 244 # Parse the subcommand and alias for the new logging command. | 224 # Parse the subcommand and alias for the new logging command. |
| 245 action_subcommand = self.args.pop(0) | 225 action_subcommand = self.args.pop(0) |
| 246 if action_subcommand == 'get': | 226 if action_subcommand == 'get': |
| 247 func = self._Get | 227 func = self._Get |
| 248 elif action_subcommand == 'set': | 228 elif action_subcommand == 'set': |
| 249 state_subcommand = self.args.pop(0) | 229 state_subcommand = self.args.pop(0) |
| 250 if not self.args: | 230 if not self.args: |
| 251 self._RaiseWrongNumberOfArgumentsException() | 231 self._RaiseWrongNumberOfArgumentsException() |
| 252 if state_subcommand == 'on': | 232 if state_subcommand == 'on': |
| 253 func = self._Enable | 233 func = self._Enable |
| 254 elif state_subcommand == 'off': | 234 elif state_subcommand == 'off': |
| 255 func = self._Disable | 235 func = self._Disable |
| 256 else: | 236 else: |
| 257 raise CommandException(( | 237 raise CommandException(( |
| 258 'Invalid subcommand "%s" for the "%s %s" command.\n' | 238 'Invalid subcommand "%s" for the "%s %s" command.\n' |
| 259 'See "gsutil help logging".') % | 239 'See "gsutil help logging".') % ( |
| 260 (state_subcommand, self.command_name, action_subcommand)) | 240 state_subcommand, self.command_name, action_subcommand)) |
| 261 else: | 241 else: |
| 262 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' | 242 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' |
| 263 'See "gsutil help logging".') % | 243 'See "gsutil help logging".') % |
| 264 (action_subcommand, self.command_name)) | 244 (action_subcommand, self.command_name)) |
| 265 try: | 245 try: |
| 266 (self.sub_opts, self.args) = getopt.getopt(self.args, | 246 self.sub_opts, self.args = getopt.getopt( |
| 267 self.command_spec[SUPPORTED_SUB_ARGS]) | 247 self.args, self.command_spec.supported_sub_args) |
| 268 self.CheckArguments() | 248 self.CheckArguments() |
| 269 except getopt.GetoptError, e: | 249 except getopt.GetoptError, e: |
| 270 raise CommandException('%s for "%s" command.' % (e.msg, | 250 raise CommandException('%s for "%s" command.' % (e.msg, |
| 271 self.command_name)) | 251 self.command_name)) |
| 272 func() | 252 func() |
| 273 return 0 | 253 return 0 |
| OLD | NEW |