Ejemplo n.º 1
0
    def Run(self, args):
        """Run the authentication command."""

        if c_devshell.IsDevshellEnvironment():
            message = """
          You are already authenticated with gcloud when running inside
          the Developer Shell and so do not need to run this command.

          Do you wish to proceed anyway?
        """
            answer = console_io.PromptContinue(message=message)
            if not answer:
                return None

        if c_gce.Metadata().connected:
            message = textwrap.dedent("""
          You are running on a GCE VM. It is recommended that you use
          service accounts for authentication.

          You can run:

            $ gcloud config set account ``ACCOUNT''

          to switch accounts if necessary.

          Your credentials may be visible to others with access to this
          virtual machine. Are you sure you want to authenticate with
          your personal account?
          """)
            answer = console_io.PromptContinue(message=message)
            if not answer:
                return None

        account = args.account

        if account and not args.force:
            creds = c_store.LoadIfValid(account=account)
            if creds:
                # Account already has valid creds, just switch to it.
                return self.LoginAs(account, creds, args.project,
                                    args.do_not_activate)

        # No valid creds, do the web flow.
        creds = self.DoWebFlow(args.launch_browser)
        web_flow_account = creds.id_token['email']
        if account and account.lower() != web_flow_account.lower():
            raise c_exc.ToolException(
                'You attempted to log in as account [{account}] but the received '
                'credentials were for account [{web_flow_account}].\n\n'
                'Please check that your browser is logged in as account [{account}] '
                'and that you are using the correct browser profile.'.format(
                    account=account, web_flow_account=web_flow_account))

        account = web_flow_account
        # We got new creds, and they are for the correct user.
        c_store.Store(creds, account)
        return self.LoginAs(account, creds, args.project, args.do_not_activate)
Ejemplo n.º 2
0
    def Run(self, args):
        """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace, All the arguments that were provided to this
        command invocation.

    Raises:
      HttpException: An http error response was received while executing api
          request.
    Returns:
      None
    """
        prompt_message = (
            'Deleting dataset {0} will delete all objects in the dataset. '
            'Deleted datasets can be recovered with the "restore" command '
            'up to one week after the deletion occurs.').format(args.id)

        if not console_io.PromptContinue(message=prompt_message):
            raise GenomicsError('Deletion aborted by user.')

        apitools_client = self.context[commands.GENOMICS_APITOOLS_CLIENT_KEY]
        genomics_messages = self.context[commands.GENOMICS_MESSAGES_MODULE_KEY]

        dataset = genomics_messages.GenomicsDatasetsDeleteRequest(
            datasetId=str(args.id), )

        apitools_client.datasets.Delete(dataset)
        log.Print('Deleted dataset {0}'.format(args.id))
Ejemplo n.º 3
0
  def GetPublicKey(self):
    """Generates an SSH key using ssh-key (if necessary) and returns it."""
    public_ssh_key_file = self.ssh_key_file + '.pub'
    if (not os.path.exists(self.ssh_key_file) or
        not os.path.exists(public_ssh_key_file)):
      log.warn('You do not have an SSH key for Google Compute Engine.')
      log.warn('[%s] will be executed to generate a key.',
               self.ssh_keygen_executable)

      ssh_directory = os.path.dirname(public_ssh_key_file)
      if not os.path.exists(ssh_directory):
        if console_io.PromptContinue(
            'This tool needs to create the directory [{0}] before being able '
            'to generate SSH keys.'.format(ssh_directory)):
          files.MakeDir(ssh_directory, 0700)
        else:
          raise exceptions.ToolException('SSH key generation aborted by user.')

      keygen_args = [
          self.ssh_keygen_executable,
          '-t', 'rsa',
          '-f', self.ssh_key_file,
      ]
      _RunExecutable(keygen_args)

    with open(public_ssh_key_file) as f:
      return f.readline().strip()
Ejemplo n.º 4
0
    def EnsureSSHKeyExistsForUser(self, user):
        """Ensure the user's public SSH key is known by the Account Service."""
        public_key = self.GetPublicKey()
        should_upload = True
        try:
            user_info = self.LookupUser(user)
        except user_utils.UserException:
            owner_email = gaia_utils.GetAuthenticatedGaiaEmail(self.http)
            if console_io.PromptContinue(
                    'The user [{0}] does not exist. Would you like to create a new '
                    'Compute Accounts user owned by [{1}]?'.format(
                        user, owner_email)):
                self.CreateUser(user, owner_email)
                user_info = self.LookupUser(user)
            else:
                raise exceptions.ToolException(
                    'user creation aborted by user.')
        for remote_public_key in user_info.publicKeys:
            if remote_public_key.key.rstrip() == public_key:
                expiration_time = remote_public_key.expirationTimestamp

                if expiration_time and time_utils.IsExpired(expiration_time):
                    # If a key is expired we remove and reupload
                    self.RemovePublicKey(user_info.name,
                                         remote_public_key.fingerprint)
                else:
                    should_upload = False
                break

        if should_upload:
            self.UploadPublicKey(user, public_key)
        return True
Ejemplo n.º 5
0
  def Run(self, args):
    """Run 'dns managed-zone delete'.

    Args:
      args: argparse.Namespace, The arguments that this command was invoked
          with.

    Returns:
      A dict object representing the changes resource obtained by the delete
      operation if the delete was successful.
    """
    project = properties.VALUES.core.project.Get(required=True)
    really = console_io.PromptContinue(
        'Deleting %s in %s' % (args.zone, project))
    if not really:
      return

    dns = self.context['dns']
    if args.delete_zone_contents:
      self.DeleteZoneContents_(dns, project, args.zone)

    request = dns.managedZones().delete(project=project, managedZone=args.zone)
    try:
      result = request.execute()
      return result
    except errors.HttpError as error:
      raise exceptions.HttpException(util.GetError(error, verbose=True))
    except errors.Error as error:
      raise exceptions.ToolException(error)
Ejemplo n.º 6
0
    def Run(self, args):
        """Run 'dns managed-zone create'.

    Args:
      args: argparse.Namespace, The arguments that this command was invoked
          with.
    Returns:
      A dict object representing the changes resource obtained by the create
      operation if the create was successful.
    """
        project = properties.VALUES.core.project.Get(required=True)
        zone = {}
        zone['dnsName'] = args.dns_name
        zone['name'] = args.zone
        zone['description'] = args.description

        really = console_io.PromptContinue('Creating %s in %s' %
                                           (zone, project))
        if not really:
            return

        dns = self.context['dns']
        request = dns.managedZones().create(project=project, body=zone)
        try:
            result = request.execute()
            return result
        except errors.HttpError as error:
            raise exceptions.HttpException(util.GetError(error))
        except errors.Error as error:
            raise exceptions.ToolException(error)
