Exemple #1
0
  def Run(self, args):
    """Creates an SSL certificate for 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 create
      operation if the create was successful.
    Raises:
      ArgumentError: If the file path provided cannot be written to.
    """

    if os.path.exists(args.cert_file):
      raise exceptions.ArgumentError(
          'file [{path}] already exists'.format(path=args.cert_file))

    # First check if args.out_file is writeable. If not, abort and don't create
    # the useless cert.
    try:
      files.WriteFileContents(
          args.cert_file, 'placeholder\n', private=True, create_path=False)
    except (files.Error, OSError) as e:
      raise exceptions.ArgumentError('unable to write [{path}]: {error}'.format(
          path=args.cert_file, error=six.text_type(e)))

    client = api_util.SqlClient(api_util.API_VERSION_DEFAULT)
    sql_client = client.sql_client
    sql_messages = client.sql_messages

    validate.ValidateInstanceName(args.instance)
    instance_ref = client.resource_parser.Parse(
        args.instance,
        params={'project': properties.VALUES.core.project.GetOrFail},
        collection='sql.instances')

    # TODO(b/36049399): figure out how to rectify the common_name and the
    # sha1fingerprint, so that things can work with the resource parser.

    result = sql_client.sslCerts.Insert(
        sql_messages.SqlSslCertsInsertRequest(
            project=instance_ref.project,
            instance=instance_ref.instance,
            sslCertsInsertRequest=sql_messages.SslCertsInsertRequest(
                commonName=args.common_name)))

    private_key = result.clientCert.certPrivateKey
    files.WriteFileContents(args.cert_file, private_key + '\n', private=True)

    cert_ref = client.resource_parser.Create(
        collection='sql.sslCerts',
        project=instance_ref.project,
        instance=instance_ref.instance,
        sha1Fingerprint=result.clientCert.certInfo.sha1Fingerprint)

    log.CreatedResource(cert_ref)
    return result.clientCert.certInfo
Exemple #2
0
 def _CheckSourceAndDestination(self, source_instance_ref,
                                destination_instance_ref):
     if source_instance_ref.project != destination_instance_ref.project:
         raise exceptions.ArgumentError(
             'The source and the clone instance must belong to the same project:'
             ' "{src}" != "{dest}".'.format(
                 src=source_instance_ref.project,
                 dest=destination_instance_ref.project))
def _CheckSourceAndDestination(source_instance_ref, destination_instance_ref):
  """Verify that the source and destination instance ids are different."""

  if source_instance_ref.project != destination_instance_ref.project:
    raise exceptions.ArgumentError(
        'The source and the clone instance must belong to the same project:'
        ' "{src}" != "{dest}".'.format(
            src=source_instance_ref.project,
            dest=destination_instance_ref.project))
Exemple #4
0
 def _UpdateRequestFromArgs(self, request, args, sql_messages):
     if args.bin_log_file_name and args.bin_log_position:
         clone_context = request.instancesCloneRequest.cloneContext
         clone_context.binLogCoordinates = sql_messages.BinLogCoordinates(
             binLogFileName=args.bin_log_file_name,
             binLogPosition=args.bin_log_position)
     elif args.bin_log_file_name or args.bin_log_position:
         raise exceptions.ArgumentError(
             'Both --bin-log-file-name and --bin-log-position must be specified to'
             ' represent a valid binary log coordinate up to which the source is'
             ' cloned.')
