def Run(self, args): api_client = appengine_api_client.GetApiClient() # Why do this? It lets us know if we're missing services up front (fail # fast), and we get to control the error messages all_services = api_client.ListServices() services = service_util.GetMatchingServices(all_services, args.services) if args.version: console_io.PromptContinue( 'Deleting version [{0}] of {1} [{2}].'.format( args.version, text.Pluralize(len(services), 'service'), ', '.join(map(str, services))), cancel_on_no=True) versions = [ version_util.Version(api_client.project, s.id, args.version) for s in services ] version_util.DeleteVersions(api_client, versions) else: console_io.PromptContinue('Deleting {0} [{1}].'.format( text.Pluralize(len(services), 'service'), ', '.join(map(str, services))), cancel_on_no=True) service_util.DeleteServices(api_client, services)
def _Update(self): """Update() helper method. Returns the number of changed help text files.""" with file_utils.TemporaryDirectory() as temp_dir: walker_util.HelpTextGenerator(self._cli, temp_dir).Walk(hidden=True) diff = HelpTextAccumulator() DirDiff(self._help_dir, temp_dir, diff) if diff.invalid_file_count: # Bail out early on invalid content errors. These must be corrected # before proceeding. raise HelpTextUpdateError( '{0} help text {1} with invalid content must be fixed.'.format( diff.invalid_file_count, text.Pluralize(diff.invalid_file_count, 'file'))) ops = {} for op in ['add', 'delete', 'edit']: ops[op] = [] changes = 0 for op, path in sorted(diff.GetChanges()): changes += 1 if not self._test or changes < TEST_CHANGES_DISPLAY_MAX: log.status.Print('{0} {1}'.format(op, path)) ops[op].append(path) if self._test: if changes: if changes >= TEST_CHANGES_DISPLAY_MAX: log.status.Print('...') log.status.Print('{0} help test {1} changed'.format( changes, text.Pluralize(changes, 'file'))) return changes op = 'add' if ops[op]: for path in ops[op]: dest_path = os.path.join(self._help_dir, path) subdir = os.path.dirname(dest_path) if subdir: file_utils.MakeDir(subdir) temp_path = os.path.join(temp_dir, path) shutil.copyfile(temp_path, dest_path) op = 'edit' if ops[op]: for path in ops[op]: dest_path = os.path.join(self._help_dir, path) temp_path = os.path.join(temp_dir, path) shutil.copyfile(temp_path, dest_path) op = 'delete' if ops[op]: for path in ops[op]: dest_path = os.path.join(self._help_dir, path) try: os.remove(dest_path) except OSError: pass return changes
def ClusterUpgradeMessage(name, cluster=None, master=False, node_pool_name=None, new_version=None, concurrent_node_count=None): """Get a message to print during gcloud container clusters upgrade. Args: name: str, the name of the cluster being upgraded. cluster: the cluster object. master: bool, if the upgrade applies to the master version. node_pool_name: str, the name of the node pool if the upgrade is for a specific node pool. new_version: str, the name of the new version, if given. concurrent_node_count: int, the number of nodes to upgrade concurrently. Raises: NodePoolError: if the node pool name can't be found in the cluster. Returns: str, a message about which nodes in the cluster will be upgraded and to which version. """ current_version = None if new_version: new_version_message = 'version [{}]'.format(new_version) else: new_version_message = 'master version' if master: node_message = 'Master' if cluster: current_version = cluster.currentMasterVersion elif node_pool_name: node_message = 'All nodes in node pool [{}]'.format(node_pool_name) if cluster: current_version = _NodePoolFromCluster(cluster, node_pool_name).version else: if cluster: node_message = 'All nodes ({} {})'.format( cluster.currentNodeCount, text.Pluralize(cluster.currentNodeCount, 'node')) current_version = cluster.currentNodeVersion else: node_message = 'All nodes' concurrent_message = '' if not master and concurrent_node_count: concurrent_message = '{} {} will be upgraded at a time. '.format( concurrent_node_count, text.Pluralize(concurrent_node_count, 'node')) if current_version: version_message = 'version [{}]'.format(current_version) else: version_message = 'its current version' return ('{} of cluster [{}] will be upgraded from {} to {}. {}' 'This operation is long-running and will block other operations ' 'on the cluster (including delete) until it has run to completion.' .format(node_message, name, version_message, new_version_message, concurrent_message))
def _NodeUpgradeMessage(name, cluster, node_pool_name, new_version, concurrent_node_count): """Returns the prompt message during a node upgrade. Args: name: str, the name of the cluster being upgraded. cluster: the cluster object. node_pool_name: str, the name of the node pool if the upgrade is for a specific node pool. new_version: str, the name of the new version, if given. concurrent_node_count: int, the number of nodes to upgrade concurrently. Raises: NodePoolError: if the node pool name can't be found in the cluster. Returns: str, a message about which nodes in the cluster will be upgraded and to which version. """ node_message = 'All nodes' current_version = None if node_pool_name: node_message = '{} in node pool [{}]'.format(node_message, node_pool_name) if cluster: current_version = _NodePoolFromCluster(cluster, node_pool_name).version elif cluster: node_message = '{} ({} {})'.format( node_message, cluster.currentNodeCount, text.Pluralize(cluster.currentNodeCount, 'node')) current_version = cluster.currentNodeVersion if current_version: version_message = 'version [{}]'.format(current_version) else: version_message = 'its current version' if not new_version and cluster: new_version = cluster.currentMasterVersion if new_version: new_version_message = 'version [{}]'.format(new_version) else: new_version_message = 'the master version' concurrent_message = '' if concurrent_node_count: concurrent_message = ' {} {} will be upgraded at a time.'.format( concurrent_node_count, text.Pluralize(concurrent_node_count, 'node')) return ('{} of cluster [{}] will be upgraded from {} to {}.{}'.format( node_message, name, version_message, new_version_message, concurrent_message))
def Run(self, args): holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) project = properties.VALUES.core.project.Get(required=True) igm_refs = (instance_groups_flags. MULTISCOPE_INSTANCE_GROUP_MANAGERS_ARG.ResolveAsResource)( args, holder.resources, default_scope=compute_scope.ScopeEnum.ZONE, scope_lister=flags.GetDefaultScopeLister( holder.client, project)) scope_name = self._GetCommonScopeNameForRefs(igm_refs) utils.PromptForDeletion(igm_refs, scope_name=scope_name, prompt_title=None) requests = list( self._CreateDeleteRequests(holder.client.apitools_client, igm_refs)) resources = [] # Delete autoscalers first. errors = [] autoscaler_delete_requests = self._GenerateAutoscalerDeleteRequests( holder, project, mig_requests=requests) if autoscaler_delete_requests: with progress_tracker.ProgressTracker( 'Deleting ' + text.Pluralize( len(autoscaler_delete_requests), 'autoscaler'), autotick=False, ) as tracker: resources = holder.client.MakeRequests( autoscaler_delete_requests, errors, progress_tracker=tracker) if errors: utils.RaiseToolException(errors) # Now delete instance group managers. errors = [] with progress_tracker.ProgressTracker( 'Deleting ' + text.Pluralize(len(requests), 'Managed Instance Group'), autotick=False, ) as tracker: resources += holder.client.MakeRequests(requests, errors, progress_tracker=tracker) if errors: utils.RaiseToolException(errors) return resources
def _PromptSuggestedScopeChoice(resource_name, underspecified_names, scope_enum, suggested_resource): if scope_enum == compute_scope.ScopeEnum.GLOBAL: log.status.Print( 'No scope specified. Using [{0}] for {1}: [{2}].'.format( scope_enum.flag_name, text.Pluralize(len(underspecified_names), resource_name), ', '.join(underspecified_names))) else: log.status.Print( 'No {0} specified. Using {0} [{1}] for {2}: [{3}].'.format( scope_enum.flag_name, suggested_resource, text.Pluralize(len(underspecified_names), resource_name), ', '.join(underspecified_names)))
def DeleteVersions(api_client, versions): """Delete the given version of the given services.""" errors = {} for version in versions: version_path = '{0}/{1}'.format(version.service, version.id) # TODO(b/37279801): Use the core gcloud pollers instead. try: with progress_tracker.ProgressTracker( 'Deleting [{0}]'.format(version_path)): api_client.DeleteVersion(version.service, version.id) except (calliope_exceptions.HttpException, operations_util.OperationError, operations_util.OperationTimeoutError, app_exceptions.Error) as err: errors[version_path] = str(err) if errors: printable_errors = {} for version_path, error_msg in errors.items(): printable_errors[version_path] = '[{0}]: {1}'.format( version_path, error_msg) raise VersionsDeleteError('Issue deleting {0}: [{1}]\n\n'.format( text.Pluralize(len(printable_errors), 'version'), ', '.join( printable_errors.keys())) + '\n\n'.join(printable_errors.values()))
def Run(self, args): client = appengine_api_client.GetApiClientForTrack(self.ReleaseTrack()) services = client.ListServices() all_versions = client.ListVersions(services) # Sort versions to make behavior deterministic enough for unit testing. versions = sorted(version_util.GetMatchingVersions(all_versions, args.versions, args.service)) services_to_delete = [] for service in sorted(services): service_versions = len( [v for v in all_versions if v.service == service.id]) versions_to_delete = len([v for v in versions if v.service == service.id]) if service_versions == versions_to_delete and service_versions > 0: if service.id == 'default': raise VersionsDeleteError( 'The default service (module) may not be deleted, and must ' 'comprise at least one version.' ) else: services_to_delete.append(service) for version in copy.copy(versions): if version.service == service.id: versions.remove(version) for version in versions: if version.traffic_split: # TODO(b/32869800): collect info on all versions before raising. raise VersionsDeleteError( 'Version [{version}] is currently serving {allocation:.2f}% of ' 'traffic for service [{service}].\n\n' 'Please move all traffic away via one of the following methods:\n' ' - deploying a new version with the `--promote` argument\n' ' - running `gcloud app services set-traffic`\n' ' - running `gcloud app versions migrate`'.format( version=version.id, allocation=version.traffic_split * 100, service=version.service)) if services_to_delete: word = text.Pluralize(len(services_to_delete), 'service') log.warning( 'Requested deletion of all existing versions for the following {0}:' .format(word)) resource_printer.Print(services_to_delete, 'list', out=log.status) console_io.PromptContinue(prompt_string=( '\nYou cannot delete all versions of a service. Would you like to ' 'delete the entire {0} instead?').format(word), cancel_on_no=True) service_util.DeleteServices(client, services_to_delete) if versions: fmt = 'list[title="Deleting the following versions:"]' resource_printer.Print(versions, fmt, out=log.status) console_io.PromptContinue(cancel_on_no=True) else: if not services_to_delete: log.warning('No matching versions found.') version_util.DeleteVersions(client, versions)
def AddRegionFlag(parser, resource_type, operation_type, flag_prefix=None, explanation=REGION_PROPERTY_EXPLANATION, hidden=False, plural=False, custom_plural=None): """Adds a --region flag to the given parser. Args: parser: argparse parser. resource_type: str, human readable name for the resource type this flag is qualifying, for example "instance group". operation_type: str, human readable name for the operation, for example "update" or "delete". flag_prefix: str, flag will be named --{flag_prefix}-region. explanation: str, detailed explanation of the flag. hidden: bool, If True, --region argument help will be hidden. plural: bool, resource_type will be pluralized or not depending on value. custom_plural: str, If plural is True then this string will be used as resource types, otherwise resource_types will be pluralized by appending 's'. """ short_help = 'The region of the {0} to {1}.'.format( text.Pluralize( int(plural) + 1, resource_type or '', custom_plural), operation_type) flag_name = 'region' if flag_prefix is not None: flag_name = flag_prefix + '-' + flag_name parser.add_argument( '--' + flag_name, completer=completers.RegionsCompleter, action=actions.StoreProperty(properties.VALUES.compute.region), hidden=hidden, help='{0} {1}'.format(short_help, explanation))
def ValidateFieldConfig(unused_ref, args, request): """Python hook to validate the field configuration of the given request. Note that this hook is only called after the request has been formed based on the spec. Thus, the validation of the user's choices for order and array-config, as well as the check for the required field-path attribute, have already been performed. As such the only remaining things to verify are that the user has specified at least 2 fields, and that exactly one of order or array-config was specified for each field. Args: unused_ref: The resource ref (unused). args: The parsed arg namespace. request: The request formed based on the spec. Returns: The original request assuming the field configuration is valid. Raises: InvalidArgumentException: If the field configuration is invalid. """ if len(args.field_config) < 2: raise exceptions.InvalidArgumentException( '--field-config', 'Composite indexes must be configured with at least 2 fields. For ' 'single-field index management, use the commands under `gcloud ' 'firestore indexes fields`.') invalid_field_configs = [] for field_config in args.field_config: # Because of the way declarative ArgDict parsing works, the type of # field_config here is already an apitools message, as opposed to an # ArgDict. order = field_config.order array_config = field_config.arrayConfig if (order and array_config) or (not order and not array_config): invalid_field_configs.append(field_config) if invalid_field_configs: raise exceptions.InvalidArgumentException( '--field-config', "Exactly one of 'order' or 'array-config' must be specified for the " "{field_word} with the following {path_word}: [{paths}].".format( field_word=text.Pluralize(len(invalid_field_configs), 'field'), path_word=text.Pluralize(len(invalid_field_configs), 'path'), paths=', '.join(field_config.fieldPath for field_config in invalid_field_configs))) return request
def UpdateCliTrees(cli=None, commands=None, directory=None, verbose=False, warn_on_exceptions=False): """(re)generates the CLI trees in directory if non-existent or out ot date. This function uses the progress tracker because some of the updates can take ~minutes. Args: cli: The default CLI. If not None then the default CLI is also updated. commands: Update only the commands in this list. directory: The directory containing the CLI tree JSON files. If None then the default installation directories are used. verbose: Display a status line for up to date CLI trees if True. warn_on_exceptions: Emits warning messages in lieu of exceptions. Used during installation. Raises: NoCliTreeGeneratorForCommand: A command in commands is not supported (doesn't have a generator). """ # Initialize the list of directories to search for CLI tree files. The default # CLI tree is only searched for and generated in directories[0]. Other # existing trees are updated in the directory in which they were found. New # trees are generated in directories[-1]. directories = [] if directory: directories.append(directory) else: try: directories.append(cli_tree.CliTreeDir()) except cli_tree.SdkRootNotFoundError as e: if not warn_on_exceptions: raise log.warn(str(e)) directories.append(cli_tree.CliTreeConfigDir()) if not commands: commands = set([cli_tree.DEFAULT_CLI_NAME] + GENERATORS.keys()) failed = [] for command in sorted(commands): if command != cli_tree.DEFAULT_CLI_NAME: generator = GetCliTreeGenerator(command) try: generator.LoadOrGenerate(directories=directories, verbose=verbose, warn_on_exceptions=warn_on_exceptions) except subprocess.CalledProcessError: failed.append(command) elif cli: cli_tree.Load(cli=cli, path=cli_tree.CliTreePath(directory=directories[0]), verbose=verbose) if failed: message = 'No CLI tree {} for [{}].'.format( text_utils.Pluralize(len(failed), 'generator'), ', '.join(sorted(failed))) if not warn_on_exceptions: raise NoCliTreeGeneratorForCommand(message) log.warn(message)
def Run(self, args): client = appengine_api_client.GetApiClient(self.Http(timeout=None)) services = client.ListServices() all_versions = client.ListVersions(services) versions = version_util.GetMatchingVersions(all_versions, args.versions, args.service, client.project) services_to_delete = [] for service in services: if (len([v for v in all_versions if v.service == service.id]) == len( [v for v in versions if v.service == service.id])): services_to_delete.append(service) for version in copy.copy(versions): if version.service == service.id: versions.remove(version) for version in versions: if version.traffic_split: # TODO(user): mention `migrate` once it's implemented. raise VersionsDeleteError( 'Version [{version}] is currently serving {allocation:.2f}% of ' 'traffic for service [{service}].\n\n' 'Please move all traffic away by deploying a new version with the' '`--promote` argument or running `gcloud preview app services ' 'set-traffic`.'.format(version=version.id, allocation=version.traffic_split * 100, service=version.service)) if services_to_delete: word = text.Pluralize(len(services_to_delete), 'service') log.warn( 'Requested deletion of all existing versions for the following ' '{0}:'.format(word)) printer = console_io.ListPrinter('') printer.Print(services_to_delete, output_stream=log.status) console_io.PromptContinue(prompt_string=( '\nYou cannot delete all versions of a service. Would you like to ' 'delete the entire {0} instead?').format(word), cancel_on_no=True) service_util.DeleteServices(client, services_to_delete) if versions: printer = console_io.ListPrinter( 'Deleting the following versions:') printer.Print(versions, output_stream=log.status) console_io.PromptContinue(cancel_on_no=True) else: if not services_to_delete: log.warn('No matching versions found.') version_util.DeleteVersions(client, versions)
def _GetHelpTextForAttribute(self, attribute): """Helper to get the help text for the attribute arg.""" if self._IsAnchor(attribute): help_text = ANCHOR_HELP if not self.plural else PLURAL_ANCHOR_HELP else: help_text = attribute.help_text expansion_name = text.Pluralize(2 if self.plural else 1, self.resource_spec.name, plural=getattr(self.resource_spec, 'plural_name', None)) return help_text.format(resource=expansion_name)
def _Update(self, restrict): """Update() helper method. Returns the number of changed help doc files.""" with file_utils.TemporaryDirectory() as temp_dir: pb = console_io.ProgressBar(label='Generating Help Document Files') with TimeIt('Creating walker'): walker = self._generator( self._cli, temp_dir, pb.SetProgress, restrict=restrict) start = time.time() pb.Start() walker.Walk(hidden=True) pb.Finish() elapsed_time = time.time() - start log.info('Generating Help Document Files took {}'.format(elapsed_time)) diff = HelpAccumulator(restrict=restrict) with TimeIt('Diffing'): DirDiff(self._directory, temp_dir, diff) ops = collections.defaultdict(list) changes = 0 with TimeIt('Getting diffs'): for op, path in sorted(diff.GetChanges()): changes += 1 if not self._test or changes < TEST_CHANGES_DISPLAY_MAX: log.status.Print('{0} {1}'.format(op, path)) ops[op].append(path) if self._test: if changes: if changes >= TEST_CHANGES_DISPLAY_MAX: log.status.Print('...') log.status.Print('{0} help text {1} changed'.format( changes, text.Pluralize(changes, 'file'))) return changes with TimeIt('Updating destination files'): for op in ('add', 'edit', 'delete'): for path in ops[op]: dest_path = os.path.join(self._directory, path) if op in ('add', 'edit'): if op == 'add': subdir = os.path.dirname(dest_path) if subdir: file_utils.MakeDir(subdir) temp_path = os.path.join(temp_dir, path) shutil.copyfile(temp_path, dest_path) elif op == 'delete': try: os.remove(dest_path) except OSError: pass return changes
def UpdateCliTrees(cli=None, commands=None, directory=None, force=False, verbose=False, warn_on_exceptions=False): """(re)generates the CLI trees in directory if non-existent or out ot date. This function uses the progress tracker because some of the updates can take ~minutes. Args: cli: The default CLI. If not None then the default CLI is also updated. commands: Update only the commands in this list. directory: The directory containing the CLI tree JSON files. If None then the default installation directories are used. force: Update all exitsing trees by forcing them to be out of date if True. verbose: Display a status line for up to date CLI trees if True. warn_on_exceptions: Emits warning messages in lieu of exceptions. Used during installation. Raises: NoCliTreeGeneratorForCommand: A command in commands is not supported (doesn't have a generator). """ directories = _GetDirectories(directory=directory, warn_on_exceptions=warn_on_exceptions) if not commands: commands = set([cli_tree.DEFAULT_CLI_NAME] + list(GENERATORS.keys())) failed = [] for command in sorted(commands): if command != cli_tree.DEFAULT_CLI_NAME: tree = LoadOrGenerate(command, directories=directories, force=force, verbose=verbose, warn_on_exceptions=warn_on_exceptions) if not tree: failed.append(command) elif cli: cli_tree.Load(cli=cli, path=cli_tree.CliTreePath(directory=directories[0]), force=force, verbose=verbose) if failed: message = 'No CLI tree {} for [{}].'.format( text_utils.Pluralize(len(failed), 'generator'), ', '.join(sorted(failed))) if not warn_on_exceptions: raise NoCliTreeGeneratorForCommand(message) log.warning(message)
def Display(self, args, result): """This method is called to print the result of the Run() method. Args: args: The arguments that command was run with. result: The value returned from the Run() method. """ succeeded, failed = result successes = len(succeeded) failures = len(failed) if successes: fmt = 'list[title="{0} {1} deleted successfully"]'.format( successes, text.Pluralize(successes, 'subscription')) resource_printer.Print( [subscription for subscription in succeeded], fmt) if failures: fmt = 'list[title="{0} {1} failed"]'.format( failures, text.Pluralize(failures, 'subscription')) resource_printer.Print([ '{0} (reason: {1})'.format(subs, reason) for subs, reason in failed ], fmt)
def _PromptWithScopeChoices(resource_name, underspecified_names, scope_value_choices, choice_names, choice_mapping): """Queries user to choose scope and its value.""" title = ('For the following {0}:\n {1}\n'.format( text.Pluralize(len(underspecified_names), resource_name), '\n '.join('- [{0}]'.format(n) for n in sorted(underspecified_names)))) flags = ' or '.join( sorted([s.prefix + s.flag_name for s in scope_value_choices.keys()])) idx = console_io.PromptChoice(options=choice_names, message='{0}choose {1}:'.format( title, flags)) if idx is None: return None, None else: return choice_mapping[idx]
def FromServiceLists(cls, requested_services, all_services): """Format a ServiceNotFoundError. Args: requested_services: list of str, IDs of services that were not found. all_services: list of str, IDs of all available services Returns: ServicesNotFoundError, error with properly formatted message """ return cls('The following {0} not found: [{1}]\n\n' 'All services: [{2}]'.format( text.Pluralize(len(requested_services), 'service was', plural='services were'), ', '.join(requested_services), ', '.join(all_services)))
def _GetHelpTextForAttribute(self, attribute): """Helper to get the help text for the attribute arg.""" if self._IsAnchor(attribute): help_text = ANCHOR_HELP if not self.plural else PLURAL_ANCHOR_HELP else: help_text = attribute.help_text expansion_name = text.Pluralize(2 if self.plural else 1, self.resource_spec.name, plural=getattr(self.resource_spec, 'plural_name', None)) hints = self.GetHints(attribute.name) if hints: hint = ' To set the [{}] attribute: {}.'.format( attribute.name, '; '.join(hints)) help_text += hint return help_text.format(resource=expansion_name)
def GetNotesHelpSection(self, contents=None): """Returns the NOTES section with explicit and generated help.""" if not contents: contents = self.detailed_help.get('NOTES') notes = _Notes(contents) if self.IsHidden(): notes.AddLine('This command is an internal implementation detail and may ' 'change or disappear without notice.') notes.AddLine(self.ReleaseTrack().help_note) alternates = self.GetExistingAlternativeReleaseTracks() if alternates: notes.AddLine('{} also available:'.format( text.Pluralize( len(alternates), 'This variant is', 'These variants are'))) notes.AddLine('') for alternate in alternates: notes.AddLine(' $ ' + alternate) return notes.GetContents()
def UploadFiles(files_to_upload, num_threads=DEFAULT_NUM_THREADS, show_progress_bar=False): """Upload the given files to the given Cloud Storage URLs. Uses the appropriate parallelism (multi-process, multi-thread, both, or synchronous). Args: files_to_upload: list of FileUploadTask num_threads: int (optional), the number of threads to use. show_progress_bar: bool. If true, show a progress bar to the users when uploading files. """ num_files = len(files_to_upload) label = 'Uploading {} {} to Google Cloud Storage'.format( num_files, text.Pluralize(num_files, 'file')) _DoParallelOperation(num_threads, files_to_upload, _UploadFile, label, show_progress_bar)
def DeleteObjects(objects_to_delete, num_threads=DEFAULT_NUM_THREADS, show_progress_bar=False): """Delete the given Cloud Storage objects. Uses the appropriate parallelism (multi-process, multi-thread, both, or synchronous). Args: objects_to_delete: list of ObjectDeleteTask num_threads: int (optional), the number of threads to use. show_progress_bar: bool. If true, show a progress bar to the users when deleting files. """ num_objects = len(objects_to_delete) label = 'Deleting {} {} from Google Cloud Storage'.format( num_objects, text.Pluralize(num_objects, 'object')) _DoParallelOperation(num_threads, objects_to_delete, _DeleteObject, label, show_progress_bar)
def DeleteServices(api_client, services): """Delete the given services.""" errors = {} for service in services: try: operations_util.CallAndCollectOpErrors(api_client.DeleteService, service.id) except operations_util.MiscOperationError as err: errors[service.id] = str(err) if errors: printable_errors = {} for service_id, error_msg in errors.items(): printable_errors[service_id] = '[{0}]: {1}'.format( service_id, error_msg) raise ServicesDeleteError('Issue deleting {0}: [{1}]\n\n'.format( text.Pluralize(len(printable_errors), 'service'), ', '.join( list(printable_errors.keys()))) + '\n\n'.join(list(printable_errors.values())))
def _DisplayExecutionStats(self, out, prepend, beneath_stub): """Prints the relevant execution statistics for a node. More specifically, print out latency information and the number of executions. This information only exists when query is run in 'PROFILE' mode. Args: out: Output stream to which we print. prepend: String that precedes any information about this node to maintain a visible hierarchy. beneath_stub: String that preserves the indentation of the vertical lines. """ if not self.properties.executionStats: return None stat_props = [] num_executions = self._GetNestedStatProperty('execution_summary', 'num_executions') if num_executions: num_executions = int(num_executions) executions_str = '{} {}'.format( num_executions, text.Pluralize(num_executions, 'execution')) stat_props.append(executions_str) # Total latency and latency unit are always expected to be present when # latency exists. Latency exists when the query is run in PROFILE mode. mean_latency = self._GetNestedStatProperty('latency', 'mean') total_latency = self._GetNestedStatProperty('latency', 'total') unit = self._GetNestedStatProperty('latency', 'unit') if mean_latency: stat_props.append('{} {} average latency'.format( mean_latency, unit)) elif total_latency: stat_props.append('{} {} total latency'.format( total_latency, unit)) if stat_props: executions_stats_str = '{}{} ({})'.format(prepend, beneath_stub, ', '.join(stat_props)) out.Print(executions_stats_str)
def DeleteVersions(api_client, versions): """Delete the given version of the given services.""" errors = {} for version in versions: version_path = '{0}/{1}'.format(version.service, version.id) try: operations_util.CallAndCollectOpErrors(api_client.DeleteVersion, version.service, version.id) except operations_util.MiscOperationError as err: errors[version_path] = str(err) if errors: printable_errors = {} for version_path, error_msg in errors.items(): printable_errors[version_path] = '[{0}]: {1}'.format( version_path, error_msg) raise VersionsDeleteError('Issue deleting {0}: [{1}]\n\n'.format( text.Pluralize(len(printable_errors), 'version'), ', '.join( list(printable_errors.keys()))) + '\n\n'.join(list(printable_errors.values())))
def DeleteServices(api_client, services): """Delete the given services.""" errors = {} for service in services: try: api_client.DeleteService(service.id) except (core_api_exceptions.HttpException, operations_util.OperationError, operations_util.OperationTimeoutError, app_exceptions.Error) as err: errors[service.id] = str(err) if errors: printable_errors = {} for service_id, error_msg in errors.items(): printable_errors[service_id] = '[{0}]: {1}'.format(service_id, error_msg) raise ServicesDeleteError( 'Issue deleting {0}: [{1}]\n\n'.format( text.Pluralize(len(printable_errors), 'service'), ', '.join(printable_errors.keys())) + '\n\n'.join(printable_errors.values()))
def ClusterUpgradeMessage(cluster, master=False, node_pool=None, new_version=None): """Get a message to print during gcloud container clusters upgrade. Args: cluster: the cluster object. master: bool, if the upgrade applies to the master version. node_pool: str, the name of the node pool if the upgrade is for a specific node pool. new_version: str, the name of the new version, if given. Raises: NodePoolError: if the node pool name can't be found in the cluster. Returns: str, a message about which nodes in the cluster will be upgraded and to which version. """ if new_version: new_version_message = 'version [{}]'.format(new_version) else: new_version_message = 'master version' if master: node_message = 'Master' current_version = cluster.currentMasterVersion elif node_pool: node_message = 'All nodes in node pool [{}]'.format(node_pool) node_pool = _NodePoolFromCluster(cluster, node_pool) current_version = node_pool.version else: node_message = 'All nodes ({} {})'.format( cluster.currentNodeCount, text.Pluralize(cluster.currentNodeCount, 'node')) current_version = cluster.currentNodeVersion return ('{} of cluster [{}] will be upgraded from version [{}] to {}. ' 'This operation is long-running and will block other operations ' 'on the cluster (including delete) until it has run to completion.' .format(node_message, cluster.name, current_version, new_version_message))
def DeleteVersions(api_client, versions): """Delete the given version of the given services.""" errors = {} for version in versions: version_path = '{0}/{1}'.format(version.service, version.id) try: api_client.DeleteVersion(version.service, version.id) except (core_api_exceptions.HttpException, operations_util.OperationError, operations_util.OperationTimeoutError, app_exceptions.Error) as err: errors[version_path] = str(err) if errors: printable_errors = {} for version_path, error_msg in errors.items(): printable_errors[version_path] = '[{0}]: {1}'.format( version_path, error_msg) raise VersionsDeleteError('Issue deleting {0}: [{1}]\n\n'.format( text.Pluralize(len(printable_errors), 'version'), ', '.join( printable_errors.keys())) + '\n\n'.join(printable_errors.values()))
def ReadConfig(self, path, allowed_fields): """Read a config file and return Version object with the values. The object is based on a YAML configuration file. The file may only have the fields given in `allowed_fields`. Args: path: str, the path to the YAML file. allowed_fields: Collection, the fields allowed in the YAML. Returns: A Version object (for the corresponding API version). Raises: InvalidVersionConfigFile: If the file contains unexpected fields. """ try: data = yaml.load_path(path) except (yaml.Error) as err: raise InvalidVersionConfigFile( 'Could not read Version configuration file [{path}]:\n\n' '{err}'.format(path=path, err=six.text_type(err.inner_error))) if data: version = encoding.DictToMessage(data, self.version_class) specified_fields = set( [f.name for f in version.all_fields() if getattr(version, f.name)]) invalid_fields = (specified_fields - allowed_fields | set(version.all_unrecognized_fields())) if invalid_fields: raise InvalidVersionConfigFile( 'Invalid {noun} [{fields}] in configuration file [{path}]. ' 'Allowed fields: [{allowed}].'.format( noun=text.Pluralize(len(invalid_fields), 'field'), fields=', '.join(sorted(invalid_fields)), path=path, allowed=', '.join(sorted(allowed_fields)))) return version
def AddInstance(self, positional=True, required=True, multiple=False, additional_help=None): """Add argument for instance ID to parser.""" help_text = 'ID of the {}.'.format( text.Pluralize(2 if multiple else 1, 'instance')) if additional_help: help_text = ' '.join([help_text, additional_help]) name = 'instance' if positional else '--instance' args = {'completer': InstanceCompleter, 'help': help_text} if multiple: if positional: args['nargs'] = '+' else: name = '--instances' args['type'] = arg_parsers.ArgList() args['metavar'] = 'INSTANCE' if not positional: args['required'] = required self.parser.add_argument(name, **args) return self