Ejemplo n.º 7
0
def Prompts(usage_reporting):
    """Display prompts to opt out of usage reporting.

  Args:
    usage_reporting: bool, If True, enable usage reporting. If None, ask.
  """

    if config.InstallationConfig.Load().IsAlternateReleaseChannel():
        usage_reporting = True
        print("""
Usage reporting is always on for alternate release channels.
""")
        return

    if usage_reporting is None:
        print("""
The Google Cloud SDK is currently in developer preview. To help improve the
quality of this product, we collect anonymized data on how the SDK is used.
You may choose to opt out of this collection now (by choosing 'N' at the below
prompt), or at any time in the future by running the following command:
    gcloud config set --scope=user disable_usage_reporting true
""")

        usage_reporting = console_io.PromptContinue(
            prompt_string='Do you want to help improve the Google Cloud SDK')
    properties.PersistProperty(properties.VALUES.core.disable_usage_reporting,
                               not usage_reporting,
                               scope=properties.Scope.INSTALLATION)
Ejemplo n.º 8
0
    def Run(self, args):
        """Deletes a Cloud SQL instance.

    Args:
      args: argparse.Namespace, The arguments that this command was invoked
          with.

    Returns:
      A dict object representing the operations resource describing the delete
      operation if the delete was successful.
    Raises:
      HttpException: A http error response was received while executing api
          request.
      ToolException: An error other than http error occured while executing the
          command.
    """
        sql_client = self.context['sql_client']
        sql_messages = self.context['sql_messages']
        resources = self.context['registry']
        operation_ref = None

        util.ValidateInstanceName(args.instance)
        instance_ref = resources.Parse(args.instance,
                                       collection='sql.instances')

        if not console_io.PromptContinue(
                'All of the instance data will be lost when the instance is deleted.'
        ):
            return None
        try:
            result = sql_client.instances.Delete(
                sql_messages.SqlInstancesDeleteRequest(
                    instance=instance_ref.instance,
                    project=instance_ref.project))

            operation_ref = resources.Create(
                'sql.operations',
                operation=result.operation,
                project=instance_ref.project,
                instance=instance_ref.instance,
            )

            unused_operation = sql_client.operations.Get(
                operation_ref.Request())

            log.DeletedResource(instance_ref)
            cache = remote_completion.RemoteCompletion()
            cache.DeleteFromCache(instance_ref.SelfLink())

        except apitools_base.HttpError:
            log.debug('operation : %s', str(operation_ref))
            raise
Ejemplo n.º 9
0
  def Run(self, args):
    log.status.write('Welcome! This command will take you through '
                     'the configuration of gcloud.\n\n')

    creds = c_store.LoadIfValid()
    if not creds:
      log.status.write('To start, you must login.  When the web browser opens, '
                       'login with your google account and hit accept.\n\n')
      answer = console_io.PromptContinue()
      if not answer:
        return
      creds = self.cli.Execute(['auth', 'login'])

    log.status.write('\nYou are now logged in as: [{0}]\n'
                     .format(creds.id_token['email']))
Ejemplo n.º 10
0
    def Run(self, args):
        """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace, All the arguments that were provided to this
        command invocation.

    Raises:
      ToolException: when user cancels dataset removal.

    Returns:
      Some value that we want to have printed later.
    """
        apitools_client = self.context[commands.APITOOLS_CLIENT_KEY]
        bigquery_messages = self.context[commands.BIGQUERY_MESSAGES_MODULE_KEY]
        resource_parser = self.context[commands.BIGQUERY_REGISTRY_KEY]
        resource = resource_parser.Parse(args.dataset_name,
                                         collection='bigquery.datasets')
        reference = message_conversions.DatasetResourceToReference(
            bigquery_messages, resource)

        if not args.quiet:
            dataset_exists = bigquery_client_helper.DatasetExists(
                apitools_client, bigquery_messages, reference)
            if dataset_exists:
                removal_confirmed = console_io.PromptContinue(
                    message='About to remove dataset {0}.'.format(resource))
                if not removal_confirmed:
                    raise exceptions.ToolException('canceled by user')

        request = bigquery_messages.BigqueryDatasetsDeleteRequest(
            projectId=reference.projectId,
            datasetId=reference.datasetId,
            deleteContents=args.remove_tables)
        try:
            apitools_client.datasets.Delete(request)
            log.DeletedResource(resource)
        except apitools_base.HttpError as server_error:
            try:
                raise bigquery.Error.ForHttpError(server_error)
            except bigquery.NotFoundError:
                if args.ignore_not_found:
                    log.status.Print(
                        'Dataset {0} did not exist.'.format(resource))
                else:
                    raise
Ejemplo n.º 11
0
    def PromptIfDisksWithoutAutoDeleteWillBeDeleted(self, args,
                                                    disks_to_warn_for):
        """Prompts if disks with False autoDelete will be deleted."""
        if not disks_to_warn_for:
            return

        prompt_list = []
        disk_refs = self.CreateZonalReferences(disks_to_warn_for,
                                               args.zone,
                                               resource_type='disks')
        for ref in disk_refs:
            prompt_list.append('[{0}] in [{1}]'.format(ref.Name(), ref.zone))
            prompt_message = utils.ConstructList(
                'The following disks are not configured to be automatically deleted '
                'with instance deletion, but they will be deleted as a result of '
                'this operation if they are not attached to any other instances:',
                prompt_list)
        if not console_io.PromptContinue(message=prompt_message):
            raise exceptions.ToolException('Deletion aborted by user.')
Ejemplo n.º 12
0
    def _PromptDidYouMeanScope(self, ambiguous_refs, attribute, resource_type,
                               suggested_resource, raise_on_prompt_failure):
        """Prompts "did you mean <scope>".  Returns str or None, or raises."""

        # targetInstances -> target instances
        resource_name = utils.CamelCaseToOutputFriendly(resource_type)
        names = ['[{0}]'.format(name) for name, _ in ambiguous_refs]
        message = 'Did you mean {0} [{1}] for {2}: [{3}]?'.format(
            attribute, suggested_resource, resource_name, names)

        try:
            if console_io.PromptContinue(message=message,
                                         default=True,
                                         throw_if_unattended=True):
                return suggested_resource
            else:
                return None
        except console_io.UnattendedPromptError:
            raise_on_prompt_failure()
