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
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))
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.')
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
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