| 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 update command for updating gsutil.""" |
| 16 |
| 17 from __future__ import absolute_import |
| 14 | 18 |
| 15 import os | 19 import os |
| 16 import shutil | 20 import shutil |
| 17 import signal | 21 import signal |
| 22 import stat |
| 18 import tarfile | 23 import tarfile |
| 19 import tempfile | 24 import tempfile |
| 20 import textwrap | 25 import textwrap |
| 21 | 26 |
| 22 import gslib | 27 import gslib |
| 23 from gslib.command import Command | 28 from gslib.command import Command |
| 24 from gslib.command import COMMAND_NAME | 29 from gslib.cs_api_map import ApiSelector |
| 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 PROVIDER_URIS_OK | |
| 30 from gslib.command import SUPPORTED_SUB_ARGS | |
| 31 from gslib.command import URIS_START_ARG | |
| 32 from gslib.exception import CommandException | 30 from gslib.exception import CommandException |
| 33 from gslib.help_provider import HELP_NAME | 31 from gslib.util import CERTIFICATE_VALIDATION_ENABLED |
| 34 from gslib.help_provider import HELP_NAME_ALIASES | |
| 35 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | |
| 36 from gslib.help_provider import HELP_TEXT | |
| 37 from gslib.help_provider import HELP_TYPE | |
| 38 from gslib.help_provider import HelpType | |
| 39 from gslib.storage_uri_builder import StorageUriBuilder | |
| 40 from gslib.util import BOTO_IS_SECURE | |
| 41 from gslib.util import CompareVersions | 32 from gslib.util import CompareVersions |
| 33 from gslib.util import GetBotoConfigFileList |
| 42 from gslib.util import GSUTIL_PUB_TARBALL | 34 from gslib.util import GSUTIL_PUB_TARBALL |
| 43 from gslib.util import IS_CYGWIN | 35 from gslib.util import IS_CYGWIN |
| 44 from gslib.util import IS_WINDOWS | 36 from gslib.util import IS_WINDOWS |
| 45 from gslib.util import LookUpGsutilVersion | 37 from gslib.util import LookUpGsutilVersion |
| 46 from gslib.util import LookUpGsutilVersion | |
| 47 from gslib.util import RELEASE_NOTES_URL | 38 from gslib.util import RELEASE_NOTES_URL |
| 48 | 39 |
| 49 | 40 |
| 50 _detailed_help_text = (""" | 41 _DETAILED_HELP_TEXT = (""" |
| 51 <B>SYNOPSIS</B> | 42 <B>SYNOPSIS</B> |
| 52 gsutil update [-f] [-n] [uri] | 43 gsutil update [-f] [-n] [uri] |
| 53 | 44 |
| 54 | 45 |
| 55 <B>DESCRIPTION</B> | 46 <B>DESCRIPTION</B> |
| 56 The gsutil update command downloads the latest gsutil release, checks its | 47 The gsutil update command downloads the latest gsutil release, checks its |
| 57 version, and offers to let you update to it if it differs from the version | 48 version, and offers to let you update to it if it differs from the version |
| 58 you're currently running. | 49 you're currently running. |
| 59 | 50 |
| 60 Once you say "Y" to the prompt of whether to install the update, the gsutil | 51 Once you say "Y" to the prompt of whether to install the update, the gsutil |
| (...skipping 29 matching lines...) Expand all Loading... |
| 90 a corrupted local copy. | 81 a corrupted local copy. |
| 91 | 82 |
| 92 -n Causes update command to run without prompting [Y/n] whether to | 83 -n Causes update command to run without prompting [Y/n] whether to |
| 93 continue if an update is available. | 84 continue if an update is available. |
| 94 """ % GSUTIL_PUB_TARBALL) | 85 """ % GSUTIL_PUB_TARBALL) |
| 95 | 86 |
| 96 | 87 |
| 97 class UpdateCommand(Command): | 88 class UpdateCommand(Command): |
| 98 """Implementation of gsutil update command.""" | 89 """Implementation of gsutil update command.""" |
| 99 | 90 |
| 100 # Command specification (processed by parent class). | 91 # Command specification. See base class for documentation. |
| 101 command_spec = { | 92 command_spec = Command.CreateCommandSpec( |
| 102 # Name of command. | 93 'update', |
| 103 COMMAND_NAME: 'update', | 94 command_name_aliases=['refresh'], |
| 104 # List of command name aliases. | 95 min_args=0, |
| 105 COMMAND_NAME_ALIASES: ['refresh'], | 96 max_args=1, |
| 106 # Min number of args required by this command. | 97 supported_sub_args='fn', |
| 107 MIN_ARGS: 0, | 98 file_url_ok=True, |
| 108 # Max number of args required by this command, or NO_MAX. | 99 provider_url_ok=False, |
| 109 MAX_ARGS: 1, | 100 urls_start_arg=0, |
| 110 # Getopt-style string specifying acceptable sub args. | 101 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 111 SUPPORTED_SUB_ARGS: 'fn', | 102 gs_default_api=ApiSelector.JSON, |
| 112 # True if file URIs acceptable for this command. | 103 ) |
| 113 FILE_URIS_OK: True, | 104 # Help specification. See help_provider.py for documentation. |
| 114 # True if provider-only URIs acceptable for this command. | 105 help_spec = Command.HelpSpec( |
| 115 PROVIDER_URIS_OK: False, | 106 help_name='update', |
| 116 # Index in args of first URI arg. | 107 help_name_aliases=['refresh'], |
| 117 URIS_START_ARG: 0, | 108 help_type='command_help', |
| 118 } | 109 help_one_line_summary='Update to the latest gsutil release', |
| 119 help_spec = { | 110 help_text=_DETAILED_HELP_TEXT, |
| 120 # Name of command or auxiliary help info for which this help applies. | 111 subcommand_help_text={}, |
| 121 HELP_NAME: 'update', | 112 ) |
| 122 # List of help name aliases. | |
| 123 HELP_NAME_ALIASES: ['refresh'], | |
| 124 # Type of help: | |
| 125 HELP_TYPE: HelpType.COMMAND_HELP, | |
| 126 # One line summary of this help. | |
| 127 HELP_ONE_LINE_SUMMARY: 'Update to the latest gsutil release', | |
| 128 # The full help text. | |
| 129 HELP_TEXT: _detailed_help_text, | |
| 130 } | |
| 131 | 113 |
| 132 def _DisallowUpdataIfDataInGsutilDir(self): | 114 def _DisallowUpdataIfDataInGsutilDir(self): |
| 133 """ | 115 """Disallows the update command if files not in the gsutil distro are found. |
| 134 Disallows the update command from running if files other than those | 116 |
| 135 distributed with gsutil are found. This prevents users from losing data if | 117 This prevents users from losing data if they are in the habit of running |
| 136 they are in the habit of running gsutil from the gsutil directory, and | 118 gsutil from the gsutil directory and leaving data in that directory. |
| 137 leaving data in that directory. | |
| 138 | 119 |
| 139 This will also detect someone attempting to run gsutil update from a git | 120 This will also detect someone attempting to run gsutil update from a git |
| 140 repo, since the top-level directory will contain git files and dirs (like | 121 repo, since the top-level directory will contain git files and dirs (like |
| 141 .git) that are not distributed with gsutil. | 122 .git) that are not distributed with gsutil. |
| 142 | 123 |
| 143 Raises: | 124 Raises: |
| 144 CommandException: if files other than those distributed with gsutil found. | 125 CommandException: if files other than those distributed with gsutil found. |
| 145 """ | 126 """ |
| 146 # Manifest includes recursive-includes of gslib and scripts. Directly add | 127 # Manifest includes recursive-includes of gslib. Directly add |
| 147 # those to the list here so we will skip them in os.listdir() loop without | 128 # those to the list here so we will skip them in os.listdir() loop without |
| 148 # having to build deeper handling of the MANIFEST file here. Also include | 129 # having to build deeper handling of the MANIFEST file here. Also include |
| 149 # 'third_party', which isn't present in manifest but gets added to the | 130 # 'third_party', which isn't present in manifest but gets added to the |
| 150 # gsutil distro by the gsutil submodule configuration; and the MANIFEST.in | 131 # gsutil distro by the gsutil submodule configuration; and the MANIFEST.in |
| 151 # and CHANGES.md files. | 132 # and CHANGES.md files. |
| 152 manifest_lines = ['gslib', 'scripts', 'third_party', 'MANIFEST.in', | 133 manifest_lines = ['gslib', 'third_party', 'MANIFEST.in', 'CHANGES.md'] |
| 153 'CHANGES.md'] | 134 |
| 154 | |
| 155 try: | 135 try: |
| 156 with open(os.path.join(gslib.GSUTIL_DIR, 'MANIFEST.in'), 'r') as fp: | 136 with open(os.path.join(gslib.GSUTIL_DIR, 'MANIFEST.in'), 'r') as fp: |
| 157 for line in fp.readlines(): | 137 for line in fp: |
| 158 if line.startswith('include '): | 138 if line.startswith('include '): |
| 159 manifest_lines.append(line.split()[-1]) | 139 manifest_lines.append(line.split()[-1]) |
| 160 except IOError: | 140 except IOError: |
| 161 self.logger.warn('MANIFEST.in not found in %s.\nSkipping user data ' | 141 self.logger.warn('MANIFEST.in not found in %s.\nSkipping user data ' |
| 162 'check.\n', gslib.GSUTIL_DIR) | 142 'check.\n', gslib.GSUTIL_DIR) |
| 163 return | 143 return |
| 164 | 144 |
| 165 # Look just at top-level directory. We don't try to catch data dropped into | 145 # Look just at top-level directory. We don't try to catch data dropped into |
| 166 # subdirs (like gslib) because that would require deeper parsing of | 146 # subdirs (like gslib) because that would require deeper parsing of |
| 167 # MANFFEST.in, and most users who drop data into gsutil dir do so at the top | 147 # MANFFEST.in, and most users who drop data into gsutil dir do so at the top |
| 168 # level directory. | 148 # level directory. |
| 169 for file in os.listdir(gslib.GSUTIL_DIR): | 149 for filename in os.listdir(gslib.GSUTIL_DIR): |
| 170 if file[-4:] == '.pyc': | 150 if filename.endswith('.pyc'): |
| 171 # Ignore compiled code. | 151 # Ignore compiled code. |
| 172 continue | 152 continue |
| 173 if file not in manifest_lines: | 153 if filename not in manifest_lines: |
| 174 raise CommandException('\n'.join(textwrap.wrap( | 154 raise CommandException('\n'.join(textwrap.wrap( |
| 175 'A file (%s) that is not distributed with gsutil was found in ' | 155 'A file (%s) that is not distributed with gsutil was found in ' |
| 176 'the gsutil directory. The update command cannot run with user ' | 156 'the gsutil directory. The update command cannot run with user ' |
| 177 'data in the gsutil directory.' % | 157 'data in the gsutil directory.' % |
| 178 os.path.join(gslib.GSUTIL_DIR, file)))) | 158 os.path.join(gslib.GSUTIL_DIR, filename)))) |
| 179 | 159 |
| 180 def _ExplainIfSudoNeeded(self, tf, dirs_to_remove): | 160 def _ExplainIfSudoNeeded(self, tf, dirs_to_remove): |
| 181 """Explains what to do if sudo needed to update gsutil software. | 161 """Explains what to do if sudo needed to update gsutil software. |
| 182 | 162 |
| 183 Happens if gsutil was previously installed by a different user (typically if | 163 Happens if gsutil was previously installed by a different user (typically if |
| 184 someone originally installed in a shared file system location, using sudo). | 164 someone originally installed in a shared file system location, using sudo). |
| 185 | 165 |
| 186 Args: | 166 Args: |
| 187 tf: Opened TarFile. | 167 tf: Opened TarFile. |
| 188 dirs_to_remove: List of directories to remove. | 168 dirs_to_remove: List of directories to remove. |
| 189 | 169 |
| 190 Raises: | 170 Raises: |
| 191 CommandException: if errors encountered. | 171 CommandException: if errors encountered. |
| 192 """ | 172 """ |
| 193 # If running under Windows or Cygwin we don't need (or have) sudo. | 173 # If running under Windows or Cygwin we don't need (or have) sudo. |
| 194 if IS_CYGWIN or IS_WINDOWS: | 174 if IS_CYGWIN or IS_WINDOWS: |
| 195 return | 175 return |
| 196 | 176 |
| 197 user_id = os.getuid() | 177 user_id = os.getuid() |
| 198 if os.stat(gslib.GSUTIL_DIR).st_uid == user_id: | 178 if os.stat(gslib.GSUTIL_DIR).st_uid == user_id: |
| 199 return | 179 return |
| 200 | 180 |
| 201 # Won't fail - this command runs after main startup code that insists on | 181 # Won't fail - this command runs after main startup code that insists on |
| 202 # having a config file. | 182 # having a config file. |
| 203 config_files = ' '.join(self.config_file_list) | 183 config_file_list = GetBotoConfigFileList() |
| 184 config_files = ' '.join(config_file_list) |
| 204 self._CleanUpUpdateCommand(tf, dirs_to_remove) | 185 self._CleanUpUpdateCommand(tf, dirs_to_remove) |
| 186 |
| 187 # Pick current protection of each boto config file for command that restores |
| 188 # protection (rather than fixing at 600) to support use cases like how GCE |
| 189 # installs a service account with an /etc/boto.cfg file protected to 644. |
| 190 chmod_cmds = [] |
| 191 for config_file in config_file_list: |
| 192 mode = oct(stat.S_IMODE((os.stat(config_file)[stat.ST_MODE]))) |
| 193 chmod_cmds.append('\n\tsudo chmod %s %s' % (mode, config_file)) |
| 194 |
| 205 raise CommandException('\n'.join(textwrap.wrap( | 195 raise CommandException('\n'.join(textwrap.wrap( |
| 206 'Since it was installed by a different user previously, you will need ' | 196 'Since it was installed by a different user previously, you will need ' |
| 207 'to update using the following commands. You will be prompted for your ' | 197 'to update using the following commands. You will be prompted for your ' |
| 208 'password, and the install will run as "root". If you\'re unsure what ' | 198 'password, and the install will run as "root". If you\'re unsure what ' |
| 209 'this means please ask your system administrator for help:')) | 199 'this means please ask your system administrator for help:')) + ( |
| 210 + ('\n\tchmod 644 %s\n\tsudo env BOTO_CONFIG=%s gsutil update' | 200 '\n\tsudo chmod 0644 %s\n\tsudo env BOTO_CONFIG="%s" %s update' |
| 211 '\n\tchmod 600 %s') % (config_files, config_files, config_files), | 201 '%s') % (config_files, config_files, self.gsutil_path, |
| 212 informational=True) | 202 ' '.join(chmod_cmds)), informational=True) |
| 213 | 203 |
| 214 # This list is checked during gsutil update by doing a lowercased | 204 # This list is checked during gsutil update by doing a lowercased |
| 215 # slash-left-stripped check. For example "/Dev" would match the "dev" entry. | 205 # slash-left-stripped check. For example "/Dev" would match the "dev" entry. |
| 216 unsafe_update_dirs = [ | 206 unsafe_update_dirs = [ |
| 217 'applications', 'auto', 'bin', 'boot', 'desktop', 'dev', | 207 'applications', 'auto', 'bin', 'boot', 'desktop', 'dev', |
| 218 'documents and settings', 'etc', 'export', 'home', 'kernel', 'lib', | 208 'documents and settings', 'etc', 'export', 'home', 'kernel', 'lib', |
| 219 'lib32', 'library', 'lost+found', 'mach_kernel', 'media', 'mnt', 'net', | 209 'lib32', 'library', 'lost+found', 'mach_kernel', 'media', 'mnt', 'net', |
| 220 'null', 'network', 'opt', 'private', 'proc', 'program files', 'python', | 210 'null', 'network', 'opt', 'private', 'proc', 'program files', 'python', |
| 221 'root', 'sbin', 'scripts', 'srv', 'sys', 'system', 'tmp', 'users', 'usr', | 211 'root', 'sbin', 'scripts', 'srv', 'sys', 'system', 'tmp', 'users', 'usr', |
| 222 'var', 'volumes', 'win', 'win32', 'windows', 'winnt', | 212 'var', 'volumes', 'win', 'win32', 'windows', 'winnt', |
| 223 ] | 213 ] |
| 224 | 214 |
| 225 def _EnsureDirsSafeForUpdate(self, dirs): | 215 def _EnsureDirsSafeForUpdate(self, dirs): |
| 226 """Throws Exception if any of dirs is known to be unsafe for gsutil update. | 216 """Raises Exception if any of dirs is known to be unsafe for gsutil update. |
| 227 | 217 |
| 228 This provides a fail-safe check to ensure we don't try to overwrite | 218 This provides a fail-safe check to ensure we don't try to overwrite |
| 229 or delete any important directories. (That shouldn't happen given the | 219 or delete any important directories. (That shouldn't happen given the |
| 230 way we construct tmp dirs, etc., but since the gsutil update cleanup | 220 way we construct tmp dirs, etc., but since the gsutil update cleanup |
| 231 uses shutil.rmtree() it's prudent to add extra checks.) | 221 uses shutil.rmtree() it's prudent to add extra checks.) |
| 232 | 222 |
| 233 Args: | 223 Args: |
| 234 dirs: List of directories to check. | 224 dirs: List of directories to check. |
| 235 | 225 |
| 236 Raises: | 226 Raises: |
| (...skipping 21 matching lines...) Expand all Loading... |
| 258 try: | 248 try: |
| 259 shutil.rmtree(directory) | 249 shutil.rmtree(directory) |
| 260 except OSError: | 250 except OSError: |
| 261 # Ignore errors while attempting to remove old dirs under Windows. They | 251 # Ignore errors while attempting to remove old dirs under Windows. They |
| 262 # happen because of Windows exclusive file locking, and the update | 252 # happen because of Windows exclusive file locking, and the update |
| 263 # actually succeeds but just leaves the old versions around in the | 253 # actually succeeds but just leaves the old versions around in the |
| 264 # user's temp dir. | 254 # user's temp dir. |
| 265 if not IS_WINDOWS: | 255 if not IS_WINDOWS: |
| 266 raise | 256 raise |
| 267 | 257 |
| 268 # Command entry point. | |
| 269 def RunCommand(self): | 258 def RunCommand(self): |
| 259 """Command entry point for the update command.""" |
| 270 | 260 |
| 271 if gslib.IS_PACKAGE_INSTALL: | 261 if gslib.IS_PACKAGE_INSTALL: |
| 272 raise CommandException( | 262 raise CommandException( |
| 273 'Update command is only available for gsutil installed from a ' | 263 'The update command is only available for gsutil installed from a ' |
| 274 'tarball. If you installed gsutil via another method, use the same ' | 264 'tarball. If you installed gsutil via another method, use the same ' |
| 275 'method to update it.') | 265 'method to update it.') |
| 276 | 266 |
| 277 is_secure = BOTO_IS_SECURE | 267 if os.environ.get('CLOUDSDK_WRAPPER') == '1': |
| 278 if not is_secure[0]: | |
| 279 raise CommandException( | 268 raise CommandException( |
| 280 'Your boto configuration has %s = False. The update command\n' | 269 'The update command is disabled for Cloud SDK installs. Please run ' |
| 281 'cannot be run this way, for security reasons.' % is_secure[1]) | 270 '"gcloud components update" to update it. Note: the Cloud SDK ' |
| 271 'incorporates updates to the underlying tools approximately every 2 ' |
| 272 'weeks, so if you are attempting to update to a recently created ' |
| 273 'release / pre-release of gsutil it may not yet be available via ' |
| 274 'the Cloud SDK.') |
| 275 |
| 276 https_validate_certificates = CERTIFICATE_VALIDATION_ENABLED |
| 277 if not https_validate_certificates: |
| 278 raise CommandException( |
| 279 'Your boto configuration has https_validate_certificates = False.\n' |
| 280 'The update command cannot be run this way, for security reasons.') |
| 282 | 281 |
| 283 self._DisallowUpdataIfDataInGsutilDir() | 282 self._DisallowUpdataIfDataInGsutilDir() |
| 284 | 283 |
| 285 force_update = False | 284 force_update = False |
| 286 no_prompt = False | 285 no_prompt = False |
| 287 if self.sub_opts: | 286 if self.sub_opts: |
| 288 for o, unused_a in self.sub_opts: | 287 for o, unused_a in self.sub_opts: |
| 289 if o == '-f': | 288 if o == '-f': |
| 290 force_update = True | 289 force_update = True |
| 291 if o == '-n': | 290 if o == '-n': |
| 292 no_prompt = True | 291 no_prompt = True |
| 293 | 292 |
| 294 dirs_to_remove = [] | 293 dirs_to_remove = [] |
| 295 tmp_dir = tempfile.mkdtemp() | 294 tmp_dir = tempfile.mkdtemp() |
| 296 dirs_to_remove.append(tmp_dir) | 295 dirs_to_remove.append(tmp_dir) |
| 297 os.chdir(tmp_dir) | 296 os.chdir(tmp_dir) |
| 298 | 297 |
| 299 if not no_prompt: | 298 if not no_prompt: |
| 300 self.logger.info('Checking for software update...') | 299 self.logger.info('Checking for software update...') |
| 301 if self.args: | 300 if self.args: |
| 302 update_from_uri_str = self.args[0] | 301 update_from_uri_str = self.args[0] |
| 303 if not update_from_uri_str.endswith('.tar.gz'): | 302 if not update_from_uri_str.endswith('.tar.gz'): |
| 304 raise CommandException( | 303 raise CommandException( |
| 305 'The update command only works with tar.gz files.') | 304 'The update command only works with tar.gz files.') |
| 306 for i, result in enumerate(self.WildcardIterator(update_from_uri_str)): | 305 for i, result in enumerate(self.WildcardIterator(update_from_uri_str)): |
| 307 if i > 0: | 306 if i > 0: |
| 308 raise CommandException( | 307 raise CommandException( |
| 309 'Invalid update URI. Must name a single .tar.gz file.') | 308 'Invalid update URI. Must name a single .tar.gz file.') |
| 310 if result.uri.names_file(): | 309 storage_url = result.storage_url |
| 310 if storage_url.IsFileUrl() and not storage_url.IsDirectory(): |
| 311 if not force_update: | 311 if not force_update: |
| 312 raise CommandException( | 312 raise CommandException( |
| 313 ('"update" command does not support "file://" URIs without the ' | 313 ('"update" command does not support "file://" URIs without the ' |
| 314 '-f option.')) | 314 '-f option.')) |
| 315 elif not result.uri.names_object(): | 315 elif not (storage_url.IsCloudUrl() and storage_url.IsObject()): |
| 316 raise CommandException( | 316 raise CommandException( |
| 317 'Invalid update object URI. Must name a single .tar.gz file.') | 317 'Invalid update object URI. Must name a single .tar.gz file.') |
| 318 else: | 318 else: |
| 319 update_from_uri_str = GSUTIL_PUB_TARBALL | 319 update_from_uri_str = GSUTIL_PUB_TARBALL |
| 320 | 320 |
| 321 # Try to retrieve version info from tarball metadata; failing that; download | 321 # Try to retrieve version info from tarball metadata; failing that; download |
| 322 # the tarball and extract the VERSION file. The version lookup will fail | 322 # the tarball and extract the VERSION file. The version lookup will fail |
| 323 # when running the update system test, because it retrieves the tarball from | 323 # when running the update system test, because it retrieves the tarball from |
| 324 # a temp file rather than a cloud URI (files lack the version metadata). | 324 # a temp file rather than a cloud URI (files lack the version metadata). |
| 325 suri_builder = StorageUriBuilder(self.debug, self.bucket_storage_uri_class) | 325 tarball_version = LookUpGsutilVersion(self.gsutil_api, update_from_uri_str) |
| 326 tarball_version = LookUpGsutilVersion( | |
| 327 self.suri_builder.StorageUri(update_from_uri_str)) | |
| 328 if tarball_version: | 326 if tarball_version: |
| 329 tf = None | 327 tf = None |
| 330 else: | 328 else: |
| 331 tf = self._FetchAndOpenGsutilTarball(update_from_uri_str) | 329 tf = self._FetchAndOpenGsutilTarball(update_from_uri_str) |
| 332 tf.extractall() | 330 tf.extractall() |
| 333 with open(os.path.join('gsutil', 'VERSION'), 'r') as ver_file: | 331 with open(os.path.join('gsutil', 'VERSION'), 'r') as ver_file: |
| 334 tarball_version = ver_file.read().strip() | 332 tarball_version = ver_file.read().strip() |
| 335 | 333 |
| 336 if not force_update and gslib.VERSION == tarball_version: | 334 if not force_update and gslib.VERSION == tarball_version: |
| 337 self._CleanUpUpdateCommand(tf, dirs_to_remove) | 335 self._CleanUpUpdateCommand(tf, dirs_to_remove) |
| 338 if self.args: | 336 if self.args: |
| 339 raise CommandException('You already have %s installed.' % | 337 raise CommandException('You already have %s installed.' % |
| 340 update_from_uri_str, informational=True) | 338 update_from_uri_str, informational=True) |
| 341 else: | 339 else: |
| 342 raise CommandException('You already have the latest gsutil release ' | 340 raise CommandException('You already have the latest gsutil release ' |
| 343 'installed.', informational=True) | 341 'installed.', informational=True) |
| 344 | 342 |
| 345 if not no_prompt: | 343 if not no_prompt: |
| 346 (g, m) = CompareVersions(tarball_version, gslib.VERSION) | 344 (_, major) = CompareVersions(tarball_version, gslib.VERSION) |
| 347 if m: | 345 if major: |
| 348 print('\n'.join(textwrap.wrap( | 346 print('\n'.join(textwrap.wrap( |
| 349 'This command will update to the "%s" version of gsutil at %s. ' | 347 'This command will update to the "%s" version of gsutil at %s. ' |
| 350 'NOTE: This a major new version, so it is strongly recommended ' | 348 'NOTE: This a major new version, so it is strongly recommended ' |
| 351 'that you review the release note details at %s before updating to ' | 349 'that you review the release note details at %s before updating to ' |
| 352 'this version, especially if you use gsutil in scripts.' | 350 'this version, especially if you use gsutil in scripts.' |
| 353 % (tarball_version, gslib.GSUTIL_DIR, RELEASE_NOTES_URL)))) | 351 % (tarball_version, gslib.GSUTIL_DIR, RELEASE_NOTES_URL)))) |
| 354 else: | 352 else: |
| 355 print('This command will update to the "%s" version of\ngsutil at %s' | 353 print('This command will update to the "%s" version of\ngsutil at %s' |
| 356 % (tarball_version, gslib.GSUTIL_DIR)) | 354 % (tarball_version, gslib.GSUTIL_DIR)) |
| 357 self._ExplainIfSudoNeeded(tf, dirs_to_remove) | 355 self._ExplainIfSudoNeeded(tf, dirs_to_remove) |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 390 self._CleanUpUpdateCommand(tf, dirs_to_remove) | 388 self._CleanUpUpdateCommand(tf, dirs_to_remove) |
| 391 raise CommandException('Update failed: %s.' % e) | 389 raise CommandException('Update failed: %s.' % e) |
| 392 | 390 |
| 393 # For enterprise mode (shared/central) installation, users with | 391 # For enterprise mode (shared/central) installation, users with |
| 394 # different user/group than the installation user/group must be | 392 # different user/group than the installation user/group must be |
| 395 # able to run gsutil so we need to do some permissions adjustments | 393 # able to run gsutil so we need to do some permissions adjustments |
| 396 # here. Since enterprise mode is not not supported for Windows | 394 # here. Since enterprise mode is not not supported for Windows |
| 397 # users, we can skip this step when running on Windows, which | 395 # users, we can skip this step when running on Windows, which |
| 398 # avoids the problem that Windows has no find or xargs command. | 396 # avoids the problem that Windows has no find or xargs command. |
| 399 if not IS_WINDOWS: | 397 if not IS_WINDOWS: |
| 400 # Make all files and dirs in updated area readable by other | 398 # Make all files and dirs in updated area owner-RW and world-R, and make |
| 401 # and make all directories executable by other. | 399 # all directories owner-RWX and world-RX. |
| 402 os.system('chmod -R o+r ' + new_dir) | 400 for dirname, subdirs, filenames in os.walk(new_dir): |
| 403 os.system('find ' + new_dir + ' -type d | xargs chmod o+x') | 401 for filename in filenames: |
| 402 fd = os.open(os.path.join(dirname, filename), os.O_RDONLY) |
| 403 os.fchmod(fd, stat.S_IWRITE | stat.S_IRUSR | |
| 404 stat.S_IRGRP | stat.S_IROTH) |
| 405 os.close(fd) |
| 406 for subdir in subdirs: |
| 407 fd = os.open(os.path.join(dirname, subdir), os.O_RDONLY) |
| 408 os.fchmod(fd, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH | |
| 409 stat.S_IRGRP | stat.S_IROTH) |
| 410 os.close(fd) |
| 404 | 411 |
| 405 # Make main gsutil script readable and executable by other. | 412 # Make main gsutil script owner-RWX and world-RX. |
| 406 os.system('chmod o+rx ' + os.path.join(new_dir, 'gsutil')) | 413 fd = os.open(os.path.join(new_dir, 'gsutil', 'gsutil'), os.O_RDONLY) |
| 414 os.fchmod(fd, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | |
| 415 stat.S_IROTH | stat.S_IXOTH) |
| 416 os.close(fd) |
| 407 | 417 |
| 408 # Move old installation aside and new into place. | 418 # Move old installation aside and new into place. |
| 409 os.rename(gslib.GSUTIL_DIR, os.path.join(old_dir, 'old')) | 419 os.rename(gslib.GSUTIL_DIR, os.path.join(old_dir, 'old')) |
| 410 os.rename(os.path.join(new_dir, 'gsutil'), gslib.GSUTIL_DIR) | 420 os.rename(os.path.join(new_dir, 'gsutil'), gslib.GSUTIL_DIR) |
| 411 self._CleanUpUpdateCommand(tf, dirs_to_remove) | 421 self._CleanUpUpdateCommand(tf, dirs_to_remove) |
| 412 signal.signal(signal.SIGINT, signal.SIG_DFL) | 422 signal.signal(signal.SIGINT, signal.SIG_DFL) |
| 413 self.logger.info('Update complete.') | 423 self.logger.info('Update complete.') |
| 414 return 0 | 424 return 0 |
| 415 | 425 |
| 416 def _FetchAndOpenGsutilTarball(self, update_from_uri_str): | 426 def _FetchAndOpenGsutilTarball(self, update_from_uri_str): |
| 417 self.command_runner.RunNamedCommand( | 427 self.command_runner.RunNamedCommand( |
| 418 'cp', [update_from_uri_str, 'file://gsutil.tar.gz'], self.headers, | 428 'cp', [update_from_uri_str, 'file://gsutil.tar.gz'], self.headers, |
| 419 self.debug, skip_update_check=True) | 429 self.debug, skip_update_check=True) |
| 420 # Note: tf is closed in _CleanUpUpdateCommand. | 430 # Note: tf is closed in _CleanUpUpdateCommand. |
| 421 tf = tarfile.open('gsutil.tar.gz') | 431 tf = tarfile.open('gsutil.tar.gz') |
| 422 tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions. | 432 tf.errorlevel = 1 # So fatal tarball unpack errors raise exceptions. |
| 423 return tf | 433 return tf |
| OLD | NEW |