Exemple #5
0
def ValidateInstanceName(instance_name):
  if ':' in instance_name:
    name_components = instance_name.split(':')
    possible_project = name_components[0]
    possible_instance = name_components[-1]
    raise sql_exceptions.ArgumentError("""\
Instance names cannot contain the ':' character. If you meant to indicate the
project for [{instance}], use only '{instance}' for the argument, and either add
'--project {project}' to the command line or first run
  $ gcloud config set project {project}
""".format(project=possible_project, instance=possible_instance))
def RunBaseCreateCommand(args, release_track):
  """Creates a new Cloud SQL instance.

  Args:
    args: argparse.Namespace, The arguments that this command was invoked with.
    release_track: base.ReleaseTrack, the release track that this was run under.

  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.
    RequiredArgumentException: A required argument was not supplied by the user,
      such as omitting --root-password on a SQL Server instance.
    ArgumentError: An argument supplied by the user was incorrect, such as
      attempting to create a V1 instance.
  """
  client = common_api_util.SqlClient(common_api_util.API_VERSION_DEFAULT)
  sql_client = client.sql_client
  sql_messages = client.sql_messages

  validate.ValidateInstanceName(args.instance)
  instance_ref = client.resource_parser.Parse(
      args.instance,
      params={'project': properties.VALUES.core.project.GetOrFail},
      collection='sql.instances')

  # Get the region, tier, and database version from the master if these fields
  # are not specified.
  # TODO(b/64266672): Remove once API does not require these fields.
  if args.IsSpecified('master_instance_name'):
    master_instance_ref = client.resource_parser.Parse(
        args.master_instance_name,
        params={'project': properties.VALUES.core.project.GetOrFail},
        collection='sql.instances')
    try:
      master_instance_resource = sql_client.instances.Get(
          sql_messages.SqlInstancesGetRequest(
              project=instance_ref.project,
              instance=master_instance_ref.instance))
    except apitools_exceptions.HttpError as error:
      # TODO(b/64292220): Remove once API gives helpful error message.
      log.debug('operation : %s', six.text_type(master_instance_ref))
      exc = exceptions.HttpException(error)
      if resource_property.Get(exc.payload.content,
                               resource_lex.ParseKey('error.errors[0].reason'),
                               None) == 'notAuthorized':
        msg = ('You are either not authorized to access the master instance or '
               'it does not exist.')
        raise exceptions.HttpException(msg)
      raise
    if not args.IsSpecified('region'):
      args.region = master_instance_resource.region
    if not args.IsSpecified('database_version'):
      args.database_version = master_instance_resource.databaseVersion.name
    if not args.IsSpecified('tier') and master_instance_resource.settings:
      args.tier = master_instance_resource.settings.tier
    # Check for CMEK usage; warn the user about replica inheriting the setting.
    if master_instance_resource.diskEncryptionConfiguration:
      command_util.ShowCmekWarning('replica', 'the master instance')

  # --root-password is required when creating SQL Server instances
  if args.IsSpecified('database_version') and args.database_version.startswith(
      'SQLSERVER') and not args.IsSpecified('root_password'):
    raise exceptions.RequiredArgumentException(
        '--root-password',
        '`--root-password` is required when creating SQL Server instances.')

  instance_resource = (
      command_util.InstancesV1Beta4.ConstructCreateInstanceFromArgs(
          sql_messages,
          args,
          instance_ref=instance_ref,
          release_track=release_track))

  # TODO(b/122660263): Remove when V1 instances are no longer supported.
  # V1 instances are deprecated.
  # Note that the exception type is intentionally vague because the user may not
  # have directly supplied the offending argument.  For example, creating a read
  # replica defaults its tier to that of its master.
  if api_util.IsInstanceV1(sql_messages, instance_resource):
    raise sql_exceptions.ArgumentError(
        'First Generation instances can no longer be created.')

  operation_ref = None
  try:
    result_operation = sql_client.instances.Insert(instance_resource)

    operation_ref = client.resource_parser.Create(
        'sql.operations',
        operation=result_operation.name,
        project=instance_ref.project)

    if args.async_:
      if not args.IsSpecified('format'):
        args.format = 'default'
      return sql_client.operations.Get(
          sql_messages.SqlOperationsGetRequest(
              project=operation_ref.project, operation=operation_ref.operation))

    operations.OperationsV1Beta4.WaitForOperation(
        sql_client,
        operation_ref,
        'Creating Cloud SQL instance',
        # TODO(b/138403566): Remove the override once we improve creation times.
        max_wait_seconds=680)

    log.CreatedResource(instance_ref)

    new_resource = sql_client.instances.Get(
        sql_messages.SqlInstancesGetRequest(
            project=instance_ref.project, instance=instance_ref.instance))
    return new_resource
  except apitools_exceptions.HttpError as error:
    log.debug('operation : %s', six.text_type(operation_ref))
    exc = exceptions.HttpException(error)
    if resource_property.Get(exc.payload.content,
                             resource_lex.ParseKey('error.errors[0].reason'),
                             None) == 'errorMaxInstancePerLabel':
      msg = resource_property.Get(exc.payload.content,
                                  resource_lex.ParseKey('error.message'), None)
      raise exceptions.HttpException(msg)
    raise