Ejemplo n.º 13
0
  def PrintAndConfirmWarningMessage(self, args):
    """Print and confirm warning indicating the effect of applying the patch."""
    continue_msg = None
    if any([args.tier, args.database_flags, args.clear_database_flags,
            args.enable_database_replication,
            args.no_enable_database_replication]):
      continue_msg = ('WARNING: This patch modifies a value that requires '
                      'your instance to be restarted. Submitting this patch '
                      'will immediately restart your instance if it\'s running.'
                     )
    else:
      if any([args.follow_gae_app, args.gce_zone]):
        continue_msg = ('WARNING: This patch modifies the zone your instance '
                        'is set to run in, which may require it to be moved. '
                        'Submitting this patch will restart your instance '
                        'if it is running in a different zone.')

    if continue_msg and not console_io.PromptContinue(continue_msg):
      raise exceptions.ToolException('canceled by the user.')
Ejemplo n.º 14
0
def PromptForDeletion(refs, scope_name=None, prompt_title=None):
    """Prompts the user to confirm deletion of resources."""
    if not refs:
        return
    resource_type = CollectionToResourceType(refs[0].Collection())
    resource_name = CamelCaseToOutputFriendly(resource_type)
    prompt_list = []
    for ref in refs:
        if scope_name:
            item = '[{0}] in [{1}]'.format(ref.Name(),
                                           getattr(ref, scope_name))
        else:
            item = '[{0}]'.format(ref.Name())
        prompt_list.append(item)

    prompt_title = (prompt_title or
                    'The following {0} will be deleted:'.format(resource_name))
    prompt_message = ConstructList(prompt_title, prompt_list)
    if not console_io.PromptContinue(message=prompt_message):
        raise calliope_exceptions.ToolException('Deletion aborted by user.')
Ejemplo n.º 15
0
  def Restore(self):
    """Restores the latest backup installation of the Cloud SDK.

    Raises:
      NoBackupError: If there is no valid backup to restore.
    """
    self._EnsureNotDisabled()
    install_state = self._GetInstallState()
    if not install_state.HasBackup():
      raise NoBackupError('There is currently no backup to restore.')

    self._ShouldDoFastUpdate(allow_no_backup=False, fast_mode_impossible=True)

    if not console_io.PromptContinue(
        message='Your Cloud SDK installation will be restored to its previous '
        'state.'):
      return

    self.__Write(log.status, 'Restoring backup...')
    install_state.RestoreBackup()

    self.__Write(log.status, 'Restoration done!\n')
Ejemplo n.º 16
0
    def Restore(self):
        """Restores the latest backup installation of the Cloud SDK.

    Raises:
      NoBackupError: If there is no valid backup to restore.
    """
        self._EnsureNotDisabled()
        install_state = self._GetInstallState()
        if not install_state.HasBackup():
            raise NoBackupError('There is currently no backup to restore.')

        self._CheckCWD()

        if not console_io.PromptContinue(
                message=
                'Your Cloud SDK installation will be restored to its previous '
                'state.'):
            return

        self.__Write('Restoring backup...\n')
        install_state.RestoreBackup()

        self.__Write('\nDone!\n')
Ejemplo n.º 17
0
  def Run(self, args):
    """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace, All the arguments that were provided to this
        command invocation.

    Raises:
      ToolException: if user cancels table removal.
    """
    apitools_client = self.context[commands.APITOOLS_CLIENT_KEY]
    bigquery_messages = self.context[commands.BIGQUERY_MESSAGES_MODULE_KEY]
    resource_parser = self.context[commands.BIGQUERY_REGISTRY_KEY]

    table_reference = resource_parser.Parse(
        args.table_or_view, collection='bigquery.tables')
    if not args.quiet:
      if not console_io.PromptContinue(
          message='About to delete table [{0}].'.format(table_reference)):
        raise calliope_exceptions.ToolException('canceled by user')

    request = bigquery_messages.BigqueryTablesDeleteRequest(
        projectId=table_reference.projectId,
        datasetId=table_reference.datasetId,
        tableId=table_reference.tableId)

    try:
      apitools_client.tables.Delete(request)
    except apitools_base.HttpError as server_error:
      try:
        raise bigquery.Error.ForHttpError(server_error)
      except bigquery.NotFoundError:
        if args.ignore_not_found:
          log.status.Print('Table [{0}] did not exist.'.format(table_reference))
        else:
          raise
