def SetUpdateMask(ref, args, request): """Python hook that computes the update mask for a patch request. Args: ref: The game server cluster resource reference. args: The parsed args namespace. request: The update game server cluster request. Returns: Request with update mask set appropriately. Raises: NoFieldsSpecifiedError: If no fields were provided for updating. """ del ref update_mask = [] if args.IsSpecified('description'): update_mask.append('description') if (args.IsSpecified('update_labels') or args.IsSpecified('remove_labels') or args.IsSpecified('clear_labels')): update_mask.append('labels') if not args.dry_run and not update_mask: raise NoFieldsSpecifiedError( 'Must specify at least one parameter to update.') request.updateMask = ','.join(update_mask) if not args.dry_run: log.SetUserOutputEnabled(args.user_output_enabled != 'false') log.status.Print('Update request issued for: [{}]'.format( args.cluster)) log.SetUserOutputEnabled(False) return request
def ChooseUpdateOrPreviewMethod(unused_instance_ref, args): if args.dry_run: log.SetUserOutputEnabled(False) return 'previewUpdate' if args.preview_time: raise PreviewTimeFieldNotRelevantError( '`--preview-time` is only relevant if `--dry-run` is set to true.') log.SetUserOutputEnabled(False) return 'patch'
def CopyFilesToCodeBucket(modules, bucket, source_contexts): """Examines modules and copies files to a Google Cloud Storage bucket. Args: modules: [(str, ModuleYamlInfo)] List of pairs of module name, and parsed module information. bucket: str A URL to the Google Cloud Storage bucket where the files will be uploaded. source_contexts: [dict] List of json-serializable source contexts associated with the modules. Returns: A lookup from module name to a dictionary representing the manifest. See _BuildStagingDirectory. """ manifests = {} with file_utils.TemporaryDirectory() as staging_directory: for (module, info) in modules: source_directory = os.path.dirname(info.file) excluded_files_regex = info.parsed.skip_files.regex manifest = _BuildStagingDirectory(source_directory, staging_directory, bucket, excluded_files_regex, source_contexts) manifests[module] = manifest if any(manifest for manifest in manifests.itervalues()): log.status.Print('Copying files to Google Cloud Storage...') log.status.Print('Synchronizing files to [{b}].'.format(b=bucket)) try: log.SetUserOutputEnabled(False) def _StatusUpdate(result, unused_retry_state): log.info('Error synchronizing files. Return code: {0}. ' 'Retrying.'.format(result)) retryer = retry.Retryer(max_retrials=3, status_update_func=_StatusUpdate) def _ShouldRetry(return_code, unused_retry_state): return return_code != 0 try: retryer.RetryOnResult(cloud_storage.Rsync, (staging_directory, bucket), should_retry_if=_ShouldRetry) except retry.RetryException as e: raise exceptions.ToolException(( 'Could not synchronize files. The gsutil command exited with ' 'status [{s}]. Command output is available in [{l}].' ).format(s=e.last_result, l=log.GetLogFilePath())) finally: # Reset to the standard log level. log.SetUserOutputEnabled(None) return manifests
def ChooseUpdateOrPreviewMethod(unused_instance_ref, args): if args.dry_run: log.SetUserOutputEnabled(False) return 'previewUpdate' if args.preview_time: raise PreviewTimeFieldNotRelevantError( '`--preview-time` is only relevant if `--dry-run` is set to true.') log.status.Print('Update request issued for: [{}]'.format(args.realm)) log.SetUserOutputEnabled(False) return 'patch'
def CopyFilesToCodeBucket(service, source_dir, bucket_ref): """Examines services and copies files to a Google Cloud Storage bucket. Args: service: ServiceYamlInfo, The parsed service information. source_dir: str, path to the service's source directory bucket_ref: str A reference to a GCS bucket where the files will be uploaded. Returns: A dictionary representing the manifest. See _BuildStagingDirectory. """ with file_utils.TemporaryDirectory() as staging_directory: excluded_files_regex = service.parsed.skip_files.regex manifest = _BuildStagingDirectory(source_dir, staging_directory, bucket_ref, excluded_files_regex) if manifest: log.status.Print('Copying files to Google Cloud Storage...') log.status.Print('Synchronizing files to [{b}].' .format(b=bucket_ref.bucket)) try: log.SetUserOutputEnabled(False) def _StatusUpdate(result, unused_retry_state): log.info('Error synchronizing files. Return code: {0}. ' 'Retrying.'.format(result)) retryer = retry.Retryer(max_retrials=3, status_update_func=_StatusUpdate) def _ShouldRetry(return_code, unused_retry_state): return return_code != 0 # gsutil expects a trailing / dest_dir = bucket_ref.ToBucketUrl() try: retryer.RetryOnResult( storage_api.Rsync, (staging_directory, dest_dir), should_retry_if=_ShouldRetry) except retry.RetryException as e: raise exceptions.StorageError( ('Could not synchronize files. The gsutil command exited with ' 'status [{s}]. Command output is available in [{l}].').format( s=e.last_result, l=log.GetLogFilePath())) finally: # Reset to the standard log level. log.SetUserOutputEnabled(None) log.status.Print('File upload done.') return manifest
def testOutputDisabled(self): self.SetConsoleSize(20) log.SetUserOutputEnabled(False) with progress_tracker.ProgressTracker('tracker', autotick=True) as t: t.Tick() self.AssertOutputEquals('') self.AssertErrEquals('')
def ConvertOutput(response, args): # Try to reenable the log output which was disabled in the request hook log.SetUserOutputEnabled(args.user_output_enabled != 'false') if not args.dry_run: utils.WaitForOperation(response, utils.GetApiVersionFromArgs(args)) log.status.Print('Updated realm: [{}]'.format(args.realm)) return GetExistingResource(args) return response
def ChooseUpdateOrPreviewMethod(unused_instance_ref, args): """Python hook that decides to call previewUpdate or update api. Args: unused_instance_ref: The unused instace reference. args: The parsed args namespace. Returns: Method to be called. Raises: PreviewTimeFieldNotRelevantError: If preview-time provided when `--dry-run` is set to false. """ if args.dry_run: log.SetUserOutputEnabled(False) return 'previewUpdate' if args.preview_time: raise PreviewTimeFieldNotRelevantError( '`--preview-time` is only relevant if `--dry-run` is set to true.') log.SetUserOutputEnabled(False) return 'patch'
def CopyFilesToCodeBucket(modules, bucket): """Examines modules and copies files to a Google Cloud Storage bucket. Args: modules: [(str, ModuleYamlInfo)] List of pairs of module name, and parsed module information. bucket: str A URL to the Google Cloud Storage bucket where the files will be uploaded. Returns: A lookup from module name to a dictionary representing the manifest. See _BuildStagingDirectory. """ manifests = {} with file_utils.TemporaryDirectory() as staging_directory: for (module, info) in modules: source_directory = os.path.dirname(info.file) excluded_files_regex = info.parsed.skip_files.regex manifest = _BuildStagingDirectory(source_directory, staging_directory, bucket, excluded_files_regex) manifests[module] = manifest if any(manifest for manifest in manifests.itervalues()): log.status.Print('Copying files to Google Cloud Storage...') log.status.Print('Synchronizing files to [{b}].'.format(b=bucket)) try: log.SetUserOutputEnabled(False) exit_code = cloud_storage.Rsync(staging_directory, bucket) if exit_code: raise exceptions.ToolException( ('Could not synchronize files. The gsutil command exited with ' 'status [{s}]. Command output is available in [{l}].').format( s=exit_code, l=log.GetLogFilePath())) finally: # Reset to the standard log level. log.SetUserOutputEnabled(None) return manifests
def ChooseCreateOrPreviewMethod(unused_instance_ref, args): """Python hook that decides to call previewCreate or create api. Args: unused_instance_ref: The unused instace reference. args: The parsed args namespace. Returns: Method to be called. Raises: PreviewTimeFieldNotRelevantError: If preview-time provided when `--dry-run` is set to false. """ if args.dry_run: log.SetUserOutputEnabled(False) if not args.format: args.format = 'yaml' return 'previewCreate' if args.preview_time: raise PreviewTimeFieldNotRelevantError( '`--preview-time` is only relevant if `--dry-run` is set to true.') log.status.Print('Create request issued for: [{}]'.format(args.cluster)) log.SetUserOutputEnabled(False) return 'create'
def ConvertOutput(response, args): """Python hook that converts the output depending on preview or not both create and update api calls. Args: response: The reference to response instace. args: The parsed args namespace. Returns: Output response. """ # Try to reenable the log output which was disabled in the request hook log.SetUserOutputEnabled(args.user_output_enabled != 'false') if not args.dry_run: utils.WaitForOperation(response, utils.GetApiVersionFromArgs(args)) if 'update' in args.command_path: log.status.Print('Updated game server cluster: [{}]'.format( args.cluster)) else: log.status.Print('Created game server cluster: [{}]'.format( args.cluster)) return GetExistingResource(args) return response
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. """ if call_arg_complete: self._ArgComplete() if not args: args = sys.argv[1:] for s in args: try: s.decode('ascii') except UnicodeError: raise exceptions.InvalidStringException(s) # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name try: properties.VALUES.PushInvocationValues() args = self.__parser.parse_args(args) # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. log.SetUserOutputEnabled(None) log.SetVerbosity(None) command_path_string = '.'.join(args.command_path) # TODO(user): put a real version here metrics.Commands(command_path_string, None) for hook in self.__pre_run_hooks: hook.Run(command_path_string) result = args.cmd_func(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) return result except exceptions.ExitCodeNoError as exc: self._HandleKnownError(command_path_string, exc, print_error=False) except core_exceptions.Error as exc: self._HandleKnownError(command_path_string, exc, print_error=True) except Exception as exc: # Make sure any uncaught exceptions still make it into the log file. log.file_only_logger.debug(str(exc), exc_info=sys.exc_info()) metrics.Error(command_path_string, exc) raise finally: properties.VALUES.PopInvocationValues() # Reset these values to their previous state now that we popped the flag # values. log.SetUserOutputEnabled(None) log.SetVerbosity(None)
def Run(self, cli, args, pre_run_hooks=None, post_run_hooks=None): """Run this command with the given arguments. Args: cli: The cli.CLI object for this command line tool. args: The arguments for this command as a namespace. pre_run_hooks: [_RunHook], Things to run before the command. post_run_hooks: [_RunHook], Things to run after the command. Returns: The object returned by the module's Run() function. Raises: exceptions.Error: if thrown by the Run() function. """ command_path_string = '.'.join(self._path) # TODO(user): user-output-enabled was mostly needed for interactive # mode. There should still be the option to disable output (for things # like completion) but it can be cleaned up to be on by default. # Enable user output for CLI mode only if it is not explicitly set in the # properties (or given in the provided arguments that were just pushed into # the properties object). user_output_enabled = properties.VALUES.core.user_output_enabled.GetBool( ) set_user_output_property = user_output_enabled is None if set_user_output_property: properties.VALUES.core.user_output_enabled.Set(True) # Now that we have pushed the args, reload the settings so the flags will # take effect. These will use the values from the properties. old_user_output_enabled = log.SetUserOutputEnabled(None) old_verbosity = log.SetVerbosity(None) try: if pre_run_hooks: for hook in pre_run_hooks: hook.Run(command_path_string) def Http(**kwargs): # TODO(user) This check is required due to tests that use interactive # mode. Remove this check when all tests are converted to use cli mode. if hasattr(args, 'trace_token'): return core_cli.Http(cmd_path=command_path_string, trace_token=args.trace_token, **kwargs) else: return core_cli.Http(cmd_path=command_path_string, **kwargs) tool_context = self._config_hooks.load_context() last_group = None for context_filter in self._config_hooks.context_filters: last_group = context_filter(tool_context, Http, args) command_instance = self._common_type(cli=cli, context=tool_context, group=last_group, http_func=Http) def OutputFormatter(obj): command_instance.Display(args, obj) output_formatter = OutputFormatter def Format(obj): if not obj: return resource_printer.Print(obj, args.format or 'yaml', out=log.out) command_instance.format = Format if args.format: output_formatter = command_instance.format log.debug('Running %s with %s.', command_path_string, args) result = command_instance.Run(args) if properties.VALUES.core.user_output_enabled.GetBool(): output_formatter(result) if post_run_hooks: for hook in post_run_hooks: hook.Run(command_path_string) return result except exceptions.ExitCodeNoError as exc: msg = '({0}) {1}'.format(command_path_string, str(exc)) log.debug(msg, exc_info=sys.exc_info()) self._Exit(exc) except core_exceptions.Error as exc: msg = '({0}) {1}'.format(command_path_string, str(exc)) log.debug(msg, exc_info=sys.exc_info()) log.error(msg) self._Exit(exc) except Exception as exc: # Make sure any uncaught exceptions still make it into the log file. log.file_only_logger.debug(str(exc), exc_info=sys.exc_info()) raise finally: if set_user_output_property: properties.VALUES.core.user_output_enabled.Set(None) log.SetUserOutputEnabled(old_user_output_enabled) log.SetVerbosity(old_verbosity)
def SetUp(self): log.Reset() log.SetUserOutputEnabled(True)
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if type(args) is str: raise ValueError( 'Execute expects an iterable of strings, not a string.') if call_arg_complete: self._ArgComplete() if not args: args = sys.argv[1:] # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.PushFromArgs(args) properties.VALUES.PushInvocationValues() flag_names = None try: for s in args: try: s.decode('ascii') except UnicodeDecodeError: raise exceptions.InvalidCharacterInArgException( [sys.argv[0]] + args, s) args = self.__parser.parse_args(args) flag_names = self.__parser.GetFlagCollection() # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. log.SetUserOutputEnabled(None) log.SetVerbosity(None) command_path_string = '.'.join(args.command_path) properties.VALUES.SetInvocationValue( properties.VALUES.metrics.command_name, command_path_string, None) metrics.Commands(command_path_string, config.CLOUD_SDK_VERSION, flag_names) for hook in self.__pre_run_hooks: hook.Run(command_path_string) result = args.cmd_func(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) return result except exceptions.ExitCodeNoError as exc: self._HandleKnownError(command_path_string, exc, flag_names, print_error=False) except core_exceptions.Error as exc: self._HandleKnownError(command_path_string, exc, flag_names, print_error=True) except Exception as exc: if type(exc) in KNOWN_ERRORS: self._HandleKnownError(command_path_string, exc, flag_names, print_error=True) else: # Make sure any uncaught exceptions still make it into the log file. exc_printable = self.SafeExceptionToString(exc) log.debug(exc_printable, exc_info=sys.exc_info()) metrics.Error(command_path_string, exc, flag_names) raise finally: properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. log.SetUserOutputEnabled(None) log.SetVerbosity(None)
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if type(args) is str: raise ValueError( 'Execute expects an iterable of strings, not a string.') if call_arg_complete: self._ArgComplete() if not args: args = sys.argv[1:] for s in args: try: s.decode('ascii') except (UnicodeEncodeError, UnicodeError): try: s_printable = s.decode('utf-8') except (UnicodeEncodeError, UnicodeError): s_printable = repr(s) raise exceptions.InvalidStringException(s_printable) # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name named_configs.FLAG_OVERRIDE_STACK.AllocateFrame() properties.VALUES.PushInvocationValues() try: # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.ReplaceTop( named_configs.AdhocConfigFlagParse(args), properties.PropertiesFile.Invalidate) args = self.__parser.parse_args(args) # -h|--help|--document are dispatched by parse_args and never get here. # In principle it's possible that the initial round of ad-hoc parsing of # --configuration would not agree with the later parse by argparse. The # following warning is intended to be dead code, but just in case... if named_configs.FLAG_OVERRIDE_STACK.Peek() != args.configuration: log.warn('Problem parsing --configration flag. Using named ' 'flag value --configuration=[{0}].'.format( named_configs.FLAG_OVERRIDE_STACK.Peek())) # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. log.SetUserOutputEnabled(None) log.SetVerbosity(None) command_path_string = '.'.join(args.command_path) metrics.Commands(command_path_string, config.CLOUD_SDK_VERSION) for hook in self.__pre_run_hooks: hook.Run(command_path_string) result = args.cmd_func(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) return result except exceptions.ExitCodeNoError as exc: self._HandleKnownError(command_path_string, exc, print_error=False) except core_exceptions.Error as exc: self._HandleKnownError(command_path_string, exc, print_error=True) except Exception as exc: if type(exc) in KNOWN_ERRORS: self._HandleKnownError(command_path_string, exc, print_error=True) else: # Make sure any uncaught exceptions still make it into the log file. exc_printable = self.SafeExceptionToString(exc) log.file_only_logger.debug(exc_printable, exc_info=sys.exc_info()) metrics.Error(command_path_string, exc) raise finally: properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. log.SetUserOutputEnabled(None) log.SetVerbosity(None)
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if isinstance(args, six.string_types): raise ValueError( 'Execute expects an iterable of strings, not a string.') # The argparse module does not handle unicode args when run in Python 2 # because it uses str(x) even when type(x) is unicode. This sets itself up # for failure because it converts unicode strings back to byte strings which # will trigger ASCII codec exceptions. It works in Python 3 because str() is # equivalent to unicode() in Python 3. The next Pythonically magic and dirty # statement coaxes the Python 3 behavior out of argparse running in # Python 2. Doing it here ensures that the workaround is in place for # calliope argparse use cases. argparse.str = six.text_type # We need the argparse 1.2.1 patch in _SubParsersActionCall. # TODO(b/77288697) delete after py3 tests use non-hermetic python if argparse.__version__ == '1.1': argparse._SubParsersAction.__call__ = _SubParsersActionCall # pylint: disable=protected-access if call_arg_complete: _ArgComplete(self.__top_element.ai) if not args: args = sys.argv[1:] # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.PushFromArgs(args) properties.VALUES.PushInvocationValues() # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name specified_arg_names = None # Convert py2 args to text. argv = [console_attr.Decode(arg) for arg in args] if six.PY2 else args old_user_output_enabled = None old_verbosity = None try: args = self.__parser.parse_args(_ApplyFlagsFile(argv)) if args.CONCEPT_ARGS is not None: args.CONCEPT_ARGS.ParseConcepts() calliope_command = args._GetCommand() # pylint: disable=protected-access command_path_string = '.'.join(calliope_command.GetPath()) specified_arg_names = args.GetSpecifiedArgNames() # If the CLI has not been reloaded since the last command execution (e.g. # in test runs), args.CONCEPTS may contain cached values. if args.CONCEPTS is not None: args.CONCEPTS.Reset() # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. old_user_output_enabled = log.SetUserOutputEnabled(None) old_verbosity = log.SetVerbosity(None) # Set the command_name property so it is persisted until the process ends. # Only do this for the top level command that can be detected by looking # at the stack. It will have one initial level, and another level added by # the PushInvocationValues earlier in this method. if len(properties.VALUES.GetInvocationStack()) == 2: properties.VALUES.metrics.command_name.Set(command_path_string) # Set the invocation value for all commands, this is lost when popped properties.VALUES.SetInvocationValue( properties.VALUES.metrics.command_name, command_path_string, None) for hook in self.__pre_run_hooks: hook.Run(command_path_string) resources = calliope_command.Run(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) # Preserve generator or static list resources. if isinstance(resources, types.GeneratorType): def _Yield(): """Activates generator exceptions.""" try: for resource in resources: yield resource except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) return _Yield() # Do this last. If there is an error, the error handler will log the # command execution along with the error. metrics.Commands(command_path_string, config.CLOUD_SDK_VERSION, specified_arg_names) return resources except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) finally: properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. if old_user_output_enabled is not None: log.SetUserOutputEnabled(old_user_output_enabled) if old_verbosity is not None: log.SetVerbosity(old_verbosity)
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if isinstance(args, basestring): raise ValueError( 'Execute expects an iterable of strings, not a string.') # The argparse module does not handle unicode args when run in Python 2 # because it uses str(x) even when type(x) is unicode. This sets itself up # for failure because it converts unicode strings back to byte strings which # will trigger ASCII codec exceptions. It works in Python 3 because str() is # equivalent to unicode() in Python 3. The next Pythonically magic and dirty # statement coaxes the Python 3 behavior out of argparse running in # Python 2. Doing it here ensures that the workaround is in place for # calliope argparse use cases. argparse.str = unicode if call_arg_complete: self._ArgComplete() if not args: args = sys.argv[1:] # help ... is the same as ... --help or ... --document=style=help. We use # --document=style=help to signal the metrics.Help() 'help' label in # actions.RenderDocumentAction().Action(). It doesn't matter if we append # to a command that already has --help or --document=style=... because the # first --help/--document from the left takes effect. Note that # `help ... --help` produces help for the help command itself. if args and args[0] == 'help' and '--help' not in args: args = args[1:] + ['--document=style=help'] # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.PushFromArgs(args) properties.VALUES.PushInvocationValues() # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name flag_collection = None argv = self._ConvertNonAsciiArgsToUnicode(args) try: args = self.__parser.parse_args(argv) command_path_string = '.'.join(args.command_path) if not args.calliope_command.IsUnicodeSupported(): self._EnforceAsciiArgs(argv) flag_collection = self.__parser.GetFlagCollection() # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. log.SetUserOutputEnabled(None) log.SetVerbosity(None) properties.VALUES.SetInvocationValue( properties.VALUES.metrics.command_name, command_path_string, None) metrics.Commands(command_path_string, config.CLOUD_SDK_VERSION, flag_collection) for hook in self.__pre_run_hooks: hook.Run(command_path_string) resources = args.calliope_command.Run(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) # Preserve generator or static list resources. if isinstance(resources, types.GeneratorType): def _Yield(): """Activates generator exceptions.""" try: for resource in resources: yield resource except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, flag_collection) return _Yield() return resources except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, flag_collection) finally: properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. log.SetUserOutputEnabled(None) log.SetVerbosity(None)
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if isinstance(args, basestring): raise ValueError( 'Execute expects an iterable of strings, not a string.') # The argparse module does not handle unicode args when run in Python 2 # because it uses str(x) even when type(x) is unicode. This sets itself up # for failure because it converts unicode strings back to byte strings which # will trigger ASCII codec exceptions. It works in Python 3 because str() is # equivalent to unicode() in Python 3. The next Pythonically magic and dirty # statement coaxes the Python 3 behavior out of argparse running in # Python 2. Doing it here ensures that the workaround is in place for # calliope argparse use cases. argparse.str = unicode if call_arg_complete: _ArgComplete(self.__top_element.ai) if not args: args = sys.argv[1:] # help ... is the same as ... --help or ... --document=style=help. We use # --document=style=help to signal the metrics.Help() 'help' label in # actions.RenderDocumentAction().Action(). It doesn't matter if we append # to a command that already has --help or --document=style=... because the # first --help/--document from the left takes effect. Note that # `help ... --help` produces help for the help command itself. if args and args[0] == 'help' and '--help' not in args: args = args[1:] + ['--document=style=help'] # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.PushFromArgs(args) properties.VALUES.PushInvocationValues() # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name specified_arg_names = None argv = self._ConvertNonAsciiArgsToUnicode(args) old_user_output_enabled = None old_verbosity = None try: args = self.__parser.parse_args(argv) calliope_command = args._GetCommand() # pylint: disable=protected-access command_path_string = '.'.join(calliope_command.GetPath()) if not calliope_command.IsUnicodeSupported(): self._EnforceAsciiArgs(argv) specified_arg_names = args.GetSpecifiedArgNames() # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. old_user_output_enabled = log.SetUserOutputEnabled(None) old_verbosity = log.SetVerbosity(None) # Set the command_name property so it is persisted until the process ends. # Only do this for the top level command that can be detected by looking # at the stack. It will have one initial level, and another level added by # the PushInvocationValues earlier in this method. if len(properties.VALUES.GetInvocationStack()) == 2: properties.VALUES.metrics.command_name.Set(command_path_string) # Set the invocation value for all commands, this is lost when popped properties.VALUES.SetInvocationValue( properties.VALUES.metrics.command_name, command_path_string, None) if properties.VALUES.core.capture_session_file.Get() is not None: capturer = session_capturer.SessionCapturer() capturer.CaptureArgs(args) capturer.CaptureState() capturer.CaptureProperties(properties.VALUES.AllValues()) session_capturer.SessionCapturer.capturer = capturer for hook in self.__pre_run_hooks: hook.Run(command_path_string) resources = calliope_command.Run(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) # Preserve generator or static list resources. if isinstance(resources, types.GeneratorType): def _Yield(): """Activates generator exceptions.""" try: for resource in resources: yield resource except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) return _Yield() # Do this last. If there is an error, the error handler will log the # command execution along with the error. metrics.Commands(command_path_string, config.CLOUD_SDK_VERSION, specified_arg_names) return resources except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) finally: if session_capturer.SessionCapturer.capturer is not None: with open(properties.VALUES.core.capture_session_file.Get(), 'w') as f: session_capturer.SessionCapturer.capturer.Print(f) properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. if old_user_output_enabled is not None: log.SetUserOutputEnabled(old_user_output_enabled) if old_verbosity is not None: log.SetVerbosity(old_verbosity)
def Run(self, args, command=None, cli_mode=False, pre_run_hooks=None, post_run_hooks=None): """Run this command with the given arguments. Args: args: The arguments for this command as a namespace. command: The bound Command object that is used to run this Command. cli_mode: bool, True if running from the command line, False if running interactively. pre_run_hooks: [_RunHook], Things to run before the command. post_run_hooks: [_RunHook], Things to run after the command. Returns: The object returned by the module's Run() function. Raises: exceptions.Error: if thrown by the Run() function. """ command_path_string = '.'.join(self._path) properties.VALUES.PushArgs(args) # Enable user output for CLI mode only if it is not explicitly set in the # properties (or given in the provided arguments that were just pushed into # the properties object). user_output_enabled = properties.VALUES.core.user_output_enabled.GetBool( ) set_user_output_property = cli_mode and user_output_enabled is None if set_user_output_property: properties.VALUES.core.user_output_enabled.Set(True) # Now that we have pushed the args, reload the settings so the flags will # take effect. These will use the values from the properties. old_user_output_enabled = log.SetUserOutputEnabled(None) old_verbosity = log.SetVerbosity(None) try: if cli_mode and pre_run_hooks: for hook in pre_run_hooks: hook.Run(command_path_string) tool_context = self._config_hooks.load_context() last_group = None for context_filter in self._config_hooks.context_filters: last_group = context_filter(tool_context, args) command_instance = self._common_type( context=tool_context, entry_point=command.EntryPoint(), command=command, group=last_group) def OutputFormatter(obj): command_instance.Display(args, obj) output_formatter = OutputFormatter def Format(obj): if not obj: return resource_printer.Print(obj, args.format or 'yaml', out=log.out) command_instance.format = Format if args.format: output_formatter = command_instance.format log.debug('Running %s with %s.', command_path_string, args) result = command_instance.Run(args) if cli_mode: output_formatter(result) if cli_mode and post_run_hooks: for hook in post_run_hooks: hook.Run(command_path_string) return result except core_exceptions.Error as exc: msg = '({0}) {1}'.format(command_path_string, str(exc)) log.debug(msg, exc_info=sys.exc_info()) if cli_mode: log.error(msg) self._Exit(exc) else: raise except Exception as exc: # Make sure any uncaught exceptions still make it into the log file. log.file_only_logger.debug(str(exc), exc_info=sys.exc_info()) raise finally: if set_user_output_property: properties.VALUES.core.user_output_enabled.Set(None) log.SetUserOutputEnabled(old_user_output_enabled) log.SetVerbosity(old_verbosity) properties.VALUES.PopArgs()
def SetUp(self): log.SetUserOutputEnabled(True) # Disable console_io.PromptContinue long line folding. self.StartObjectPatch(console_io, '_DoWrap', lambda x: x)