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

Side by Side Diff: third_party/gsutil/gslib/commands/update.py

Issue 12042069: Scripts to download files from google storage based on sha1 sums (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Removed gsutil/tests and gsutil/docs Created 7 years, 10 months 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
OLDNEW
(Empty)
1 # Copyright 2011 Google Inc.
2 #
3 # 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 obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import os
16 import platform
17 import shutil
18 import signal
19 import sys
20 import tarfile
21 import tempfile
22
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 CONFIG_REQUIRED
27 from gslib.command import FILE_URIS_OK
28 from gslib.command import MAX_ARGS
29 from gslib.command import MIN_ARGS
30 from gslib.command import PROVIDER_URIS_OK
31 from gslib.command import SUPPORTED_SUB_ARGS
32 from gslib.command import URIS_START_ARG
33 from gslib.exception import CommandException
34 from gslib.help_provider import HELP_NAME
35 from gslib.help_provider import HELP_NAME_ALIASES
36 from gslib.help_provider import HELP_ONE_LINE_SUMMARY
37 from gslib.help_provider import HELP_TEXT
38 from gslib.help_provider import HelpType
39 from gslib.help_provider import HELP_TYPE
40
41 _detailed_help_text = ("""
42 <B>SYNOPSIS</B>
43 gsutil update [-f] [uri]
44
45
46 <B>DESCRIPTION</B>
47 The gsutil update command downloads the latest gsutil release, checks its
48 version, and offers to let you update to it if it differs from the version
49 you're currently running.
50
51 Once you say "Y" to the prompt of whether to install the update, the gsutil
52 update command locates where the running copy of gsutil is installed,
53 unpacks the new version into an adjacent directory, moves the previous version
54 aside, moves the new version to where the previous version was installed,
55 and removes the moved-aside old version. Because of this, users are cautioned
56 not to store data in the gsutil directory, since that data will be lost
57 when you update gsutil. (Some users change directories into the gsutil
58 directory to run the command. We advise against doing that, for this reason.)
59
60 By default gsutil update will retrieve the new code from
61 gs://pub/gsutil.tar.gz, but you can optionally specify a URI to use
62 instead. This is primarily used for distributing pre-release versions of
63 the code to a small group of early test users.
64
65
66 <B>OPTIONS</B>
67 -f Forces the update command to offer to let you update, even if you
68 have the most current copy already. This can be useful if you have
69 a corrupted local copy.
70 """)
71
72
73 class UpdateCommand(Command):
74 """Implementation of gsutil update command."""
75
76 # Command specification (processed by parent class).
77 command_spec = {
78 # Name of command.
79 COMMAND_NAME : 'update',
80 # List of command name aliases.
81 COMMAND_NAME_ALIASES : ['refresh'],
82 # Min number of args required by this command.
83 MIN_ARGS : 0,
84 # Max number of args required by this command, or NO_MAX.
85 MAX_ARGS : 1,
86 # Getopt-style string specifying acceptable sub args.
87 SUPPORTED_SUB_ARGS : 'f',
88 # True if file URIs acceptable for this command.
89 FILE_URIS_OK : False,
90 # True if provider-only URIs acceptable for this command.
91 PROVIDER_URIS_OK : False,
92 # Index in args of first URI arg.
93 URIS_START_ARG : 0,
94 # True if must configure gsutil before running command.
95 CONFIG_REQUIRED : True,
96 }
97 help_spec = {
98 # Name of command or auxiliary help info for which this help applies.
99 HELP_NAME : 'update',
100 # List of help name aliases.
101 HELP_NAME_ALIASES : ['refresh'],
102 # Type of help:
103 HELP_TYPE : HelpType.COMMAND_HELP,
104 # One line summary of this help.
105 HELP_ONE_LINE_SUMMARY : 'Update to the latest gsutil release',
106 # The full help text.
107 HELP_TEXT : _detailed_help_text,
108 }
109
110 def _ExplainIfSudoNeeded(self, tf, dirs_to_remove):
111 """Explains what to do if sudo needed to update gsutil software.
112
113 Happens if gsutil was previously installed by a different user (typically if
114 someone originally installed in a shared file system location, using sudo).
115
116 Args:
117 tf: Opened TarFile.
118 dirs_to_remove: List of directories to remove.
119
120 Raises:
121 CommandException: if errors encountered.
122 """
123 system = platform.system()
124 # If running under Windows we don't need (or have) sudo.
125 if system.lower().startswith('windows'):
126 return
127
128 user_id = os.getuid()
129 if (os.stat(self.gsutil_bin_dir).st_uid == user_id
130 and os.stat(self.boto_lib_dir).st_uid == user_id):
131 return
132
133 # Won't fail - this command runs after main startup code that insists on
134 # having a config file.
135 config_files = ' '.join(self.config_file_list)
136 self._CleanUpUpdateCommand(tf, dirs_to_remove)
137 raise CommandException(
138 ('Since it was installed by a different user previously, you will need '
139 'to update using the following commands.\nYou will be prompted for '
140 'your password, and the install will run as "root". If you\'re unsure '
141 'what this means please ask your system administrator for help:'
142 '\n\tchmod 644 %s\n\tsudo env BOTO_CONFIG=%s gsutil update'
143 '\n\tchmod 600 %s') % (config_files, config_files, config_files),
144 informational=True)
145
146 # This list is checked during gsutil update by doing a lowercased
147 # slash-left-stripped check. For example "/Dev" would match the "dev" entry.
148 unsafe_update_dirs = [
149 'applications', 'auto', 'bin', 'boot', 'desktop', 'dev',
150 'documents and settings', 'etc', 'export', 'home', 'kernel', 'lib',
151 'lib32', 'library', 'lost+found', 'mach_kernel', 'media', 'mnt', 'net',
152 'null', 'network', 'opt', 'private', 'proc', 'program files', 'python',
153 'root', 'sbin', 'scripts', 'srv', 'sys', 'system', 'tmp', 'users', 'usr',
154 'var', 'volumes', 'win', 'win32', 'windows', 'winnt',
155 ]
156
157 def _EnsureDirsSafeForUpdate(self, dirs):
158 """Throws Exception if any of dirs is known to be unsafe for gsutil update.
159
160 This provides a fail-safe check to ensure we don't try to overwrite
161 or delete any important directories. (That shouldn't happen given the
162 way we construct tmp dirs, etc., but since the gsutil update cleanup
163 uses shutil.rmtree() it's prudent to add extra checks.)
164
165 Args:
166 dirs: List of directories to check.
167
168 Raises:
169 CommandException: If unsafe directory encountered.
170 """
171 for d in dirs:
172 if not d:
173 d = 'null'
174 if d.lstrip(os.sep).lower() in self.unsafe_update_dirs:
175 raise CommandException('EnsureDirsSafeForUpdate: encountered unsafe '
176 'directory (%s); aborting update' % d)
177
178 def _CleanUpUpdateCommand(self, tf, dirs_to_remove):
179 """Cleans up temp files etc. from running update command.
180
181 Args:
182 tf: Opened TarFile.
183 dirs_to_remove: List of directories to remove.
184
185 """
186 tf.close()
187 self._EnsureDirsSafeForUpdate(dirs_to_remove)
188 for directory in dirs_to_remove:
189 try:
190 shutil.rmtree(directory)
191 except OSError as e:
192 # Ignore errors while attempting to remove old dirs under Windows. They
193 # happen because of Windows exclusive file locking, and the update
194 # actually succeeds but just leaves the old versions around in the
195 # user's temp dir.
196 if not platform.system().lower().startswith('windows'):
197 raise
198
199 # Command entry point.
200 def RunCommand(self):
201 dirs_to_remove = []
202 # Retrieve gsutil tarball and check if it's newer than installed code.
203 # TODO: Store this version info as metadata on the tarball object and
204 # change this command's implementation to check that metadata instead of
205 # downloading the tarball to check the version info.
206 tmp_dir = tempfile.mkdtemp()
207 dirs_to_remove.append(tmp_dir)
208 os.chdir(tmp_dir)
209 print 'Checking for software update...'
210 if len(self.args):
211 update_from_uri_str = self.args[0]
212 if not update_from_uri_str.endswith('.tar.gz'):
213 raise CommandException(
214 'The update command only works with tar.gz files.')
215 else:
216 update_from_uri_str = 'gs://pub/gsutil.tar.gz'
217 self.command_runner.RunNamedCommand('cp', [update_from_uri_str,
218 'file://gsutil.tar.gz'],
219 self.headers, self.debug)
220 # Note: tf is closed in _CleanUpUpdateCommand.
221 tf = tarfile.open('gsutil.tar.gz')
222 tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions.
223 tf.extract('./gsutil/VERSION')
224
225 ver_file = open('gsutil/VERSION', 'r')
226 try:
227 latest_version_string = ver_file.read().rstrip('\n')
228 finally:
229 ver_file.close()
230
231 force_update = False
232 if self.sub_opts:
233 for o, unused_a in self.sub_opts:
234 if o == '-f':
235 force_update = True
236 if not force_update and self.gsutil_ver == latest_version_string:
237 self._CleanUpUpdateCommand(tf, dirs_to_remove)
238 if len(self.args):
239 raise CommandException('You already have %s installed.' %
240 update_from_uri_str, informational=True)
241 else:
242 raise CommandException('You already have the latest gsutil release '
243 'installed.', informational=True)
244
245 print(('This command will update to the "%s" version of\ngsutil at %s') %
246 (latest_version_string, self.gsutil_bin_dir))
247 self._ExplainIfSudoNeeded(tf, dirs_to_remove)
248
249 answer = raw_input('Proceed? [y/N] ')
250 if not answer or answer.lower()[0] != 'y':
251 self._CleanUpUpdateCommand(tf, dirs_to_remove)
252 raise CommandException('Not running update.', informational=True)
253
254 # Ignore keyboard interrupts during the update to reduce the chance someone
255 # hitting ^C leaves gsutil in a broken state.
256 signal.signal(signal.SIGINT, signal.SIG_IGN)
257
258 # self.gsutil_bin_dir lists the path where the code should end up (like
259 # /usr/local/gsutil), which is one level down from the relative path in the
260 # tarball (since the latter creates files in ./gsutil). So, we need to
261 # extract at the parent directory level.
262 gsutil_bin_parent_dir = os.path.dirname(self.gsutil_bin_dir)
263
264 # Extract tarball to a temporary directory in a sibling to gsutil_bin_dir.
265 old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
266 new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir)
267 dirs_to_remove.append(old_dir)
268 dirs_to_remove.append(new_dir)
269 self._EnsureDirsSafeForUpdate(dirs_to_remove)
270 try:
271 tf.extractall(path=new_dir)
272 except Exception, e:
273 self._CleanUpUpdateCommand(tf, dirs_to_remove)
274 raise CommandException('Update failed: %s.' % e)
275
276 # For enterprise mode (shared/central) installation, users with
277 # different user/group than the installation user/group must be
278 # able to run gsutil so we need to do some permissions adjustments
279 # here. Since enterprise mode is not not supported for Windows
280 # users, we can skip this step when running on Windows, which
281 # avoids the problem that Windows has no find or xargs command.
282 system = platform.system()
283 if not system.lower().startswith('windows'):
284 # Make all files and dirs in updated area readable by other
285 # and make all directories executable by other. These steps
286 os.system('chmod -R o+r ' + new_dir)
287 os.system('find ' + new_dir + ' -type d | xargs chmod o+x')
288
289 # Make main gsutil script readable and executable by other.
290 os.system('chmod o+rx ' + os.path.join(new_dir, 'gsutil'))
291
292 # Move old installation aside and new into place.
293 os.rename(self.gsutil_bin_dir, old_dir + os.sep + 'old')
294 os.rename(new_dir + os.sep + 'gsutil', self.gsutil_bin_dir)
295 self._CleanUpUpdateCommand(tf, dirs_to_remove)
296 signal.signal(signal.SIGINT, signal.SIG_DFL)
297 print 'Update complete.'
298 return 0
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698