Ejemplo n.º 18
0
    def Run(self, args):
        """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace. All the arguments that were provided to this
        command invocation.

    Returns:
      Some value that we want to have printed later.
    """
        adapter = self.context['api_adapter']

        cluster_refs = []
        for name in args.names:
            cluster_refs.append(adapter.ParseCluster(name))

        if not console_io.PromptContinue(message=util.ConstructList(
                'The following clusters will be deleted.', [
                    '[{name}] in [{zone}]'.format(name=ref.clusterId,
                                                  zone=adapter.Zone(ref))
                    for ref in cluster_refs
                ]),
                                         throw_if_unattended=True):
            raise exceptions.ToolException('Deletion aborted by user.')

        operations = []
        errors = []
        # Issue all deletes first
        for cluster_ref in cluster_refs:
            try:
                # Make sure it exists (will raise appropriate error if not)
                adapter.GetCluster(cluster_ref)

                op_ref = adapter.DeleteCluster(cluster_ref)
                operations.append((op_ref, cluster_ref))
            except apitools_base.HttpError as error:
                errors.append(util.GetError(error))
            except util.Error as error:
                errors.append(error)
        if args.wait:
            # Poll each operation for completion
            for operation_ref, cluster_ref in operations:
                try:
                    adapter.WaitForOperation(
                        operation_ref,
                        'Deleting cluster {0}'.format(cluster_ref.clusterId))
                    # Purge cached config files
                    util.ClusterConfig.Purge(cluster_ref.clusterId,
                                             adapter.Zone(cluster_ref),
                                             cluster_ref.projectId)
                    if properties.VALUES.container.cluster.Get(
                    ) == cluster_ref.clusterId:
                        properties.PersistProperty(
                            properties.VALUES.container.cluster, None)
                    log.DeletedResource(cluster_ref)
                except apitools_base.HttpError as error:
                    errors.append(util.GetError(error))
                except util.Error as error:
                    errors.append(error)

        if errors:
            raise exceptions.ToolException(
                util.ConstructList('Some requests did not succeed:', errors))
    def Run(self, args):
        start = time_utils.CurrentTimeSec()

        # Set up Encryption utilities.
        openssl_executable = files.FindExecutableOnPath('openssl')
        if openssl_executable:
            crypt = openssl_encryption_utils.OpensslCrypt(openssl_executable)
        elif windows_encryption_utils:
            crypt = windows_encryption_utils.WinCrypt()
        else:
            raise exceptions.ToolException(
                'Your platform does not support OpenSSL.')

        # Get Authenticated email address and default username.
        email = gaia_utils.GetAuthenticatedGaiaEmail(self.http)
        if args.user:
            user = args.user
        else:
            user = gaia_utils.MapGaiaEmailToDefaultAccountName(email)

        # Warn user (This warning doesn't show for non-interactive sessions).
        message = RESET_PASSWORD_WARNING.format(user)
        prompt_string = (
            'Would you like to set or reset the password for [{0}]'.format(
                user))
        console_io.PromptContinue(message=message,
                                  prompt_string=prompt_string,
                                  cancel_on_no=True)

        log.status.Print(
            'Resetting and retrieving password for [{0}] on [{1}]'.format(
                user, args.instance))

        # Get Encryption Keys.
        key = crypt.GetKeyPair()
        modulus, exponent = crypt.GetModulusExponentFromPublicKey(
            crypt.GetPublicKey(key))

        # Create Windows key entry.
        self.windows_key_entry = self._ConstructWindowsKeyEntry(
            user, modulus, exponent, email)

        # Call ReadWriteCommad.Run() which will fetch the instance and update
        # the metadata (using the data in self.windows_key_entry).
        objects = super(ResetWindowsPassword, self).Run(args)
        updated_instance = list(objects)[0]

        # Retrieve and Decrypt the password from the serial console.
        enc_password = self._GetEncryptedPasswordFromSerialPort(modulus)
        password = crypt.DecryptMessage(key, enc_password)

        # Get External IP address.
        try:
            access_configs = updated_instance['networkInterfaces'][0][
                'accessConfigs']
            external_ip_address = access_configs[0]['natIP']
        except KeyError:
            log.warn(NO_IP_WARNING.format(updated_instance['name']))
            external_ip_address = None

        # Check for old Windows credentials.
        if self.old_metadata_keys:
            log.warn(
                OLD_KEYS_WARNING.format(self.ref.Name(), self.ref.Name(),
                                        self.ref.zone,
                                        ' '.join(self.old_metadata_keys)))

        log.info('Total Elapsed Time: {0}'.format(time_utils.CurrentTimeSec() -
                                                  start))

        # Display the connection info.
        connection_info = {
            'username': user,
            'password': password,
            'ip_address': external_ip_address
        }
        resource_printer.Print(connection_info,
                               args.format or 'text',
                               out=None)
Ejemplo n.º 20
0
  def Run(self, args):
    self.ref = self.CreateReference(args)
    get_request = self.GetGetRequest(args)

    errors = []
    objects = list(request_helper.MakeRequests(
        requests=[get_request],
        http=self.http,
        batch_url=self.batch_url,
        errors=errors,
        custom_get_requests=None))
    if errors:
      utils.RaiseToolException(
          errors,
          error_message='Could not fetch resource:')

    self.original_object = objects[0]
    self.original_record = encoding.MessageToDict(self.original_object)

    # Selects only the fields that can be modified.
    field_selector = property_selector.PropertySelector(
        properties=self._resource_spec.editables)
    self.modifiable_record = field_selector.Apply(self.original_record)

    buf = cStringIO.StringIO()
    for line in _HELP.splitlines():
      buf.write('#')
      if line:
        buf.write(' ')
      buf.write(line)
      buf.write('\n')

    buf.write('\n')
    buf.write(_SerializeDict(self.modifiable_record,
                             args.format or BaseEdit.DEFAULT_FORMAT))
    buf.write('\n')

    example = _SerializeDict(
        encoding.MessageToDict(self.example_resource),
        args.format or BaseEdit.DEFAULT_FORMAT)
    _WriteResourceInCommentBlock(example, 'Example resource:', buf)

    buf.write('#\n')

    original = _SerializeDict(self.original_record,
                              args.format or BaseEdit.DEFAULT_FORMAT)
    _WriteResourceInCommentBlock(original, 'Original resource:', buf)

    file_contents = buf.getvalue()
    while True:
      file_contents = edit.OnlineEdit(file_contents)
      try:
        resources = self.ProcessEditedResource(file_contents, args)
        break
      except (ValueError, yaml.error.YAMLError,
              protorpc.messages.ValidationError,
              calliope_exceptions.ToolException) as e:
        if isinstance(e, ValueError):
          message = e.message
        else:
          message = str(e)

        if isinstance(e, calliope_exceptions.ToolException):
          problem_type = 'applying'
        else:
          problem_type = 'parsing'

        message = ('There was a problem {0} your changes: {1}'
                   .format(problem_type, message))
        if not console_io.PromptContinue(
            message=message,
            prompt_string='Would you like to edit the resource again?'):
          raise calliope_exceptions.ToolException('Edit aborted by user.')

    resources = lister.ProcessResults(
        resources=resources,
        field_selector=property_selector.PropertySelector(
            properties=None,
            transformations=self.transformations))
    for resource in resources:
      yield resource