Exemple #7
0
def RunBasePatchCommand(args, release_track):
    """Updates settings of a Cloud SQL instance using the patch api method.

  Args:
    args: argparse.Namespace, The arguments that this command was invoked with.
    release_track: base.ReleaseTrack, the release track that this was run under.

  Returns:
    A dict object representing the operations resource describing the patch
    operation if the patch was successful.
  Raises:
    CancelledError: The user chose not to continue.
  """
    if args.diff and not args.IsSpecified('format'):
        args.format = 'diff(old, new)'

    client = common_api_util.SqlClient(common_api_util.API_VERSION_DEFAULT)
    sql_client = client.sql_client
    sql_messages = client.sql_messages

    validate.ValidateInstanceName(args.instance)
    instance_ref = client.resource_parser.Parse(
        args.instance,
        params={'project': properties.VALUES.core.project.GetOrFail},
        collection='sql.instances')

    if args.IsSpecified('no_backup'):
        if args.IsSpecified('enable_bin_log'):
            raise exceptions.ArgumentError(
                '`--enable-bin-log` cannot be specified when --no-backup is '
                'specified')
        elif args.IsSpecified('enable_point_in_time_recovery'):
            raise exceptions.ArgumentError(
                '`--enable-point-in-time-recovery` cannot be specified when '
                '--no-backup is specified')

    # If --authorized-networks is used, confirm that the user knows the networks
    # will get overwritten.
    if args.authorized_networks:
        api_util.InstancesV1Beta4.PrintAndConfirmAuthorizedNetworksOverwrite()

    original_instance_resource = sql_client.instances.Get(
        sql_messages.SqlInstancesGetRequest(project=instance_ref.project,
                                            instance=instance_ref.instance))

    patch_instance = command_util.InstancesV1Beta4.ConstructPatchInstanceFromArgs(
        sql_messages,
        args,
        original=original_instance_resource,
        release_track=release_track)
    patch_instance.project = instance_ref.project
    patch_instance.name = instance_ref.instance

    # TODO(b/122660263): Remove when V1 instances are no longer supported.
    # V1 deprecation notice.
    if api_util.IsInstanceV1(sql_messages, original_instance_resource):
        command_util.ShowV1DeprecationWarning()

    cleared_fields = _GetConfirmedClearedFields(args, patch_instance,
                                                original_instance_resource)
    # beta only
    if args.maintenance_window_any:
        cleared_fields.append('settings.maintenanceWindow')

    with sql_client.IncludeFields(cleared_fields):
        result_operation = sql_client.instances.Patch(
            sql_messages.SqlInstancesPatchRequest(
                databaseInstance=patch_instance,
                project=instance_ref.project,
                instance=instance_ref.instance))

    operation_ref = client.resource_parser.Create(
        'sql.operations',
        operation=result_operation.name,
        project=instance_ref.project)

    if args.async_:
        return sql_client.operations.Get(
            sql_messages.SqlOperationsGetRequest(
                project=operation_ref.project,
                operation=operation_ref.operation))

    operations.OperationsV1Beta4.WaitForOperation(
        sql_client, operation_ref, 'Patching Cloud SQL instance')

    log.UpdatedResource(instance_ref)

    changed_instance_resource = sql_client.instances.Get(
        sql_messages.SqlInstancesGetRequest(project=instance_ref.project,
                                            instance=instance_ref.instance))
    return _Result(changed_instance_resource, original_instance_resource)
