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

Side by Side Diff: gslib/command_runner.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/command.py ('k') | gslib/commands/__init__.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 #!/usr/bin/env python 1 # -*- coding: utf-8 -*-
2 # coding=utf8
3 # Copyright 2011 Google Inc. All Rights Reserved. 2 # Copyright 2011 Google Inc. All Rights Reserved.
4 # 3 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # Licensed under the Apache License, Version 2.0 (the "License");
6 # 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.
7 # You may obtain a copy of the License at 6 # You may obtain a copy of the License at
8 # 7 #
9 # http://www.apache.org/licenses/LICENSE-2.0 8 # http://www.apache.org/licenses/LICENSE-2.0
10 # 9 #
11 # Unless required by applicable law or agreed to in writing, software 10 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, 11 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and 13 # See the License for the specific language governing permissions and
15 # limitations under the License. 14 # limitations under the License.
16
17 """Class that runs a named gsutil command.""" 15 """Class that runs a named gsutil command."""
18 16
19 import boto 17 from __future__ import absolute_import
18
20 import difflib 19 import difflib
21 import logging 20 import logging
21 import os
22 import pkgutil 22 import pkgutil
23 import os
24 import sys 23 import sys
25 import textwrap 24 import textwrap
26 import time 25 import time
27 26
27 import boto
28 from boto.storage_uri import BucketStorageUri 28 from boto.storage_uri import BucketStorageUri
29 import gslib 29 import gslib
30 import gslib.commands
31 from gslib.command import Command 30 from gslib.command import Command
32 from gslib.command import COMMAND_NAME 31 from gslib.command import GetFailureCount
33 from gslib.command import COMMAND_NAME_ALIASES
34 from gslib.command import OLD_ALIAS_MAP 32 from gslib.command import OLD_ALIAS_MAP
35 from gslib.command import ShutDownGsutil 33 from gslib.command import ShutDownGsutil
34 import gslib.commands
35 from gslib.cs_api_map import GsutilApiClassMapFactory
36 from gslib.exception import CommandException 36 from gslib.exception import CommandException
37 from gslib.help_provider import SUBCOMMAND_HELP_TEXT 37 from gslib.gcs_json_api import GcsJsonApi
38 from gslib.storage_uri_builder import StorageUriBuilder 38 from gslib.no_op_credentials import NoOpCredentials
39 from gslib.util import CompareVersions 39 from gslib.util import CompareVersions
40 from gslib.util import ConfigureNoOpAuthIfNeeded
41 from gslib.util import GetGsutilVersionModifiedTime 40 from gslib.util import GetGsutilVersionModifiedTime
42 from gslib.util import GSUTIL_PUB_TARBALL 41 from gslib.util import GSUTIL_PUB_TARBALL
43 from gslib.util import IsRunningInteractively 42 from gslib.util import IsRunningInteractively
44 from gslib.util import LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE 43 from gslib.util import LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE
45 from gslib.util import LookUpGsutilVersion 44 from gslib.util import LookUpGsutilVersion
46 from gslib.util import MultiprocessingIsAvailable 45 from gslib.util import MultiprocessingIsAvailable
47 from gslib.util import RELEASE_NOTES_URL 46 from gslib.util import RELEASE_NOTES_URL
48 from gslib.util import SECONDS_PER_DAY 47 from gslib.util import SECONDS_PER_DAY
48 from gslib.util import UTF8
49 49
50 50
51 def HandleArgCoding(args): 51 def HandleArgCoding(args):
52 """ 52 """Handles coding of command-line args.
53 Handles coding of command-line args.
54 53
55 Args: 54 Args:
56 args: array of command-line args. 55 args: array of command-line args.
57 56
58 Returns: 57 Returns:
59 array of command-line args. 58 array of command-line args.
60 59
61 Raises: 60 Raises:
62 CommandException: if errors encountered. 61 CommandException: if errors encountered.
63 """ 62 """
64 # Python passes arguments from the command line as byte strings. To 63 # Python passes arguments from the command line as byte strings. To
65 # correctly interpret them, we decode ones other than -h and -p args (which 64 # correctly interpret them, we decode ones other than -h and -p args (which
66 # will be passed as headers, and thus per HTTP spec should not be encoded) as 65 # will be passed as headers, and thus per HTTP spec should not be encoded) as
67 # utf-8. The exception is x-goog-meta-* headers, which are allowed to contain 66 # utf-8. The exception is x-goog-meta-* headers, which are allowed to contain
68 # non-ASCII content (and hence, should be decoded), per 67 # non-ASCII content (and hence, should be decoded), per
69 # https://developers.google.com/storage/docs/gsutil/addlhelp/WorkingWithObject Metadata 68 # https://developers.google.com/storage/docs/gsutil/addlhelp/WorkingWithObject Metadata
70 processing_header = False 69 processing_header = False
71 for i in range(len(args)): 70 for i in range(len(args)):
72 arg = args[i] 71 arg = args[i]
73 decoded = arg.decode('utf-8') 72 decoded = arg.decode(UTF8)
74 if processing_header: 73 if processing_header:
75 if arg.lower().startswith('x-goog-meta'): 74 if arg.lower().startswith('x-goog-meta'):
76 args[i] = decoded 75 args[i] = decoded
77 else: 76 else:
78 try: 77 try:
79 # Try to encode as ASCII to check for invalid header values (which 78 # Try to encode as ASCII to check for invalid header values (which
80 # can't be sent over HTTP). 79 # can't be sent over HTTP).
81 decoded.encode('ascii') 80 decoded.encode('ascii')
82 except UnicodeEncodeError: 81 except UnicodeEncodeError:
83 # Raise the CommandException using the decoded value because 82 # Raise the CommandException using the decoded value because
84 # _OutputAndExit function re-encodes at the end. 83 # _OutputAndExit function re-encodes at the end.
85 raise CommandException( 84 raise CommandException(
86 'Invalid non-ASCII header value (%s).\nOnly ASCII characters are ' 85 'Invalid non-ASCII header value (%s).\nOnly ASCII characters are '
87 'allowed in headers other than x-goog-meta- headers' % decoded) 86 'allowed in headers other than x-goog-meta- headers' % decoded)
88 else: 87 else:
89 args[i] = decoded 88 args[i] = decoded
90 processing_header = (arg in ('-h', '-p')) 89 processing_header = (arg in ('-h', '-p'))
91 return args 90 return args
92 91
93 92
94 class CommandRunner(object): 93 class CommandRunner(object):
94 """Runs gsutil commands and does some top-level argument handling."""
95 95
96 def __init__(self, config_file_list, 96 def __init__(self, bucket_storage_uri_class=BucketStorageUri,
97 bucket_storage_uri_class=BucketStorageUri): 97 gsutil_api_class_map_factory=GsutilApiClassMapFactory):
98 """ 98 """Instantiates a CommandRunner.
99
99 Args: 100 Args:
100 config_file_list: Config file list returned by GetBotoConfigFileList().
101 bucket_storage_uri_class: Class to instantiate for cloud StorageUris. 101 bucket_storage_uri_class: Class to instantiate for cloud StorageUris.
102 Settable for testing/mocking. 102 Settable for testing/mocking.
103 gsutil_api_class_map_factory: Creates map of cloud storage interfaces.
104 Settable for testing/mocking.
103 """ 105 """
104 self.config_file_list = config_file_list
105 self.bucket_storage_uri_class = bucket_storage_uri_class 106 self.bucket_storage_uri_class = bucket_storage_uri_class
107 self.gsutil_api_class_map_factory = gsutil_api_class_map_factory
106 self.command_map = self._LoadCommandMap() 108 self.command_map = self._LoadCommandMap()
107 109
108 def _LoadCommandMap(self): 110 def _LoadCommandMap(self):
109 """Returns dict mapping each command_name to implementing class.""" 111 """Returns dict mapping each command_name to implementing class."""
110 # Import all gslib.commands submodules. 112 # Import all gslib.commands submodules.
111 for _, module_name, _ in pkgutil.iter_modules(gslib.commands.__path__): 113 for _, module_name, _ in pkgutil.iter_modules(gslib.commands.__path__):
112 __import__('gslib.commands.%s' % module_name) 114 __import__('gslib.commands.%s' % module_name)
113 115
114 command_map = {} 116 command_map = {}
115 # Only include Command subclasses in the dict. 117 # Only include Command subclasses in the dict.
116 for command in Command.__subclasses__(): 118 for command in Command.__subclasses__():
117 command_map[command.command_spec[COMMAND_NAME]] = command 119 command_map[command.command_spec.command_name] = command
118 for command_name_aliases in command.command_spec[COMMAND_NAME_ALIASES]: 120 for command_name_aliases in command.command_spec.command_name_aliases:
119 command_map[command_name_aliases] = command 121 command_map[command_name_aliases] = command
120 return command_map 122 return command_map
121 123
122 def RunNamedCommand(self, command_name, args=None, headers=None, debug=0, 124 def RunNamedCommand(self, command_name, args=None, headers=None, debug=0,
123 parallel_operations=False, test_method=None, 125 parallel_operations=False, test_method=None,
124 skip_update_check=False, logging_filters=None): 126 skip_update_check=False, logging_filters=None,
125 """Runs the named command. Used by gsutil main, commands built atop 127 do_shutdown=True):
126 other commands, and tests . 128 """Runs the named command.
127 129
128 Args: 130 Used by gsutil main, commands built atop other commands, and tests.
129 command_name: The name of the command being run.
130 args: Command-line args (arg0 = actual arg, not command name ala bash).
131 headers: Dictionary containing optional HTTP headers to pass to boto.
132 debug: Debug level to pass in to boto connection (range 0..3).
133 parallel_operations: Should command operations be executed in parallel?
134 test_method: Optional general purpose method for testing purposes.
135 Application and semantics of this method will vary by
136 command and test type.
137 skip_update_check: Set to True to disable checking for gsutil updates.
138 logging_filters: Optional list of logging.Filters to apply to this
139 command's logger.
140 131
141 Raises: 132 Args:
142 CommandException: if errors encountered. 133 command_name: The name of the command being run.
134 args: Command-line args (arg0 = actual arg, not command name ala bash).
135 headers: Dictionary containing optional HTTP headers to pass to boto.
136 debug: Debug level to pass in to boto connection (range 0..3).
137 parallel_operations: Should command operations be executed in parallel?
138 test_method: Optional general purpose method for testing purposes.
139 Application and semantics of this method will vary by
140 command and test type.
141 skip_update_check: Set to True to disable checking for gsutil updates.
142 logging_filters: Optional list of logging.Filters to apply to this
143 command's logger.
144 do_shutdown: Stop all parallelism framework workers iff this is True.
145
146 Raises:
147 CommandException: if errors encountered.
148
149 Returns:
150 Return value(s) from Command that was run.
143 """ 151 """
144 ConfigureNoOpAuthIfNeeded()
145 if (not skip_update_check and 152 if (not skip_update_check and
146 self._MaybeCheckForAndOfferSoftwareUpdate(command_name, debug)): 153 self.MaybeCheckForAndOfferSoftwareUpdate(command_name, debug)):
147 command_name = 'update' 154 command_name = 'update'
148 args = ['-n'] 155 args = ['-n']
149 156
150 if not args: 157 if not args:
151 args = [] 158 args = []
152 159
153 # Include api_version header in all commands. 160 # Include api_version header in all commands.
154 api_version = boto.config.get_value('GSUtil', 'default_api_version', '1') 161 api_version = boto.config.get_value('GSUtil', 'default_api_version', '1')
155 if not headers: 162 if not headers:
156 headers = {} 163 headers = {}
157 headers['x-goog-api-version'] = api_version 164 headers['x-goog-api-version'] = api_version
158 165
159 if command_name not in self.command_map: 166 if command_name not in self.command_map:
160 close_matches = difflib.get_close_matches( 167 close_matches = difflib.get_close_matches(
161 command_name, self.command_map.keys(), n=1) 168 command_name, self.command_map.keys(), n=1)
162 if len(close_matches): 169 if close_matches:
163 # Instead of suggesting a deprecated command alias, suggest the new 170 # Instead of suggesting a deprecated command alias, suggest the new
164 # name for that command. 171 # name for that command.
165 translated_command_name = ( 172 translated_command_name = (
166 OLD_ALIAS_MAP.get(close_matches[0], close_matches)[0]) 173 OLD_ALIAS_MAP.get(close_matches[0], close_matches)[0])
167 print >> sys.stderr, 'Did you mean this?' 174 print >> sys.stderr, 'Did you mean this?'
168 print >> sys.stderr, '\t%s' % translated_command_name 175 print >> sys.stderr, '\t%s' % translated_command_name
169 raise CommandException('Invalid command "%s".' % command_name) 176 raise CommandException('Invalid command "%s".' % command_name)
170 if '--help' in args: 177 if '--help' in args:
171 new_args = [command_name] 178 new_args = [command_name]
172 original_command_class = self.command_map[command_name] 179 original_command_class = self.command_map[command_name]
173 subcommands = original_command_class.help_spec.get( 180 subcommands = original_command_class.help_spec.subcommand_help_text.keys()
174 SUBCOMMAND_HELP_TEXT, {}).keys()
175 for arg in args: 181 for arg in args:
176 if arg in subcommands: 182 if arg in subcommands:
177 new_args.append(arg) 183 new_args.append(arg)
178 break # Take the first match and throw away the rest. 184 break # Take the first match and throw away the rest.
179 args = new_args 185 args = new_args
180 command_name = 'help' 186 command_name = 'help'
181 187
182 args = HandleArgCoding(args) 188 args = HandleArgCoding(args)
183 189
184 command_class = self.command_map[command_name] 190 command_class = self.command_map[command_name]
185 command_inst = command_class( 191 command_inst = command_class(
186 self, args, headers, debug, parallel_operations, self.config_file_list, 192 self, args, headers, debug, parallel_operations,
187 self.bucket_storage_uri_class, test_method, logging_filters, 193 self.bucket_storage_uri_class, self.gsutil_api_class_map_factory,
188 command_alias_used=command_name) 194 test_method, logging_filters, command_alias_used=command_name)
195 return_code = command_inst.RunCommand()
189 196
190 return_values = command_inst.RunCommand() 197 if MultiprocessingIsAvailable()[0] and do_shutdown:
191 if MultiprocessingIsAvailable()[0]:
192 ShutDownGsutil() 198 ShutDownGsutil()
193 return return_values 199 if GetFailureCount() > 0:
200 return_code = 1
201 return return_code
194 202
195 def _MaybeCheckForAndOfferSoftwareUpdate(self, command_name, debug): 203 def MaybeCheckForAndOfferSoftwareUpdate(self, command_name, debug):
196 """Checks the last time we checked for an update, and if it's been longer 204 """Checks the last time we checked for an update and offers one if needed.
197 than the configured threshold offers the user to update gsutil.
198 205
199 Args: 206 Offer is made if the time since the last update check is longer
200 command_name: The name of the command being run. 207 than the configured threshold offers the user to update gsutil.
201 debug: Debug level to pass in to boto connection (range 0..3).
202 208
203 Returns: 209 Args:
204 True if the user decides to update. 210 command_name: The name of the command being run.
211 debug: Debug level to pass in to boto connection (range 0..3).
212
213 Returns:
214 True if the user decides to update.
205 """ 215 """
206 # Don't try to interact with user if: 216 # Don't try to interact with user if:
207 # - gsutil is not connected to a tty (e.g., if being run from cron); 217 # - gsutil is not connected to a tty (e.g., if being run from cron);
208 # - user is running gsutil -q 218 # - user is running gsutil -q
209 # - user is running the config command (which could otherwise attempt to 219 # - user is running the config command (which could otherwise attempt to
210 # check for an update for a user running behind a proxy, who has not yet 220 # check for an update for a user running behind a proxy, who has not yet
211 # configured gsutil to go through the proxy; for such users we need the 221 # configured gsutil to go through the proxy; for such users we need the
212 # first connection attempt to be made by the gsutil config command). 222 # first connection attempt to be made by the gsutil config command).
213 # - user is running the version command (which gets run when using 223 # - user is running the version command (which gets run when using
214 # gsutil -D, which would prevent users with proxy config problems from 224 # gsutil -D, which would prevent users with proxy config problems from
215 # sending us gsutil -D output). 225 # sending us gsutil -D output).
216 # - user is running the update command (which could otherwise cause an 226 # - user is running the update command (which could otherwise cause an
217 # additional note that an update is available when user is already trying 227 # additional note that an update is available when user is already trying
218 # to perform an update); 228 # to perform an update);
219 # - user specified gs_host (which could be a non-production different 229 # - user specified gs_host (which could be a non-production different
220 # service instance, in which case credentials won't work for checking 230 # service instance, in which case credentials won't work for checking
221 # gsutil tarball). 231 # gsutil tarball).
232 logger = logging.getLogger()
222 gs_host = boto.config.get('Credentials', 'gs_host', None) 233 gs_host = boto.config.get('Credentials', 'gs_host', None)
223 if (not IsRunningInteractively() 234 if (not IsRunningInteractively()
224 or command_name in ('config', 'update', 'ver', 'version') 235 or command_name in ('config', 'update', 'ver', 'version')
225 or not logging.getLogger().isEnabledFor(logging.INFO) 236 or not logger.isEnabledFor(logging.INFO)
226 or gs_host): 237 or gs_host):
227 return False 238 return False
228 239
229 software_update_check_period = boto.config.getint( 240 software_update_check_period = boto.config.getint(
230 'GSUtil', 'software_update_check_period', 30) 241 'GSUtil', 'software_update_check_period', 30)
231 # Setting software_update_check_period to 0 means periodic software 242 # Setting software_update_check_period to 0 means periodic software
232 # update checking is disabled. 243 # update checking is disabled.
233 if software_update_check_period == 0: 244 if software_update_check_period == 0:
234 return False 245 return False
235 246
236 cur_ts = int(time.time()) 247 cur_ts = int(time.time())
237 if not os.path.isfile(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE): 248 if not os.path.isfile(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE):
238 # Set last_checked_ts from date of VERSION file, so if the user installed 249 # Set last_checked_ts from date of VERSION file, so if the user installed
239 # an old copy of gsutil it will get noticed (and an update offered) the 250 # an old copy of gsutil it will get noticed (and an update offered) the
240 # first time they try to run it. 251 # first time they try to run it.
241 last_checked_ts = GetGsutilVersionModifiedTime() 252 last_checked_ts = GetGsutilVersionModifiedTime()
242 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: 253 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f:
243 f.write(str(last_checked_ts)) 254 f.write(str(last_checked_ts))
244 else: 255 else:
245 try: 256 try:
246 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'r') as f: 257 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'r') as f:
247 last_checked_ts = int(f.readline()) 258 last_checked_ts = int(f.readline())
248 except (TypeError, ValueError): 259 except (TypeError, ValueError):
249 return False 260 return False
250 261
251 if (cur_ts - last_checked_ts 262 if (cur_ts - last_checked_ts
252 > software_update_check_period * SECONDS_PER_DAY): 263 > software_update_check_period * SECONDS_PER_DAY):
253 suri_builder = StorageUriBuilder(debug, self.bucket_storage_uri_class) 264 # Create a credential-less gsutil API to check for the public
254 cur_ver = LookUpGsutilVersion(suri_builder.StorageUri(GSUTIL_PUB_TARBALL)) 265 # update tarball.
266 gsutil_api = GcsJsonApi(self.bucket_storage_uri_class, logger,
267 credentials=NoOpCredentials(), debug=debug)
268
269 cur_ver = LookUpGsutilVersion(gsutil_api, GSUTIL_PUB_TARBALL)
255 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: 270 with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f:
256 f.write(str(cur_ts)) 271 f.write(str(cur_ts))
257 (g, m) = CompareVersions(cur_ver, gslib.VERSION) 272 (g, m) = CompareVersions(cur_ver, gslib.VERSION)
258 if m: 273 if m:
259 print '\n'.join(textwrap.wrap( 274 print '\n'.join(textwrap.wrap(
260 'A newer version of gsutil (%s) is available than the version you ' 275 'A newer version of gsutil (%s) is available than the version you '
261 'are running (%s). NOTE: This is a major new version, so it is ' 276 'are running (%s). NOTE: This is a major new version, so it is '
262 'strongly recommended that you review the release note details at %s ' 277 'strongly recommended that you review the release note details at '
263 'before updating to this version, especially if you use gsutil in ' 278 '%s before updating to this version, especially if you use gsutil '
264 'scripts.' % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) 279 'in scripts.' % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL)))
265 if gslib.IS_PACKAGE_INSTALL: 280 if gslib.IS_PACKAGE_INSTALL:
266 return False 281 return False
267 print 282 print
268 answer = raw_input('Would you like to update [y/N]? ') 283 answer = raw_input('Would you like to update [y/N]? ')
269 return answer and answer.lower()[0] == 'y' 284 return answer and answer.lower()[0] == 'y'
270 elif g: 285 elif g:
271 print '\n'.join(textwrap.wrap( 286 print '\n'.join(textwrap.wrap(
272 'A newer version of gsutil (%s) is available than the version you ' 287 'A newer version of gsutil (%s) is available than the version you '
273 'are running (%s). A detailed log of gsutil release changes is ' 288 'are running (%s). A detailed log of gsutil release changes is '
274 'available at %s if you would like to read them before updating.' 289 'available at %s if you would like to read them before updating.'
275 % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) 290 % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL)))
276 if gslib.IS_PACKAGE_INSTALL: 291 if gslib.IS_PACKAGE_INSTALL:
277 return False 292 return False
278 print 293 print
279 answer = raw_input('Would you like to update [Y/n]? ') 294 answer = raw_input('Would you like to update [Y/n]? ')
280 return not answer or answer.lower()[0] != 'n' 295 return not answer or answer.lower()[0] != 'n'
281 return False 296 return False
OLDNEW
« no previous file with comments | « gslib/command.py ('k') | gslib/commands/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698