Ejemplo n.º 21
0
    def Run(self, args):
        """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace. All the arguments that were provided to this
        command invocation.

    Returns:
      Some value that we want to have printed later.
    """
        client = self.context['container_client']
        messages = self.context['container_messages']
        resources = self.context['registry']

        properties.VALUES.compute.zone.Get(required=True)
        properties.VALUES.core.project.Get(required=True)
        cluster_refs = []
        for name in args.names:
            cluster_refs.append(
                resources.Parse(
                    name, collection='container.projects.zones.clusters'))

        if not console_io.PromptContinue(message=util.ConstructList(
                'The following clusters will be deleted.', [
                    '[{name}] in [{zone}]'.format(name=ref.clusterId,
                                                  zone=ref.zoneId)
                    for ref in cluster_refs
                ]),
                                         throw_if_unattended=True):
            raise exceptions.ToolException('Deletion aborted by user.')

        operations = []
        errors = []
        # Issue all deletes first
        for ref in cluster_refs:
            try:
                # Make sure it exists (will raise appropriate error if not)
                util.DescribeCluster(ref, self.context)

                op = client.projects_zones_clusters.Delete(
                    messages.ContainerProjectsZonesClustersDeleteRequest(
                        clusterId=ref.clusterId,
                        zoneId=ref.zoneId,
                        projectId=ref.projectId))
                operations.append((op, ref))
            except apitools_base.HttpError as error:
                errors.append(util.GetError(error))
            except util.Error as error:
                errors.append(error)
        if args.wait:
            # Poll each operation for completion
            for operation, ref in operations:
                try:
                    util.WaitForOperation(
                        operation, ref.projectId, self.context,
                        'Deleting cluster {0}'.format(ref.clusterId))
                    # Purge cached config files
                    util.ClusterConfig.Purge(ref.clusterId, ref.zoneId,
                                             ref.projectId)
                    if properties.VALUES.container.cluster.Get(
                    ) == ref.clusterId:
                        properties.PersistProperty(
                            properties.VALUES.container.cluster, None)

                    log.DeletedResource(ref)
                except apitools_base.HttpError as error:
                    errors.append(util.GetError(error))
                except util.Error as error:
                    errors.append(error)

        if errors:
            raise exceptions.ToolException(
                util.ConstructList('Some requests did not succeed:', errors))
Ejemplo n.º 22
0
    def _DoFreshInstall(self, message, no_update, download_url):
        """Do a reinstall of what we have based on a fresh download of the SDK.

    Args:
      message: str, A message to show to the user before the re-installation.
      no_update: bool, True to show the message and tell the user they must
        re-download manually.
      download_url: The URL the Cloud SDK can be downloaded from.

    Returns:
      bool, True if the update succeeded, False if it was cancelled.
    """
        self._EnsureNotDisabled()
        if os.environ.get('CLOUDSDK_REINSTALL_COMPONENTS'):
            # We are already reinstalling but got here somehow.  Something is very
            # wrong and we want to avoid the infinite loop.
            self._RaiseReinstallationFailedError()

        # Print out an arbitrary message that we wanted to show users for this
        # update.
        if message:
            self.__Write(log.status, msg=message, word_wrap=True)

        # We can decide that for some reason we just never want to update past this
        # version of the schema.
        if no_update:
            return False

        answer = console_io.PromptContinue(
            message=
            '\nThe component manager must perform a self update before you '
            'can continue.  It and all components will be updated to their '
            'latest versions.')
        if not answer:
            return False

        self._ShouldDoFastUpdate(allow_no_backup=False,
                                 fast_mode_impossible=True)
        install_state = self._GetInstallState()

        try:
            with console_io.ProgressBar(
                    label='Downloading and extracting updated components',
                    stream=log.status) as pb:
                staging_state = install_state.CreateStagingFromDownload(
                    download_url, progress_callback=pb.SetProgress)
        except local_state.Error:
            log.error('An updated Cloud SDK failed to download')
            log.debug('Handling re-installation error', exc_info=True)
            self._RaiseReinstallationFailedError()

        # shell out to install script
        installed_component_ids = sorted(
            install_state.InstalledComponents().keys())
        env = dict(os.environ)
        env['CLOUDSDK_REINSTALL_COMPONENTS'] = ','.join(
            installed_component_ids)
        installer_path = os.path.join(staging_state.sdk_root, 'bin',
                                      'bootstrapping', 'install.py')
        p = subprocess.Popen([sys.executable, '-S', installer_path], env=env)
        ret_val = p.wait()
        if ret_val:
            self._RaiseReinstallationFailedError()

        self.__Write(log.status,
                     'Creating backup and activating new installation...')
        install_state.ReplaceWith(staging_state)

        self.__Write(log.status, '\nComponents updated!\n')
        return True
Ejemplo n.º 23
0
    def _DoFreshInstall(self, e):
        """Do a reinstall of what we have based on a fresh download of the SDK.

    Args:
      e: snapshots.IncompatibleSchemaVersionError, The exception we got with
        information about the new schema version.
    """
        self._EnsureNotDisabled()
        if os.environ.get('CLOUDSDK_REINSTALL_COMPONENTS'):
            # We are already reinstalling but got here somehow.  Something is very
            # wrong and we want to avoid the infinite loop.
            self._RaiseReinstallationFailedError()

        # Print out an arbitrary message that we wanted to show users for this
        # udpate.
        message = e.schema_version.message
        if message:
            self.__Write(msg=message, word_wrap=True)

        # We can decide that for some reason we just never want to update past this
        # version of the schema.
        if e.schema_version.no_update:
            return

        answer = console_io.PromptContinue(
            message=
            '\nThe component manager must perform a self update before you '
            'can continue.  It and all components will be updated to their '
            'latest versions.')
        if not answer:
            return

        self._CheckCWD()
        install_state = self._GetInstallState()
        self.__Write('Downloading and extracting updated components...\n')
        download_url = e.schema_version.url
        try:
            staging_state = install_state.CreateStagingFromDownload(
                download_url)
        except local_state.Error:
            log.error('An updated Cloud SDK failed to download')
            log.debug('Handling re-installation error', exc_info=True)
            self._RaiseReinstallationFailedError()

        # shell out to install script
        installed_component_ids = sorted(
            install_state.InstalledComponents().keys())
        env = dict(os.environ)
        env['CLOUDSDK_REINSTALL_COMPONENTS'] = ','.join(
            installed_component_ids)
        installer_path = os.path.join(staging_state.sdk_root, 'bin',
                                      'bootstrapping', 'install.py')
        p = subprocess.Popen([sys.executable, '-S', installer_path], env=env)
        ret_val = p.wait()
        if ret_val:
            self._RaiseReinstallationFailedError()

        self.__Write('Creating backup and activating new installation...')
        install_state.ReplaceWith(staging_state)

        self.__Write('\nDone!\n')