def BackupConfiguration(sql_messages,
                        instance=None,
                        backup_enabled=None,
                        backup_location=None,
                        backup_start_time=None,
                        enable_bin_log=None,
                        enable_point_in_time_recovery=None,
                        retained_backups_count=None,
                        retained_transaction_log_days=None):
  """Generates the backup configuration for the instance.

  Args:
    sql_messages: module, The messages module that should be used.
    instance: sql_messages.DatabaseInstance, the original instance, if the
      previous state is needed.
    backup_enabled: boolean, True if backup should be enabled.
    backup_location: string, location where to store backups by default.
    backup_start_time: string, start time of backup specified in 24-hour format.
    enable_bin_log: boolean, True if binary logging should be enabled.
    enable_point_in_time_recovery: boolean, True if point-in-time recovery
      (using write-ahead log archiving) should be enabled.
    retained_backups_count: int, how many backups to keep stored.
    retained_transaction_log_days: int, how many days of transaction logs to
      keep stored.

  Returns:
    sql_messages.BackupConfiguration object, or None

  Raises:
    ToolException: Bad combination of arguments.
  """
  should_generate_config = any([
      backup_location is not None,
      backup_start_time,
      enable_bin_log is not None,
      enable_point_in_time_recovery is not None,
      retained_backups_count is not None,
      retained_transaction_log_days is not None,
      not backup_enabled,
  ])

  if not should_generate_config:
    return None

  if not instance or not instance.settings.backupConfiguration:
    backup_config = sql_messages.BackupConfiguration(
        kind='sql#backupConfiguration',
        startTime='00:00',
        enabled=backup_enabled)
  else:
    backup_config = instance.settings.backupConfiguration

  if backup_location is not None:
    backup_config.location = backup_location
    backup_config.enabled = True
  if backup_start_time:
    backup_config.startTime = backup_start_time
    backup_config.enabled = True

  if retained_backups_count is not None:
    backup_retention_settings = (
        backup_config.backupRetentionSettings or
        sql_messages.BackupRetentionSettings())
    backup_retention_settings.retentionUnit = sql_messages.BackupRetentionSettings.RetentionUnitValueValuesEnum.COUNT
    backup_retention_settings.retainedBackups = retained_backups_count

    backup_config.backupRetentionSettings = backup_retention_settings
    backup_config.enabled = True

  if retained_transaction_log_days is not None:
    backup_config.transactionLogRetentionDays = retained_transaction_log_days
    backup_config.enabled = True

  if not backup_enabled:
    if (backup_location is not None or backup_start_time or
        retained_backups_count is not None or
        retained_transaction_log_days is not None):
      raise sql_exceptions.ArgumentError(
          'Argument --no-backup not allowed with --backup-location, '
          '--backup-start-time, --retained-backups-count, or '
          '--retained-transaction-log-days')
    backup_config.enabled = False

  if enable_bin_log is not None:
    backup_config.binaryLogEnabled = enable_bin_log

  if enable_point_in_time_recovery is not None:
    backup_config.pointInTimeRecoveryEnabled = enable_point_in_time_recovery

  # retainedTransactionLogDays is only valid when we have transaction logs,
  # i.e, have binlog or pitr.
  if (retained_transaction_log_days and not backup_config.binaryLogEnabled and
      not backup_config.pointInTimeRecoveryEnabled):
    raise sql_exceptions.ArgumentError(
        'Argument --retained-transaction-log-days only valid when '
        'transaction logs are enabled. To enable transaction logs, use '
        '--enable-bin-log for MySQL, and use --enable-point-in-time-recovery '
        'for Postgres.')

  return backup_config
def RunBaseCloneCommand(args, release_track):
  """Clones a Cloud SQL instance.

  Args:
    args: argparse.Namespace, The arguments used to invoke this command.
    release_track: base.ReleaseTrack, the release track that this was run under.

  Returns:
    A dict object representing the operations resource describing the
    clone operation if the clone was successful.
  Raises:
    ArgumentError: The arguments are invalid for some reason.
  """

  client = api_util.SqlClient(api_util.API_VERSION_DEFAULT)
  sql_client = client.sql_client
  sql_messages = client.sql_messages

  source_instance_ref, destination_instance_ref = (
      _GetInstanceRefsFromArgs(args, client))

  request = sql_messages.SqlInstancesCloneRequest(
      project=source_instance_ref.project,
      instance=source_instance_ref.instance,
      instancesCloneRequest=sql_messages.InstancesCloneRequest(
          cloneContext=sql_messages.CloneContext(
              kind='sql#cloneContext',
              destinationInstanceName=destination_instance_ref.instance)))

  _UpdateRequestFromArgs(request, args, sql_messages, release_track)

  # Check if source is V1; raise error if so.
  # Check if source has customer-managed key; show warning if so.
  try:
    source_instance_resource = sql_client.instances.Get(
        sql_messages.SqlInstancesGetRequest(
            project=source_instance_ref.project,
            instance=source_instance_ref.instance))

    # TODO(b/122660263): Remove when V1 instances are no longer supported.
    if instance_util.IsInstanceV1(sql_messages, source_instance_resource):
      raise exceptions.ArgumentError(
          'First Generation instances can no longer be created.')
    if source_instance_resource.diskEncryptionConfiguration:
      command_util.ShowCmekWarning('clone', 'the source instance')
  except apitools_exceptions.HttpError:
    # This is for informational purposes, so don't throw an error if failure.
    pass

  result = sql_client.instances.Clone(request)

  operation_ref = client.resource_parser.Create(
      'sql.operations',
      operation=result.name,
      project=destination_instance_ref.project)

  if args.async_:
    if not args.IsSpecified('format'):
      args.format = 'default'
    return sql_client.operations.Get(
        sql_messages.SqlOperationsGetRequest(
            project=operation_ref.project, operation=operation_ref.operation))
  operations.OperationsV1Beta4.WaitForOperation(sql_client, operation_ref,
                                                'Cloning Cloud SQL instance')
  log.CreatedResource(destination_instance_ref)
  rsource = sql_client.instances.Get(
      sql_messages.SqlInstancesGetRequest(
          project=destination_instance_ref.project,
          instance=destination_instance_ref.instance))
  rsource.kind = None
  return rsource