def RunCommand(self): """Command entry point for the update command.""" if gslib.IS_PACKAGE_INSTALL: raise CommandException( 'The update command is only available for gsutil installed from a ' 'tarball. If you installed gsutil via another method, use the same ' 'method to update it.') if os.environ.get('CLOUDSDK_WRAPPER') == '1': raise CommandException( 'The update command is disabled for Cloud SDK installs. Please run ' '"gcloud components update" to update it. Note: the Cloud SDK ' 'incorporates updates to the underlying tools approximately every 2 ' 'weeks, so if you are attempting to update to a recently created ' 'release / pre-release of gsutil it may not yet be available via ' 'the Cloud SDK.') https_validate_certificates = CERTIFICATE_VALIDATION_ENABLED if not https_validate_certificates: raise CommandException( 'Your boto configuration has https_validate_certificates = False.\n' 'The update command cannot be run this way, for security reasons.' ) self._DisallowUpdataIfDataInGsutilDir() force_update = False no_prompt = False if self.sub_opts: for o, unused_a in self.sub_opts: if o == '-f': force_update = True if o == '-n': no_prompt = True dirs_to_remove = [] tmp_dir = tempfile.mkdtemp() dirs_to_remove.append(tmp_dir) os.chdir(tmp_dir) if not no_prompt: self.logger.info('Checking for software update...') if self.args: update_from_url_str = self.args[0] if not update_from_url_str.endswith('.tar.gz'): raise CommandException( 'The update command only works with tar.gz files.') for i, result in enumerate( self.WildcardIterator(update_from_url_str)): if i > 0: raise CommandException( 'Invalid update URL. Must name a single .tar.gz file.') storage_url = result.storage_url if storage_url.IsFileUrl() and not storage_url.IsDirectory(): if not force_update: raise CommandException(( '"update" command does not support "file://" URLs without the ' '-f option.')) elif not (storage_url.IsCloudUrl() and storage_url.IsObject()): raise CommandException( 'Invalid update object URL. Must name a single .tar.gz file.' ) else: update_from_url_str = GSUTIL_PUB_TARBALL # Try to retrieve version info from tarball metadata; failing that; download # the tarball and extract the VERSION file. The version lookup will fail # when running the update system test, because it retrieves the tarball from # a temp file rather than a cloud URL (files lack the version metadata). tarball_version = LookUpGsutilVersion(self.gsutil_api, update_from_url_str) if tarball_version: tf = None else: tf = self._FetchAndOpenGsutilTarball(update_from_url_str) tf.extractall() with open(os.path.join('gsutil', 'VERSION'), 'r') as ver_file: tarball_version = ver_file.read().strip() if not force_update and gslib.VERSION == tarball_version: self._CleanUpUpdateCommand(tf, dirs_to_remove) if self.args: raise CommandException('You already have %s installed.' % update_from_url_str, informational=True) else: raise CommandException( 'You already have the latest gsutil release ' 'installed.', informational=True) if not no_prompt: (_, major) = CompareVersions(tarball_version, gslib.VERSION) if major: print('\n'.join( textwrap.wrap( 'This command will update to the "%s" version of gsutil at %s. ' 'NOTE: This a major new version, so it is strongly recommended ' 'that you review the release note details at %s before updating to ' 'this version, especially if you use gsutil in scripts.' % (tarball_version, gslib.GSUTIL_DIR, RELEASE_NOTES_URL)))) else: print( 'This command will update to the "%s" version of\ngsutil at %s' % (tarball_version, gslib.GSUTIL_DIR)) self._ExplainIfSudoNeeded(tf, dirs_to_remove) if no_prompt: answer = 'y' else: answer = raw_input('Proceed? [y/N] ') if not answer or answer.lower()[0] != 'y': self._CleanUpUpdateCommand(tf, dirs_to_remove) raise CommandException('Not running update.', informational=True) if not tf: tf = self._FetchAndOpenGsutilTarball(update_from_url_str) # Ignore keyboard interrupts during the update to reduce the chance someone # hitting ^C leaves gsutil in a broken state. RegisterSignalHandler(signal.SIGINT, signal.SIG_IGN) # gslib.GSUTIL_DIR lists the path where the code should end up (like # /usr/local/gsutil), which is one level down from the relative path in the # tarball (since the latter creates files in ./gsutil). So, we need to # extract at the parent directory level. gsutil_bin_parent_dir = os.path.normpath( os.path.join(gslib.GSUTIL_DIR, '..')) # Extract tarball to a temporary directory in a sibling to GSUTIL_DIR. old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir) new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir) dirs_to_remove.append(old_dir) dirs_to_remove.append(new_dir) self._EnsureDirsSafeForUpdate(dirs_to_remove) try: tf.extractall(path=new_dir) except Exception, e: self._CleanUpUpdateCommand(tf, dirs_to_remove) raise CommandException('Update failed: %s.' % e)
def MaybeCheckForAndOfferSoftwareUpdate(self, command_name, debug): """Checks the last time we checked for an update and offers one if needed. Offer is made if the time since the last update check is longer than the configured threshold offers the user to update gsutil. Args: command_name: The name of the command being run. debug: Debug level to pass in to boto connection (range 0..3). Returns: True if the user decides to update. """ # Don't try to interact with user if: # - gsutil is not connected to a tty (e.g., if being run from cron); # - user is running gsutil -q # - user is running the config command (which could otherwise attempt to # check for an update for a user running behind a proxy, who has not yet # configured gsutil to go through the proxy; for such users we need the # first connection attempt to be made by the gsutil config command). # - user is running the version command (which gets run when using # gsutil -D, which would prevent users with proxy config problems from # sending us gsutil -D output). # - user is running the update command (which could otherwise cause an # additional note that an update is available when user is already trying # to perform an update); # - user specified gs_host (which could be a non-production different # service instance, in which case credentials won't work for checking # gsutil tarball). # - user is using a Cloud SDK install (which should only be updated via # gcloud components update) logger = logging.getLogger() gs_host = boto.config.get('Credentials', 'gs_host', None) if (not IsRunningInteractively() or command_name in ('config', 'update', 'ver', 'version') or not logger.isEnabledFor(logging.INFO) or gs_host or os.environ.get('CLOUDSDK_WRAPPER') == '1'): return False software_update_check_period = boto.config.getint( 'GSUtil', 'software_update_check_period', 30) # Setting software_update_check_period to 0 means periodic software # update checking is disabled. if software_update_check_period == 0: return False cur_ts = int(time.time()) if not os.path.isfile(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE): # Set last_checked_ts from date of VERSION file, so if the user installed # an old copy of gsutil it will get noticed (and an update offered) the # first time they try to run it. last_checked_ts = GetGsutilVersionModifiedTime() with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: f.write(str(last_checked_ts)) else: try: with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'r') as f: last_checked_ts = int(f.readline()) except (TypeError, ValueError): return False if (cur_ts - last_checked_ts > software_update_check_period * SECONDS_PER_DAY): # Create a credential-less gsutil API to check for the public # update tarball. gsutil_api = GcsJsonApi(self.bucket_storage_uri_class, logger, credentials=NoOpCredentials(), debug=debug) cur_ver = LookUpGsutilVersion(gsutil_api, GSUTIL_PUB_TARBALL) with open(LAST_CHECKED_FOR_GSUTIL_UPDATE_TIMESTAMP_FILE, 'w') as f: f.write(str(cur_ts)) (g, m) = CompareVersions(cur_ver, gslib.VERSION) if m: print '\n'.join( textwrap.wrap( 'A newer version of gsutil (%s) is available than the version you ' 'are running (%s). NOTE: This is a major new version, so it is ' 'strongly recommended that you review the release note details at ' '%s before updating to this version, especially if you use gsutil ' 'in scripts.' % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) if gslib.IS_PACKAGE_INSTALL: return False print answer = raw_input('Would you like to update [y/N]? ') return answer and answer.lower()[0] == 'y' elif g: print '\n'.join( textwrap.wrap( 'A newer version of gsutil (%s) is available than the version you ' 'are running (%s). A detailed log of gsutil release changes is ' 'available at %s if you would like to read them before updating.' % (cur_ver, gslib.VERSION, RELEASE_NOTES_URL))) if gslib.IS_PACKAGE_INSTALL: return False print answer = raw_input('Would you like to update [Y/n]? ') return not answer or answer.lower()[0] != 'n' return False
def test_CompareVersions(self): """Tests CompareVersions for various use cases.""" # CompareVersions(first, second) returns (g, m), where # g is True if first known to be greater than second, else False. # m is True if first known to be greater by at least 1 major version, (g, m) = CompareVersions('3.37', '3.2') self.assertTrue(g) self.assertFalse(m) (g, m) = CompareVersions('7', '2') self.assertTrue(g) self.assertTrue(m) (g, m) = CompareVersions('3.32', '3.32pre') self.assertTrue(g) self.assertFalse(m) (g, m) = CompareVersions('3.32pre', '3.31') self.assertTrue(g) self.assertFalse(m) (g, m) = CompareVersions('3.4pre', '3.3pree') self.assertTrue(g) self.assertFalse(m) (g, m) = CompareVersions('3.2', '3.37') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('2', '7') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('3.32pre', '3.32') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('3.31', '3.32pre') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('3.3pre', '3.3pre') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('foobar', 'baz') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('3.32', 'baz') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('3.4', '3.3') self.assertTrue(g) self.assertFalse(m) (g, m) = CompareVersions('3.3', '3.4') self.assertFalse(g) self.assertFalse(m) (g, m) = CompareVersions('4.1', '3.33') self.assertTrue(g) self.assertTrue(m) (g, m) = CompareVersions('3.10', '3.1') self.assertTrue(g) self.assertFalse(m)
def RunCommand(self): if gslib.IS_PACKAGE_INSTALL: raise CommandException( 'Update command is only available for gsutil installed from a ' 'tarball. If you installed gsutil via another method, use the same ' 'method to update it.') is_secure = BOTO_IS_SECURE if not is_secure[0]: raise CommandException( 'Your boto configuration has %s = False. The update command\n' 'cannot be run this way, for security reasons.' % is_secure[1]) self._DisallowUpdataIfDataInGsutilDir() force_update = False no_prompt = False if self.sub_opts: for o, unused_a in self.sub_opts: if o == '-f': force_update = True if o == '-n': no_prompt = True dirs_to_remove = [] tmp_dir = tempfile.mkdtemp() dirs_to_remove.append(tmp_dir) os.chdir(tmp_dir) if not no_prompt: self.logger.info('Checking for software update...') if self.args: update_from_uri_str = self.args[0] if not update_from_uri_str.endswith('.tar.gz'): raise CommandException( 'The update command only works with tar.gz files.') for i, result in enumerate( self.WildcardIterator(update_from_uri_str)): if i > 0: raise CommandException( 'Invalid update URI. Must name a single .tar.gz file.') if result.uri.names_file(): if not force_update: raise CommandException(( '"update" command does not support "file://" URIs without the ' '-f option.')) elif not result.uri.names_object(): raise CommandException( 'Invalid update object URI. Must name a single .tar.gz file.' ) else: update_from_uri_str = GSUTIL_PUB_TARBALL # Try to retrieve version info from tarball metadata; failing that; download # the tarball and extract the VERSION file. The version lookup will fail # when running the update system test, because it retrieves the tarball from # a temp file rather than a cloud URI (files lack the version metadata). suri_builder = StorageUriBuilder(self.debug, self.bucket_storage_uri_class) tarball_version = LookUpGsutilVersion( self.suri_builder.StorageUri(update_from_uri_str)) if tarball_version: tf = None else: tf = self._FetchAndOpenGsutilTarball(update_from_uri_str) tf.extractall() with open(os.path.join('gsutil', 'VERSION'), 'r') as ver_file: tarball_version = ver_file.read().strip() if not force_update and gslib.VERSION == tarball_version: self._CleanUpUpdateCommand(tf, dirs_to_remove) if self.args: raise CommandException('You already have %s installed.' % update_from_uri_str, informational=True) else: raise CommandException( 'You already have the latest gsutil release ' 'installed.', informational=True) if not no_prompt: (g, m) = CompareVersions(tarball_version, gslib.VERSION) if m: print('\n'.join( textwrap.wrap( 'This command will update to the "%s" version of gsutil at %s. ' 'NOTE: This a major new version, so it is strongly recommended ' 'that you review the release note details at %s before updating to ' 'this version, especially if you use gsutil in scripts.' % (tarball_version, gslib.GSUTIL_DIR, RELEASE_NOTES_URL)))) else: print( 'This command will update to the "%s" version of\ngsutil at %s' % (tarball_version, gslib.GSUTIL_DIR)) self._ExplainIfSudoNeeded(tf, dirs_to_remove) if no_prompt: answer = 'y' else: answer = raw_input('Proceed? [y/N] ') if not answer or answer.lower()[0] != 'y': self._CleanUpUpdateCommand(tf, dirs_to_remove) raise CommandException('Not running update.', informational=True) if not tf: tf = self._FetchAndOpenGsutilTarball(update_from_uri_str) # Ignore keyboard interrupts during the update to reduce the chance someone # hitting ^C leaves gsutil in a broken state. signal.signal(signal.SIGINT, signal.SIG_IGN) # gslib.GSUTIL_DIR lists the path where the code should end up (like # /usr/local/gsutil), which is one level down from the relative path in the # tarball (since the latter creates files in ./gsutil). So, we need to # extract at the parent directory level. gsutil_bin_parent_dir = os.path.normpath( os.path.join(gslib.GSUTIL_DIR, '..')) # Extract tarball to a temporary directory in a sibling to GSUTIL_DIR. old_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir) new_dir = tempfile.mkdtemp(dir=gsutil_bin_parent_dir) dirs_to_remove.append(old_dir) dirs_to_remove.append(new_dir) self._EnsureDirsSafeForUpdate(dirs_to_remove) try: tf.extractall(path=new_dir) except Exception, e: self._CleanUpUpdateCommand(tf, dirs_to_remove) raise CommandException('Update failed: %s.' % e)