Ejemplo n.º 24
0
    def Remove(self, ids, allow_no_backup=False):
        """Uninstalls the given components.

    Args:
      ids: list of str, The component ids to uninstall.
      allow_no_backup: bool, True if we want to allow the updater to run
        without creating a backup.  This lets us be in the root directory of the
        SDK and still do an update.  It is more fragile if there is a failure,
        so we only do it if necessary.

    Raises:
      InvalidComponentError: If any of the given component ids are not
        installed or cannot be removed.
    """
        self._EnsureNotDisabled()
        if not ids:
            return

        install_state = self._GetInstallState()
        snapshot = install_state.Snapshot()
        id_set = set(ids)
        not_installed = id_set - set(snapshot.components.keys())
        if not_installed:
            raise InvalidComponentError(
                'The following components are not currently installed [{components}]'
                .format(components=', '.join(not_installed)))

        required_components = set(
            c_id for c_id, component in snapshot.components.iteritems()
            if c_id in id_set and component.is_required)
        if required_components:
            raise InvalidComponentError(
                ('The following components are required and cannot be removed '
                 '[{components}]').format(
                     components=', '.join(required_components)))

        to_remove = snapshot.ConsumerClosureForComponents(ids)
        if not to_remove:
            self.__Write(log.status, 'No components to remove.\n')
            return

        disable_backup = self._ShouldDoFastUpdate(
            allow_no_backup=allow_no_backup)
        components_to_remove = sorted(snapshot.ComponentsFromIds(to_remove),
                                      key=lambda c: c.details.display_name)
        self._PrintPendingAction(components_to_remove, 'removed')
        self.__Write(log.status)

        message = self._GetDontCancelMessage(disable_backup)
        if not console_io.PromptContinue(message):
            return

        if disable_backup:
            with execution_utils.UninterruptibleSection():
                self.__Write(log.status, 'Performing in place update...\n')
                self._UpdateWithProgressBar(components_to_remove,
                                            'Uninstalling',
                                            install_state.Uninstall)
        else:
            with console_io.ProgressBar(label='Creating update staging area',
                                        stream=log.status) as pb:
                staging_state = install_state.CloneToStaging(pb.SetProgress)
            self.__Write(log.status)
            self._UpdateWithProgressBar(components_to_remove, 'Uninstalling',
                                        staging_state.Uninstall)
            self.__Write(log.status)
            self.__Write(log.status,
                         'Creating backup and activating new installation...')
            install_state.ReplaceWith(staging_state)

        self.__Write(log.status, '\nUninstall done!\n')
Ejemplo n.º 25
0
    def Update(self,
               update_seed=None,
               allow_no_backup=False,
               throw_if_unattended=False):
        """Performs an update of the given components.

    If no components are provided, it will attempt to update everything you have
    installed.

    Args:
      update_seed: list of str, A list of component ids to update.
      allow_no_backup: bool, True if we want to allow the updater to run
        without creating a backup.  This lets us be in the root directory of the
        SDK and still do an update.  It is more fragile if there is a failure,
        so we only do it if necessary.
      throw_if_unattended: bool, True to throw an exception on prompts when
        not running in interactive mode.

    Returns:
      bool, True if the update succeeded (or there was nothing to do, False if
      if was cancelled by the user.

    Raises:
      InvalidComponentError: If any of the given component ids do not exist.
    """
        self._EnsureNotDisabled()
        try:
            install_state, diff = self._GetStateAndDiff()
        except snapshots.IncompatibleSchemaVersionError as e:
            return self._ReinstallOnError(e)

        if update_seed:
            invalid_seeds = diff.InvalidUpdateSeeds(update_seed)
            if invalid_seeds:
                if os.environ.get('CLOUDSDK_REINSTALL_COMPONENTS'):
                    # We are doing a reinstall.  Ignore any components that no longer
                    # exist.
                    update_seed = set(update_seed) - invalid_seeds
                else:
                    raise InvalidComponentError(
                        'The following components are unknown [{invalid_seeds}]'
                        .format(invalid_seeds=', '.join(invalid_seeds)))
        else:
            update_seed = diff.current.components.keys()

        to_remove = diff.ToRemove(update_seed)
        to_install = diff.ToInstall(update_seed)

        self.__Write(log.status)
        if not to_remove and not to_install:
            self.__Write(log.status, 'All components are up to date.')
            with install_state.LastUpdateCheck() as update_check:
                update_check.SetFromSnapshot(diff.latest, force=True)
            return True

        disable_backup = self._ShouldDoFastUpdate(
            allow_no_backup=allow_no_backup)
        self._PrintPendingAction(
            diff.DetailsForCurrent(to_remove - to_install), 'removed')
        self._PrintPendingAction(diff.DetailsForLatest(to_remove & to_install),
                                 'updated')
        self._PrintPendingAction(diff.DetailsForLatest(to_install - to_remove),
                                 'installed')
        self.__Write(log.status)

        message = self._GetDontCancelMessage(disable_backup)
        if not console_io.PromptContinue(
                message=message, throw_if_unattended=throw_if_unattended):
            return False

        components_to_install = diff.DetailsForLatest(to_install)
        components_to_remove = diff.DetailsForCurrent(to_remove)

        for c in components_to_install:
            metrics.Installs(c.id, c.version.version_string)

        if disable_backup:
            with execution_utils.UninterruptibleSection():
                self.__Write(log.status, 'Performing in place update...\n')
                self._UpdateWithProgressBar(components_to_remove,
                                            'Uninstalling',
                                            install_state.Uninstall)
                self._UpdateWithProgressBar(
                    components_to_install, 'Installing',
                    self._InstallFunction(install_state, diff))
        else:
            with console_io.ProgressBar(label='Creating update staging area',
                                        stream=log.status) as pb:
                staging_state = install_state.CloneToStaging(pb.SetProgress)
            self.__Write(log.status)
            self._UpdateWithProgressBar(components_to_remove, 'Uninstalling',
                                        staging_state.Uninstall)
            self._UpdateWithProgressBar(
                components_to_install, 'Installing',
                self._InstallFunction(staging_state, diff))
            self.__Write(log.status)
            self.__Write(log.status,
                         'Creating backup and activating new installation...')
            install_state.ReplaceWith(staging_state)

        with install_state.LastUpdateCheck() as update_check:
            update_check.SetFromSnapshot(diff.latest, force=True)
        self.__Write(log.status, '\nUpdate done!\n')

        if self.__warn:
            bad_commands = self.FindAllOldToolsOnPath()
            if bad_commands and not os.environ.get(
                    'CLOUDSDK_REINSTALL_COMPONENTS'):
                log.warning("""\
There are older versions of Google Cloud Platform tools on your system PATH.
Please remove the following to avoid accidentally invoking these old tools:

{0}

""".format('\n'.join(bad_commands)))
        return True
