# -*- coding: utf-8 -*- # # Copyright 2013 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Clones a Cloud SQL instance.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from apitools.base.py import exceptions as apitools_exceptions from googlecloudsdk.api_lib.sql import api_util from googlecloudsdk.api_lib.sql import exceptions from googlecloudsdk.api_lib.sql import operations from googlecloudsdk.api_lib.sql import validate from googlecloudsdk.calliope import arg_parsers from googlecloudsdk.calliope import base from googlecloudsdk.command_lib.sql import flags from googlecloudsdk.command_lib.sql import instances as command_util from googlecloudsdk.core import log from googlecloudsdk.core import properties DESCRIPTION = ("""\ *{command}* creates a clone of a Cloud SQL instance. The clone is an independent copy of the source instance with the same data and settings. Source and destination instances must be in the same project. An instance can be cloned from its current state, or from an earlier point in time. For MySQL: The binary log coordinates or timestamp (point in time), if specified, act as the point in time the source instance is cloned from. If not specified, the current state of the instance is cloned. For PostgreSQL: The point in time, if specified, defines a past state of the instance to clone. If not specified, the current state of the instance is cloned. For SQL Server: The point in time, if specified, defines a past state of the instance to clone. If not specified, the current state of the instance is cloned. """) EXAMPLES_GA = ("""\ To clone an instance from its current state (most recent binary log coordinates): $ {command} my-source-instance my-cloned-instance To clone a MySQL instance from an earlier point in time (past binary log coordinates): $ {command} my-source-instance my-cloned-instance --bin-log-file-name mysql-bin.000020 --bin-log-position 170 To clone a MySQL source instance at a specific point in time: $ {command} my-source-instance my-cloned-instance --point-in-time '2012-11-15T16:19:00.094Z' To clone a PostgreSQL source instance at a specific point in time: $ {command} my-source-instance my-cloned-instance --point-in-time '2012-11-15T16:19:00.094Z' To clone a SQL Server source instance at a specific point in time: $ {command} my-source-instance my-cloned-instance --point-in-time '2012-11-15T16:19:00.094Z' To clone a deleted instance, include the name and deletion time of the source instance: $ {command} my-source-instance my-cloned-instance --source-instance-deletion-time '2012-11-15T16:19:00.094Z' """) EXAMPLES_ALPHA = ("""\ To specify the allocated IP range for the private IP target Instance (reserved for future use): $ {command} my-source-instance my-cloned-instance --allocated-ip-range-name cloned-instance-ip-range """) DETAILED_HELP = { 'DESCRIPTION': DESCRIPTION, 'EXAMPLES': EXAMPLES_GA, } DETAILED_APLHA_HELP = { 'DESCRIPTION': DESCRIPTION, 'EXAMPLES': EXAMPLES_GA + EXAMPLES_ALPHA, } def _GetInstanceRefsFromArgs(args, client): """Get validated refs to source and destination instances from args.""" validate.ValidateInstanceName(args.source) validate.ValidateInstanceName(args.destination) source_instance_ref = client.resource_parser.Parse( args.source, params={'project': properties.VALUES.core.project.GetOrFail}, collection='sql.instances') destination_instance_ref = client.resource_parser.Parse( args.destination, params={'project': properties.VALUES.core.project.GetOrFail}, collection='sql.instances') _CheckSourceAndDestination(source_instance_ref, destination_instance_ref) return source_instance_ref, destination_instance_ref 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 AddAlphaArgs(parser): """Declare alpha flags for this command parser.""" parser.add_argument( '--allocated-ip-range-name', required=False, help="""\ The name of the IP range allocated for the destination instance with private network connectivity. For example: \'google-managed-services-default\'. If set, the destination instance IP is created in the allocated range represented by this name. Reserved for future use. """) def _UpdateRequestFromArgs(request, args, sql_messages, release_track): """Update request with clone options.""" clone_context = request.instancesCloneRequest.cloneContext # PITR options if args.bin_log_file_name and args.bin_log_position: clone_context.binLogCoordinates = sql_messages.BinLogCoordinates( binLogFileName=args.bin_log_file_name, binLogPosition=args.bin_log_position) elif args.point_in_time: clone_context.pointInTime = args.point_in_time.strftime( '%Y-%m-%dT%H:%M:%S.%fZ') if args.point_in_time and args.restore_database_name: clone_context.databaseNames[:] = [args.restore_database_name] if args.preferred_zone: clone_context.preferredZone = args.preferred_zone if args.preferred_secondary_zone: clone_context.preferredSecondaryZone = args.preferred_secondary_zone if args.source_instance_deletion_time: clone_context.sourceInstanceDeletionTime = ( args.source_instance_deletion_time.strftime('%Y-%m-%dT%H:%M:%S.%fZ') ) if release_track == base.ReleaseTrack.ALPHA: # ALLOCATED IP RANGE options if args.allocated_ip_range_name: clone_context.allocatedIpRange = args.allocated_ip_range_name 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 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)) 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 def AddBaseArgs(parser): """Add args common to all release tracks to parser.""" base.ASYNC_FLAG.AddToParser(parser) parser.display_info.AddFormat(flags.GetInstanceListFormat()) parser.add_argument( 'source', completer=flags.InstanceCompleter, help='Cloud SQL instance ID of the source.') parser.add_argument('destination', help='Cloud SQL instance ID of the clone.') pitr_options_group = parser.add_group(mutex=True, required=False) bin_log_group = pitr_options_group.add_group( mutex=False, required=False, help='Binary log coordinates for point-in-time recovery.') bin_log_group.add_argument( '--bin-log-file-name', required=True, help="""\ The name of the binary log file. Enable point-in-time recovery on the source instance to create a binary log file. If specified with <--bin-log-position> to form a valid binary log coordinate, it defines an earlier point in time to clone a source instance from. For example, mysql-bin.000001. """) bin_log_group.add_argument( '--bin-log-position', type=int, required=True, help="""\ Represents the state of an instance at any given point in time inside a binary log file. If specified along with <--bin-log-file-name> to form a valid binary log coordinate, it defines an earlier point in time to clone a source instance from. For example, 123 (a numeric value). """) point_in_time_group = pitr_options_group.add_group( mutex=False, required=False) point_in_time_group.add_argument( '--point-in-time', type=arg_parsers.Datetime.Parse, required=True, help="""\ Represents the state of an instance at any given point in time inside a transaction log file. For MySQL, the binary log file is used for transaction logs. For PostgreSQL, the write-ahead log file is used for transaction logs. For SQL Server, the log backup file is used for such purpose. To create a transaction log, enable point-in-time recovery on the source instance. Instance should have transaction logs accumulated up to the point in time they want to restore up to. Uses RFC 3339 format in UTC timezone. If specified, defines a past state of the instance to clone. For example, '2012-11-15T16:19:00.094Z'. """) point_in_time_group.add_argument( '--restore-database-name', required=False, help="""\ The name of the database to be restored for a point-in-time restore. If set, the destination instance will only restore the specified database. """) parser.add_argument( '--preferred-zone', required=False, help="""\ The preferred zone for the cloned instance. If you specify a value for this flag, then the destination instance uses the value as the primary zone. """) parser.add_argument( '--preferred-secondary-zone', required=False, help="""\ The preferred secondary zone for the cloned regional instance. If you specify a value for this flag, then the destination instance uses the value as the secondary zone. The secondary zone can't be the same as the primary zone. """) parser.add_argument( '--source-instance-deletion-time', type=arg_parsers.Datetime.Parse, required=False, help="""\ The time the source instance was deleted. This is required if cloning from a deleted instance. """) @base.DefaultUniverseOnly @base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA) class Clone(base.CreateCommand): """Clones a Cloud SQL instance.""" detailed_help = DETAILED_HELP @classmethod def Args(cls, parser): """Declare flag and positional arguments for the command parser.""" AddBaseArgs(parser) parser.display_info.AddCacheUpdater(flags.InstanceCompleter) def Run(self, args): return RunBaseCloneCommand(args, self.ReleaseTrack()) @base.DefaultUniverseOnly @base.ReleaseTracks(base.ReleaseTrack.ALPHA) class CloneAlpha(base.CreateCommand): """Clones a Cloud SQL instance.""" detailed_help = DETAILED_APLHA_HELP def Run(self, args): return RunBaseCloneCommand(args, self.ReleaseTrack()) @staticmethod def Args(parser): """Args is called by calliope to gather arguments for this command.""" AddBaseArgs(parser) AddAlphaArgs(parser) parser.display_info.AddCacheUpdater(flags.InstanceCompleter)