Ejemplo n.º 26
0
def UpdateRC(bash_completion, path_update, rc_path, bin_path):
    """Update the system path to include bin_path.

  Args:
    bash_completion: bool, Whether or not to do bash completion. If None, ask.
    path_update: bool, Whether or not to do bash completion. If None, ask.
    rc_path: str, The path to the rc file to update. If None, ask.
    bin_path: str, The absolute path to the directory that will contain
        Cloud SDK binaries.
  """

    host_os = platforms.OperatingSystem.Current()
    if host_os == platforms.OperatingSystem.WINDOWS:
        if path_update is None:
            path_update = console_io.PromptContinue(
                prompt_string='Update %PATH% to include Cloud SDK binaries?')
        if path_update:
            UpdatePathForWindows(bin_path)
        return

    if not rc_path:

        # figure out what file to edit
        if host_os == platforms.OperatingSystem.LINUX:
            if c_gce.Metadata().connected:
                file_name = '.bash_profile'
            else:
                file_name = '.bashrc'
        elif host_os == platforms.OperatingSystem.MACOSX:
            file_name = '.bash_profile'
        elif host_os == platforms.OperatingSystem.CYGWIN:
            file_name = '.bashrc'
        elif host_os == platforms.OperatingSystem.MSYS:
            file_name = '.profile'
        else:
            file_name = '.bashrc'
        rc_path = os.path.expanduser(os.path.join('~', file_name))

        rc_path_update = console_io.PromptResponse((
            'The Google Cloud SDK installer will now prompt you to update an rc '
            'file to bring the Google Cloud CLIs into your environment.\n\n'
            'Enter path to an rc file to update, or leave blank to use '
            '[{rc_path}]:  ').format(rc_path=rc_path))
        if rc_path_update:
            rc_path = os.path.expanduser(rc_path_update)

    if os.path.exists(rc_path):
        with open(rc_path) as rc_file:
            rc_data = rc_file.read()
            cached_rc_data = rc_data
    else:
        rc_data = ''
        cached_rc_data = ''

    updated_rc = False

    if path_update is None:
        path_update = console_io.PromptContinue(
            prompt_string=('\nModify profile to update your $PATH?'))

    path_rc_path = os.path.join(bootstrapping.SDK_ROOT, 'path.bash.inc')
    if path_update:
        path_comment = r'# The next line updates PATH for the Google Cloud SDK.'
        path_subre = re.compile(r'\n*' + path_comment + r'\n.*$', re.MULTILINE)

        path_line = "{comment}\nsource '{path_rc_path}'\n".format(
            comment=path_comment, path_rc_path=path_rc_path)
        filtered_data = path_subre.sub('', rc_data)
        rc_data = '{filtered_data}\n{path_line}'.format(
            filtered_data=filtered_data, path_line=path_line)
        updated_rc = True
    else:
        print("""\
Source [{path_rc_path}]
in your profile to add the Google Cloud SDK command line tools to your $PATH.
""".format(path_rc_path=path_rc_path))

    if bash_completion is None:
        bash_completion = console_io.PromptContinue(
            prompt_string=('\nModify profile to enable bash completion?'))

    completion_rc_path = os.path.join(bootstrapping.SDK_ROOT,
                                      'completion.bash.inc')
    if bash_completion:
        complete_comment = r'# The next line enables bash completion for gcloud.'
        complete_subre = re.compile(r'\n*' + complete_comment + r'\n.*$',
                                    re.MULTILINE)

        complete_line = "{comment}\nsource '{rc_path}'\n".format(
            comment=complete_comment, rc_path=completion_rc_path)
        filtered_data = complete_subre.sub('', rc_data)
        rc_data = '{filtered_data}\n{complete_line}'.format(
            filtered_data=filtered_data, complete_line=complete_line)
        updated_rc = True
    else:
        print("""\
Source [{completion_rc_path}]
in your profile to enable bash completion for gcloud.
""".format(completion_rc_path=completion_rc_path))

    if not updated_rc:
        return

    if cached_rc_data == rc_data:
        print('No changes necessary for [{rc}].'.format(rc=rc_path))
        return

    if os.path.exists(rc_path):
        rc_backup = rc_path + '.backup'
        print('Backing up [{rc}] to [{backup}].'.format(rc=rc_path,
                                                        backup=rc_backup))
        shutil.copyfile(rc_path, rc_backup)

    with open(rc_path, 'w') as rc_file:
        rc_file.write(rc_data)

    print("""\
[{rc_path}] has been updated.
Start a new shell for the changes to take effect.
""".format(rc_path=rc_path))
Ejemplo n.º 27
0
    def Run(self, args):
        """Run 'deployments delete'.

    Args:
      args: argparse.Namespace, The arguments that this command was invoked
          with.

    Returns:
      If --async=true, returns Operation to poll.
      Else, returns boolean indicating whether insert operation succeeded.

    Raises:
      HttpException: An http error response was received while executing api
          request.
      ToolException: The deployment deletion operation encountered an error.
    """
        client = self.context['deploymentmanager-v2beta2']
        messages = self.context['deploymentmanager-v2beta2-messages']
        project = properties.VALUES.core.project.Get(required=True)

        prompt_message = ('The following deployments will be deleted:\n- ' +
                          '\n- '.join(args.deployment_name))
        if not console_io.PromptContinue(message=prompt_message):
            raise exceptions.ToolException('Deletion aborted by user.')

        operations = []
        for deployment_name in args.deployment_name:
            try:
                operation = client.deployments.Delete(
                    messages.DeploymentmanagerDeploymentsDeleteRequest(
                        project=project,
                        deployment=deployment_name,
                    ))
            except apitools_base.HttpError as error:
                raise exceptions.HttpException(dm_v2_util.GetError(error))
            if args. async:
                operations.append(operation)
            else:
                op_name = operation.name
                try:
                    dm_v2_util.WaitForOperation(op_name, project, self.context,
                                                'delete', OPERATION_TIMEOUT)
                    log.status.Print('Delete operation ' + op_name +
                                     ' completed successfully.')
                except (exceptions.ToolException, DeploymentManagerError):
                    log.error('Delete operation ' + op_name +
                              ' has errors or failed to complete within in ' +
                              str(OPERATION_TIMEOUT) + ' seconds.')
                except apitools_base.HttpError as error:
                    raise exceptions.HttpException(dm_v2_util.GetError(error))
                try:
                    completed_operation = client.operations.Get(
                        messages.DeploymentmanagerOperationsGetRequest(
                            project=project,
                            operation=op_name,
                        ))
                except apitools_base.HttpError as error:
                    raise exceptions.HttpException(dm_v2_util.GetError(error))
                operations.append(completed_operation)

        return operations
Ejemplo n.º 28
0
    def Run(self, args):
        """Creates a new Cloud SQL instance.

    Args:
      args: argparse.Namespace, The arguments that this command was invoked
          with.

    Returns:
      A dict object representing the operations resource describing the create
      operation if the create was successful.
    Raises:
      HttpException: A http error response was received while executing api
          request.
      ToolException: An error other than http error occured while executing the
          command.
    """

        # Added this temporarily for debugging SQL instance creation failures
        log.SetVerbosity(logging.DEBUG)
        sql_client = self.context['sql_client']
        sql_messages = self.context['sql_messages']
        resources = self.context['registry']

        util.ValidateInstanceName(args.instance)
        instance_ref = resources.Parse(args.instance,
                                       collection='sql.instances')

        instance_resource = util.ConstructInstanceFromArgs(sql_messages, args)

        if args.master_instance_name:
            replication = 'ASYNCHRONOUS'
            activation_policy = 'ALWAYS'
        else:
            replication = 'SYNCHRONOUS'
            activation_policy = 'ON_DEMAND'
        if not args.replication:
            instance_resource.settings.replicationType = replication
        if not args.activation_policy:
            instance_resource.settings.activationPolicy = activation_policy

        instance_resource.project = instance_ref.project
        instance_resource.instance = instance_ref.instance
        operation_ref = None

        if args.pricing_plan == 'PACKAGE':
            if not console_io.PromptContinue(
                    'Charges will begin accruing immediately. Really create Cloud '
                    'SQL instance?'):
                raise exceptions.ToolException('canceled by the user.')

        try:
            result = sql_client.instances.Insert(instance_resource)

            operation_ref = resources.Create(
                'sql.operations',
                operation=result.operation,
                project=instance_ref.project,
                instance=instance_ref.instance,
            )

            if args. async:
                return sql_client.operations.Get(operation_ref.Request())

            util.WaitForOperation(sql_client, operation_ref,
                                  'Creating Cloud SQL instance')

            log.CreatedResource(instance_ref)

            rsource = sql_client.instances.Get(instance_ref.Request())
            cache = remote_completion.RemoteCompletion()
            cache.AddToCache(instance_ref.SelfLink())
            return rsource
        except apitools_base.HttpError:
            log.debug('operation : %s', str(operation_ref))
            raise
Ejemplo n.º 29
0
def UpdateRC(command_completion, path_update, rc_path, bin_path, sdk_root):
    """Update the system path to include bin_path.

  Args:
    command_completion: bool, Whether or not to do command completion. If None,
      ask.
    path_update: bool, Whether or not to update PATH. If None, ask.
    rc_path: str, The path to the rc file to update. If None, ask.
    bin_path: str, The absolute path to the directory that will contain
      Cloud SDK binaries.
    sdk_root: str, The path to the Cloud SDK root.
  """

    host_os = platforms.OperatingSystem.Current()
    if host_os == platforms.OperatingSystem.WINDOWS:
        if path_update is None:
            path_update = console_io.PromptContinue(
                prompt_string='Update %PATH% to include Cloud SDK binaries?')
        if path_update:
            _UpdatePathForWindows(bin_path)
        return

    if command_completion is None:
        if path_update is None:  # Ask only one question if both were not set.
            path_update = console_io.PromptContinue(
                prompt_string=('\nModify profile to update your $PATH '
                               'and enable shell command completion?'))
            command_completion = path_update
        else:
            command_completion = console_io.PromptContinue(
                prompt_string=('\nModify profile to enable shell command '
                               'completion?'))
    elif path_update is None:
        path_update = console_io.PromptContinue(
            prompt_string=('\nModify profile to update your $PATH?'))

    rc_paths = _GetRcPaths(command_completion, path_update, rc_path, sdk_root,
                           host_os)

    if rc_paths.rc_path:
        if os.path.exists(rc_paths.rc_path):
            with open(rc_paths.rc_path) as rc_file:
                rc_data = rc_file.read()
                cached_rc_data = rc_data
        else:
            rc_data = ''
            cached_rc_data = ''

        if path_update:
            rc_data = _GetRcData(
                '# The next line updates PATH for the Google Cloud'
                ' SDK.', rc_paths.path, rc_data)

        if command_completion:
            rc_data = _GetRcData(
                '# The next line enables shell command completion'
                ' for gcloud.',
                rc_paths.completion,
                rc_data,
                pattern='# The next line enables [a-z][a-z]*'
                'completion for gcloud.')

        if cached_rc_data == rc_data:
            print(
                'No changes necessary for [{rc}].'.format(rc=rc_paths.rc_path))
            return

        if os.path.exists(rc_paths.rc_path):
            rc_backup = rc_paths.rc_path + '.backup'
            print('Backing up [{rc}] to [{backup}].'.format(
                rc=rc_paths.rc_path, backup=rc_backup))
            shutil.copyfile(rc_paths.rc_path, rc_backup)

        with open(rc_paths.rc_path, 'w') as rc_file:
            rc_file.write(rc_data)

        print("""\
[{rc_path}] has been updated.
Start a new shell for the changes to take effect.
""".format(rc_path=rc_paths.rc_path))

    if not command_completion:
        print("""\
Source [{rc}]
in your profile to enable shell command completion for gcloud.
""".format(rc=rc_paths.completion))

    if not path_update:
        print("""\
Source [{rc}]
in your profile to add the Google Cloud SDK command line tools to your $PATH.
""".format(rc=rc_paths.path))
Ejemplo n.º 30
0
def PromptForDeletionHelper(resource_name, prompt_list, prompt_title=None):
    prompt_title = (prompt_title or
                    'The following {0} will be deleted:'.format(resource_name))
    prompt_message = ConstructList(prompt_title, prompt_list)
    if not console_io.PromptContinue(message=prompt_message):
        raise calliope_exceptions.ToolException('Deletion aborted by user.')