From 530b1aa08e03a28a1c789db45c1936bc0d56344f Mon Sep 17 00:00:00 2001 From: Daniel Lindsley Date: Wed, 11 Sep 2013 17:31:24 -0700 Subject: Added ``dry_run`` to. All. The. Things. --- boto/ec2/address.py | 33 +- boto/ec2/connection.py | 786 ++++++++++++++++++++++++++----- boto/ec2/ec2object.py | 20 +- boto/ec2/image.py | 70 ++- boto/ec2/instance.py | 96 ++-- boto/ec2/keypair.py | 18 +- boto/ec2/networkinterface.py | 7 +- boto/ec2/placementgroup.py | 11 +- boto/ec2/reservedinstance.py | 8 +- boto/ec2/securitygroup.py | 67 ++- boto/ec2/snapshot.py | 61 ++- boto/ec2/spotdatafeedsubscription.py | 10 +- boto/ec2/spotinstancerequest.py | 7 +- boto/ec2/volume.py | 52 +- boto/vpc/__init__.py | 295 ++++++++++-- boto/vpc/vpc.py | 7 +- boto/vpc/vpnconnection.py | 7 +- boto/vpc/vpngateway.py | 16 +- tests/integration/ec2/test_connection.py | 51 +- 19 files changed, 1286 insertions(+), 336 deletions(-) diff --git a/boto/ec2/address.py b/boto/ec2/address.py index 9eadfaa3..27608a4a 100644 --- a/boto/ec2/address.py +++ b/boto/ec2/address.py @@ -71,33 +71,50 @@ class Address(EC2Object): else: setattr(self, name, value) - def release(self): + def release(self, dry_run=False): """ Free up this Elastic IP address. :see: :meth:`boto.ec2.connection.EC2Connection.release_address` """ if self.allocation_id: - return self.connection.release_address(None, self.allocation_id) + return self.connection.release_address( + None, + self.allocation_id, + dry_run=dry_run) else: - return self.connection.release_address(self.public_ip) + return self.connection.release_address( + self.public_ip, + dry_run=dry_run + ) delete = release - def associate(self, instance_id): + def associate(self, instance_id, dry_run=False): """ Associate this Elastic IP address with a currently running instance. :see: :meth:`boto.ec2.connection.EC2Connection.associate_address` """ - return self.connection.associate_address(instance_id, self.public_ip) + return self.connection.associate_address( + instance_id, + self.public_ip, + dry_run=dry_run + ) - def disassociate(self): + def disassociate(self, dry_run=False): """ Disassociate this Elastic IP address from a currently running instance. :see: :meth:`boto.ec2.connection.EC2Connection.disassociate_address` """ if self.association_id: - return self.connection.disassociate_address(None, self.association_id) + return self.connection.disassociate_address( + None, + self.association_id, + dry_run=dry_run + ) else: - return self.connection.disassociate_address(self.public_ip) + return self.connection.disassociate_address( + self.public_ip, + dry_run=dry_run + ) diff --git a/boto/ec2/connection.py b/boto/ec2/connection.py index 9d9b1ae1..729ffdde 100644 --- a/boto/ec2/connection.py +++ b/boto/ec2/connection.py @@ -133,7 +133,7 @@ class EC2Connection(AWSQueryConnection): # Image methods def get_all_images(self, image_ids=None, owners=None, - executable_by=None, filters=None): + executable_by=None, filters=None, dry_run=False): """ Retrieve all the EC2 images available on your account. @@ -158,6 +158,9 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.image.Image` """ @@ -170,10 +173,12 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, executable_by, 'ExecutableBy') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeImages', params, [('item', Image)], verb='POST') - def get_all_kernels(self, kernel_ids=None, owners=None): + def get_all_kernels(self, kernel_ids=None, owners=None, dry_run=False): """ Retrieve all the EC2 kernels available on your account. Constructs a filter to allow the processing to happen server side. @@ -184,6 +189,9 @@ class EC2Connection(AWSQueryConnection): :type owners: list :param owners: A list of owner IDs + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.image.Image` """ @@ -194,10 +202,12 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, owners, 'Owner') filter = {'image-type': 'kernel'} self.build_filter_params(params, filter) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeImages', params, [('item', Image)], verb='POST') - def get_all_ramdisks(self, ramdisk_ids=None, owners=None): + def get_all_ramdisks(self, ramdisk_ids=None, owners=None, dry_run=False): """ Retrieve all the EC2 ramdisks available on your account. Constructs a filter to allow the processing to happen server side. @@ -208,6 +218,9 @@ class EC2Connection(AWSQueryConnection): :type owners: list :param owners: A list of owner IDs + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.image.Image` """ @@ -218,27 +231,33 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, owners, 'Owner') filter = {'image-type': 'ramdisk'} self.build_filter_params(params, filter) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeImages', params, [('item', Image)], verb='POST') - def get_image(self, image_id): + def get_image(self, image_id, dry_run=False): """ Shortcut method to retrieve a specific image (AMI). :type image_id: string :param image_id: the ID of the Image to retrieve + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.image.Image` :return: The EC2 Image specified or None if the image is not found """ try: - return self.get_all_images(image_ids=[image_id])[0] + return self.get_all_images(image_ids=[image_id], dry_run=dry_run)[0] except IndexError: # None of those images available return None def register_image(self, name=None, description=None, image_location=None, architecture=None, kernel_id=None, ramdisk_id=None, - root_device_name=None, block_device_map=None): + root_device_name=None, block_device_map=None, + dry_run=False): """ Register an image. @@ -268,6 +287,9 @@ class EC2Connection(AWSQueryConnection): :param block_device_map: A BlockDeviceMapping data structure describing the EBS volumes associated with the Image. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: string :return: The new image id """ @@ -288,11 +310,13 @@ class EC2Connection(AWSQueryConnection): params['RootDeviceName'] = root_device_name if block_device_map: block_device_map.build_list_params(params) + if dry_run: + params['DryRun'] = 'true' rs = self.get_object('RegisterImage', params, ResultSet, verb='POST') image_id = getattr(rs, 'imageId', None) return image_id - def deregister_image(self, image_id, delete_snapshot=False): + def deregister_image(self, image_id, delete_snapshot=False, dry_run=False): """ Unregister an AMI. @@ -303,6 +327,9 @@ class EC2Connection(AWSQueryConnection): :param delete_snapshot: Set to True if we should delete the snapshot associated with an EBS volume mounted at /dev/sda1 + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -313,15 +340,19 @@ class EC2Connection(AWSQueryConnection): if key == "/dev/sda1": snapshot_id = image.block_device_mapping[key].snapshot_id break - + params = { + 'ImageId': image_id, + } + if dry_run: + params['DryRun'] = 'true' result = self.get_status('DeregisterImage', - {'ImageId':image_id}, verb='POST') + params, verb='POST') if result and snapshot_id: return result and self.delete_snapshot(snapshot_id) return result def create_image(self, instance_id, name, - description=None, no_reboot=False): + description=None, no_reboot=False, dry_run=False): """ Will create an AMI from the instance in the running or stopped state. @@ -343,6 +374,9 @@ class EC2Connection(AWSQueryConnection): responsibility of maintaining file system integrity is left to the owner of the instance. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: string :return: The new image id """ @@ -352,12 +386,15 @@ class EC2Connection(AWSQueryConnection): params['Description'] = description if no_reboot: params['NoReboot'] = 'true' + if dry_run: + params['DryRun'] = 'true' img = self.get_object('CreateImage', params, Image, verb='POST') return img.id # ImageAttribute methods - def get_image_attribute(self, image_id, attribute='launchPermission'): + def get_image_attribute(self, image_id, attribute='launchPermission', + dry_run=False): """ Gets an attribute from an image. @@ -371,18 +408,23 @@ class EC2Connection(AWSQueryConnection): * productCodes * blockDeviceMapping + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.image.ImageAttribute` :return: An ImageAttribute object representing the value of the attribute requested """ params = {'ImageId': image_id, 'Attribute': attribute} + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeImageAttribute', params, ImageAttribute, verb='POST') def modify_image_attribute(self, image_id, attribute='launchPermission', operation='add', user_ids=None, groups=None, - product_codes=None): + product_codes=None, dry_run=False): """ Changes an attribute of an image. @@ -406,6 +448,10 @@ class EC2Connection(AWSQueryConnection): :param product_codes: Amazon DevPay product code. Currently only one product code can be associated with an AMI. Once set, the product code cannot be changed or reset. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'ImageId': image_id, 'Attribute': attribute, @@ -416,9 +462,12 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, groups, 'UserGroup') if product_codes: self.build_list_params(params, product_codes, 'ProductCode') + if dry_run: + params['DryRun'] = 'true' return self.get_status('ModifyImageAttribute', params, verb='POST') - def reset_image_attribute(self, image_id, attribute='launchPermission'): + def reset_image_attribute(self, image_id, attribute='launchPermission', + dry_run=False): """ Resets an attribute of an AMI to its default value. @@ -428,16 +477,21 @@ class EC2Connection(AWSQueryConnection): :type attribute: string :param attribute: The attribute to reset + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: Whether the operation succeeded or not """ params = {'ImageId': image_id, 'Attribute': attribute} + if dry_run: + params['DryRun'] = 'true' return self.get_status('ResetImageAttribute', params, verb='POST') # Instance methods - def get_all_instances(self, instance_ids=None, filters=None): + def get_all_instances(self, instance_ids=None, filters=None, dry_run=False): """ Retrieve all the instance reservations associated with your account. @@ -459,6 +513,9 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instance.Reservation` @@ -467,9 +524,10 @@ class EC2Connection(AWSQueryConnection): 'replaced with get_all_reservations.'), PendingDeprecationWarning) return self.get_all_reservations(instance_ids=instance_ids, - filters=filters) + filters=filters, dry_run=dry_run) - def get_only_instances(self, instance_ids=None, filters=None): + def get_only_instances(self, instance_ids=None, filters=None, + dry_run=False): # A future release should rename this method to get_all_instances # and make get_only_instances an alias for that. """ @@ -486,15 +544,20 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instance.Instance` """ reservations = self.get_all_reservations(instance_ids=instance_ids, - filters=filters) + filters=filters, + dry_run=dry_run) return [instance for reservation in reservations for instance in reservation.instances] - def get_all_reservations(self, instance_ids=None, filters=None): + def get_all_reservations(self, instance_ids=None, filters=None, + dry_run=False): """ Retrieve all the instance reservations associated with your account. @@ -509,6 +572,9 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instance.Reservation` """ @@ -525,12 +591,14 @@ class EC2Connection(AWSQueryConnection): "by group name use the 'group-name' filter instead.", UserWarning) self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeInstances', params, [('item', Reservation)], verb='POST') def get_all_instance_status(self, instance_ids=None, max_results=None, next_token=None, - filters=None): + filters=None, dry_run=False): """ Retrieve all the instances in your account scheduled for maintenance. @@ -555,6 +623,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of instances that have maintenance scheduled. """ @@ -567,6 +638,8 @@ class EC2Connection(AWSQueryConnection): params['NextToken'] = next_token if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeInstanceStatus', params, InstanceStatusSet, verb='POST') @@ -584,7 +657,8 @@ class EC2Connection(AWSQueryConnection): security_group_ids=None, additional_info=None, instance_profile_name=None, instance_profile_arn=None, tenancy=None, - ebs_optimized=False, network_interfaces=None): + ebs_optimized=False, network_interfaces=None, + dry_run=False): """ Runs an image on EC2. @@ -715,6 +789,9 @@ class EC2Connection(AWSQueryConnection): :param network_interfaces: A list of :class:`boto.ec2.networkinterface.NetworkInterfaceSpecification` + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: Reservation :return: The :class:`boto.ec2.instance.Reservation` associated with the request for machines @@ -781,26 +858,33 @@ class EC2Connection(AWSQueryConnection): params['EbsOptimized'] = 'true' if network_interfaces: network_interfaces.build_list_params(params) + if dry_run: + params['DryRun'] = 'true' return self.get_object('RunInstances', params, Reservation, verb='POST') - def terminate_instances(self, instance_ids=None): + def terminate_instances(self, instance_ids=None, dry_run=False): """ Terminate the instances specified :type instance_ids: list :param instance_ids: A list of strings of the Instance IDs to terminate + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of the instances terminated """ params = {} if instance_ids: self.build_list_params(params, instance_ids, 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('TerminateInstances', params, [('item', Instance)], verb='POST') - def stop_instances(self, instance_ids=None, force=False): + def stop_instances(self, instance_ids=None, force=False, dry_run=False): """ Stop the instances specified @@ -810,6 +894,9 @@ class EC2Connection(AWSQueryConnection): :type force: bool :param force: Forces the instance to stop + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of the instances stopped """ @@ -818,62 +905,88 @@ class EC2Connection(AWSQueryConnection): params['Force'] = 'true' if instance_ids: self.build_list_params(params, instance_ids, 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('StopInstances', params, [('item', Instance)], verb='POST') - def start_instances(self, instance_ids=None): + def start_instances(self, instance_ids=None, dry_run=False): """ Start the instances specified :type instance_ids: list :param instance_ids: A list of strings of the Instance IDs to start + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of the instances started """ params = {} if instance_ids: self.build_list_params(params, instance_ids, 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('StartInstances', params, [('item', Instance)], verb='POST') - def get_console_output(self, instance_id): + def get_console_output(self, instance_id, dry_run=False): """ Retrieves the console output for the specified instance. :type instance_id: string :param instance_id: The instance ID of a running instance on the cloud. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.instance.ConsoleOutput` :return: The console output as a ConsoleOutput object """ params = {} self.build_list_params(params, [instance_id], 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_object('GetConsoleOutput', params, ConsoleOutput, verb='POST') - def reboot_instances(self, instance_ids=None): + def reboot_instances(self, instance_ids=None, dry_run=False): """ Reboot the specified instances. :type instance_ids: list :param instance_ids: The instances to terminate and reboot + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {} if instance_ids: self.build_list_params(params, instance_ids, 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_status('RebootInstances', params) - def confirm_product_instance(self, product_code, instance_id): + def confirm_product_instance(self, product_code, instance_id, + dry_run=False): + """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = {'ProductCode': product_code, 'InstanceId': instance_id} + if dry_run: + params['DryRun'] = 'true' rs = self.get_object('ConfirmProductInstance', params, ResultSet, verb='POST') return (rs.status, rs.ownerId) # InstanceAttribute methods - def get_instance_attribute(self, instance_id, attribute): + def get_instance_attribute(self, instance_id, attribute, dry_run=False): """ Gets an attribute from an instance. @@ -897,6 +1010,9 @@ class EC2Connection(AWSQueryConnection): * groupSet * ebsOptimized + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.image.InstanceAttribute` :return: An InstanceAttribute object representing the value of the attribute requested @@ -904,11 +1020,13 @@ class EC2Connection(AWSQueryConnection): params = {'InstanceId': instance_id} if attribute: params['Attribute'] = attribute + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeInstanceAttribute', params, InstanceAttribute, verb='POST') def modify_network_interface_attribute(self, interface_id, attr, value, - attachment_id=None): + attachment_id=None, dry_run=False): """ Changes an attribute of a network interface. @@ -935,6 +1053,10 @@ class EC2Connection(AWSQueryConnection): :type attachment_id: string :param attachment_id: If you're modifying DeleteOnTermination you must specify the attachment_id. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ bool_reqs = ( 'deleteontermination', @@ -970,10 +1092,13 @@ class EC2Connection(AWSQueryConnection): else: raise ValueError('Unknown attribute "%s"' % (attr,)) + if dry_run: + params['DryRun'] = 'true' return self.get_status( 'ModifyNetworkInterfaceAttribute', params, verb='POST') - def modify_instance_attribute(self, instance_id, attribute, value): + def modify_instance_attribute(self, instance_id, attribute, value, + dry_run=False): """ Changes an attribute of an instance @@ -997,6 +1122,9 @@ class EC2Connection(AWSQueryConnection): :type value: string :param value: The new value for the attribute + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: Whether the operation succeeded or not """ @@ -1030,9 +1158,11 @@ class EC2Connection(AWSQueryConnection): attribute = attribute[0].upper() + attribute[1:] params['%s.Value' % attribute] = value + if dry_run: + params['DryRun'] = 'true' return self.get_status('ModifyInstanceAttribute', params, verb='POST') - def reset_instance_attribute(self, instance_id, attribute): + def reset_instance_attribute(self, instance_id, attribute, dry_run=False): """ Resets an attribute of an instance to its default value. @@ -1043,17 +1173,22 @@ class EC2Connection(AWSQueryConnection): :param attribute: The attribute to reset. Valid values are: kernel|ramdisk + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: Whether the operation succeeded or not """ params = {'InstanceId': instance_id, 'Attribute': attribute} + if dry_run: + params['DryRun'] = 'true' return self.get_status('ResetInstanceAttribute', params, verb='POST') # Spot Instances def get_all_spot_instance_requests(self, request_ids=None, - filters=None): + filters=None, dry_run=False): """ Retrieve all the spot instances requests associated with your account. @@ -1068,6 +1203,9 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.spotinstancerequest.SpotInstanceRequest` @@ -1085,12 +1223,14 @@ class EC2Connection(AWSQueryConnection): "group name. Please update your filters accordingly.", UserWarning) self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeSpotInstanceRequests', params, [('item', SpotInstanceRequest)], verb='POST') def get_spot_price_history(self, start_time=None, end_time=None, instance_type=None, product_description=None, - availability_zone=None): + availability_zone=None, dry_run=False): """ Retrieve the recent history of spot instances pricing. @@ -1121,6 +1261,9 @@ class EC2Connection(AWSQueryConnection): should be returned. If not specified, data for all availability zones will be returned. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list tuples containing price and timestamp. """ @@ -1135,6 +1278,8 @@ class EC2Connection(AWSQueryConnection): params['ProductDescription'] = product_description if availability_zone: params['AvailabilityZone'] = availability_zone + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeSpotPriceHistory', params, [('item', SpotPriceHistory)], verb='POST') @@ -1152,7 +1297,7 @@ class EC2Connection(AWSQueryConnection): instance_profile_name=None, security_group_ids=None, ebs_optimized=False, - network_interfaces=None): + network_interfaces=None, dry_run=False): """ Request instances on the spot market at a particular price. @@ -1259,6 +1404,9 @@ class EC2Connection(AWSQueryConnection): :param network_interfaces: A list of :class:`boto.ec2.networkinterface.NetworkInterfaceSpecification` + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: Reservation :return: The :class:`boto.ec2.spotinstancerequest.SpotInstanceRequest` associated with the request for machines @@ -1324,38 +1472,51 @@ class EC2Connection(AWSQueryConnection): params['%s.EbsOptimized' % ls] = 'true' if network_interfaces: network_interfaces.build_list_params(params, prefix=ls + '.') + if dry_run: + params['DryRun'] = 'true' return self.get_list('RequestSpotInstances', params, [('item', SpotInstanceRequest)], verb='POST') - def cancel_spot_instance_requests(self, request_ids): + def cancel_spot_instance_requests(self, request_ids, dry_run=False): """ Cancel the specified Spot Instance Requests. :type request_ids: list :param request_ids: A list of strings of the Request IDs to terminate + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of the instances terminated """ params = {} if request_ids: self.build_list_params(params, request_ids, 'SpotInstanceRequestId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('CancelSpotInstanceRequests', params, [('item', Instance)], verb='POST') - def get_spot_datafeed_subscription(self): + def get_spot_datafeed_subscription(self, dry_run=False): """ Return the current spot instance data feed subscription associated with this account, if any. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.spotdatafeedsubscription.SpotDatafeedSubscription` :return: The datafeed subscription object or None """ + params = {} + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeSpotDatafeedSubscription', - None, SpotDatafeedSubscription, verb='POST') + params, SpotDatafeedSubscription, verb='POST') - def create_spot_datafeed_subscription(self, bucket, prefix): + def create_spot_datafeed_subscription(self, bucket, prefix, dry_run=False): """ Create a spot instance datafeed subscription for this account. @@ -1369,29 +1530,40 @@ class EC2Connection(AWSQueryConnection): :param prefix: An optional prefix that will be pre-pended to all data files written to the bucket. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.spotdatafeedsubscription.SpotDatafeedSubscription` :return: The datafeed subscription object or None """ params = {'Bucket': bucket} if prefix: params['Prefix'] = prefix + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateSpotDatafeedSubscription', params, SpotDatafeedSubscription, verb='POST') - def delete_spot_datafeed_subscription(self): + def delete_spot_datafeed_subscription(self, dry_run=False): """ Delete the current spot instance data feed subscription associated with this account + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ + params = {} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteSpotDatafeedSubscription', - None, verb='POST') + params, verb='POST') # Zone methods - def get_all_zones(self, zones=None, filters=None): + def get_all_zones(self, zones=None, filters=None, dry_run=False): """ Get all Availability Zones associated with the current region. @@ -1410,6 +1582,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list of :class:`boto.ec2.zone.Zone` :return: The requested Zone objects """ @@ -1418,12 +1593,15 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, zones, 'ZoneName') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeAvailabilityZones', params, [('item', Zone)], verb='POST') # Address methods - def get_all_addresses(self, addresses=None, filters=None, allocation_ids=None): + def get_all_addresses(self, addresses=None, filters=None, + allocation_ids=None, dry_run=False): """ Get all EIP's associated with the current credentials. @@ -1447,6 +1625,9 @@ class EC2Connection(AWSQueryConnection): present, only the Addresses associated with the given allocation IDs will be returned. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list of :class:`boto.ec2.address.Address` :return: The requested Address objects """ @@ -1457,9 +1638,11 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, allocation_ids, 'AllocationId') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeAddresses', params, [('item', Address)], verb='POST') - def allocate_address(self, domain=None): + def allocate_address(self, domain=None, dry_run=False): """ Allocate a new Elastic IP address and associate it with your account. @@ -1468,6 +1651,9 @@ class EC2Connection(AWSQueryConnection): will be allocated to VPC . Will return address object with allocation_id. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.address.Address` :return: The newly allocated Address """ @@ -1476,12 +1662,15 @@ class EC2Connection(AWSQueryConnection): if domain is not None: params['Domain'] = domain + if dry_run: + params['DryRun'] = 'true' + return self.get_object('AllocateAddress', params, Address, verb='POST') def assign_private_ip_addresses(self, network_interface_id=None, private_ip_addresses=None, secondary_private_ip_address_count=None, - allow_reassignment=False): + allow_reassignment=False, dry_run=False): """ Assigns one or more secondary private IP addresses to a network interface in Amazon VPC. @@ -1504,6 +1693,9 @@ class EC2Connection(AWSQueryConnection): that is already assigned to another network interface or instance to be reassigned to the specified network interface. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -1522,11 +1714,15 @@ class EC2Connection(AWSQueryConnection): if allow_reassignment: params['AllowReassignment'] = 'true' + if dry_run: + params['DryRun'] = 'true' + return self.get_status('AssignPrivateIpAddresses', params, verb='POST') def associate_address(self, instance_id=None, public_ip=None, allocation_id=None, network_interface_id=None, - private_ip_address=None, allow_reassociation=False): + private_ip_address=None, allow_reassociation=False, + dry_run=False): """ Associate an Elastic IP address with a currently running instance. This requires one of ``public_ip`` or ``allocation_id`` depending @@ -1559,6 +1755,9 @@ class EC2Connection(AWSQueryConnection): or instance to be re-associated with the specified instance or interface. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -1579,9 +1778,13 @@ class EC2Connection(AWSQueryConnection): if allow_reassociation: params['AllowReassociation'] = 'true' + if dry_run: + params['DryRun'] = 'true' + return self.get_status('AssociateAddress', params, verb='POST') - def disassociate_address(self, public_ip=None, association_id=None): + def disassociate_address(self, public_ip=None, association_id=None, + dry_run=False): """ Disassociate an Elastic IP address from a currently running instance. @@ -1591,6 +1794,9 @@ class EC2Connection(AWSQueryConnection): :type association_id: string :param association_id: The association ID for a VPC based elastic ip. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -1601,9 +1807,13 @@ class EC2Connection(AWSQueryConnection): elif association_id is not None: params['AssociationId'] = association_id + if dry_run: + params['DryRun'] = 'true' + return self.get_status('DisassociateAddress', params, verb='POST') - def release_address(self, public_ip=None, allocation_id=None): + def release_address(self, public_ip=None, allocation_id=None, + dry_run=False): """ Free up an Elastic IP address. Pass a public IP address to release an EC2 Elastic IP address and an AllocationId to @@ -1623,6 +1833,9 @@ class EC2Connection(AWSQueryConnection): :type allocation_id: string :param allocation_id: The Allocation ID for VPC elastic IPs. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -1633,10 +1846,13 @@ class EC2Connection(AWSQueryConnection): elif allocation_id is not None: params['AllocationId'] = allocation_id + if dry_run: + params['DryRun'] = 'true' + return self.get_status('ReleaseAddress', params, verb='POST') def unassign_private_ip_addresses(self, network_interface_id=None, - private_ip_addresses=None): + private_ip_addresses=None, dry_run=False): """ Unassigns one or more secondary private IP addresses from a network interface in Amazon VPC. @@ -1649,6 +1865,9 @@ class EC2Connection(AWSQueryConnection): :param private_ip_addresses: Specifies the secondary private IP addresses that you want to unassign from the network interface. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -1661,12 +1880,15 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, private_ip_addresses, 'PrivateIpAddress') + if dry_run: + params['DryRun'] = 'true' + return self.get_status('UnassignPrivateIpAddresses', params, verb='POST') # Volume methods - def get_all_volumes(self, volume_ids=None, filters=None): + def get_all_volumes(self, volume_ids=None, filters=None, dry_run=False): """ Get all Volumes associated with the current credentials. @@ -1685,6 +1907,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list of :class:`boto.ec2.volume.Volume` :return: The requested Volume objects """ @@ -1693,12 +1918,14 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, volume_ids, 'VolumeId') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeVolumes', params, [('item', Volume)], verb='POST') def get_all_volume_status(self, volume_ids=None, max_results=None, next_token=None, - filters=None): + filters=None, dry_run=False): """ Retrieve the status of one or more volumes. @@ -1723,6 +1950,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of volume status. """ @@ -1735,10 +1965,12 @@ class EC2Connection(AWSQueryConnection): params['NextToken'] = next_token if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeVolumeStatus', params, VolumeStatusSet, verb='POST') - def enable_volume_io(self, volume_id): + def enable_volume_io(self, volume_id, dry_run=False): """ Enables I/O operations for a volume that had I/O operations disabled because the data on the volume was potentially inconsistent. @@ -1746,14 +1978,19 @@ class EC2Connection(AWSQueryConnection): :type volume_id: str :param volume_id: The ID of the volume. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'VolumeId': volume_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('EnableVolumeIO', params, verb='POST') def get_volume_attribute(self, volume_id, - attribute='autoEnableIO'): + attribute='autoEnableIO', dry_run=False): """ Describes attribute of the volume. @@ -1765,14 +2002,20 @@ class EC2Connection(AWSQueryConnection): * autoEnableIO + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list of :class:`boto.ec2.volume.VolumeAttribute` :return: The requested Volume attribute """ params = {'VolumeId': volume_id, 'Attribute': attribute} + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeVolumeAttribute', params, VolumeAttribute, verb='POST') - def modify_volume_attribute(self, volume_id, attribute, new_value): + def modify_volume_attribute(self, volume_id, attribute, new_value, + dry_run=False): """ Changes an attribute of an Volume. @@ -1785,14 +2028,20 @@ class EC2Connection(AWSQueryConnection): :type new_value: string :param new_value: The new value of the attribute. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'VolumeId': volume_id} if attribute == 'AutoEnableIO': params['AutoEnableIO.Value'] = new_value + if dry_run: + params['DryRun'] = 'true' return self.get_status('ModifyVolumeAttribute', params, verb='POST') def create_volume(self, size, zone, snapshot=None, - volume_type=None, iops=None): + volume_type=None, iops=None, dry_run=False): """ Create a new EBS Volume. @@ -1813,6 +2062,10 @@ class EC2Connection(AWSQueryConnection): :type iops: int :param iops: The provisioned IOPs you want to associate with this volume. (optional) + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ if isinstance(zone, Zone): zone = zone.name @@ -1827,22 +2080,29 @@ class EC2Connection(AWSQueryConnection): params['VolumeType'] = volume_type if iops: params['Iops'] = str(iops) + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateVolume', params, Volume, verb='POST') - def delete_volume(self, volume_id): + def delete_volume(self, volume_id, dry_run=False): """ Delete an EBS volume. :type volume_id: str :param volume_id: The ID of the volume to be delete. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'VolumeId': volume_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteVolume', params, verb='POST') - def attach_volume(self, volume_id, instance_id, device): + def attach_volume(self, volume_id, instance_id, device, dry_run=False): """ Attach an EBS volume to an EC2 instance. @@ -1857,16 +2117,21 @@ class EC2Connection(AWSQueryConnection): :param device: The device on the instance through which the volume will be exposted (e.g. /dev/sdh) + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'InstanceId': instance_id, 'VolumeId': volume_id, 'Device': device} + if dry_run: + params['DryRun'] = 'true' return self.get_status('AttachVolume', params, verb='POST') def detach_volume(self, volume_id, instance_id=None, - device=None, force=False): + device=None, force=False, dry_run=False): """ Detach an EBS volume from an EC2 instance. @@ -1891,6 +2156,9 @@ class EC2Connection(AWSQueryConnection): use this option, you must perform file system check and repair procedures. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -1901,13 +2169,15 @@ class EC2Connection(AWSQueryConnection): params['Device'] = device if force: params['Force'] = 'true' + if dry_run: + params['DryRun'] = 'true' return self.get_status('DetachVolume', params, verb='POST') # Snapshot methods def get_all_snapshots(self, snapshot_ids=None, owner=None, restorable_by=None, - filters=None): + filters=None, dry_run=False): """ Get all EBS Snapshots associated with the current credentials. @@ -1938,6 +2208,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list of :class:`boto.ec2.snapshot.Snapshot` :return: The requested Snapshot objects """ @@ -1950,10 +2223,12 @@ class EC2Connection(AWSQueryConnection): params['RestorableBy'] = restorable_by if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeSnapshots', params, [('item', Snapshot)], verb='POST') - def create_snapshot(self, volume_id, description=None): + def create_snapshot(self, volume_id, description=None, dry_run=False): """ Create a snapshot of an existing EBS Volume. @@ -1964,26 +2239,38 @@ class EC2Connection(AWSQueryConnection): :param description: A description of the snapshot. Limited to 255 characters. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.snapshot.Snapshot` :return: The created Snapshot object """ params = {'VolumeId': volume_id} if description: params['Description'] = description[0:255] + if dry_run: + params['DryRun'] = 'true' snapshot = self.get_object('CreateSnapshot', params, Snapshot, verb='POST') - volume = self.get_all_volumes([volume_id])[0] + volume = self.get_all_volumes([volume_id], dry_run=dry_run)[0] volume_name = volume.tags.get('Name') if volume_name: snapshot.add_tag('Name', volume_name) return snapshot - def delete_snapshot(self, snapshot_id): + def delete_snapshot(self, snapshot_id, dry_run=False): + """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = {'SnapshotId': snapshot_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteSnapshot', params, verb='POST') def copy_snapshot(self, source_region, source_snapshot_id, - description=None): + description=None, dry_run=False): """ Copies a point-in-time snapshot of an Amazon Elastic Block Store (Amazon EBS) volume and stores it in Amazon Simple Storage Service @@ -2002,6 +2289,9 @@ class EC2Connection(AWSQueryConnection): :type description: str :param description: A description of the new Amazon EBS snapshot. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: str :return: The snapshot ID @@ -2012,6 +2302,8 @@ class EC2Connection(AWSQueryConnection): } if description is not None: params['Description'] = description + if dry_run: + params['DryRun'] = 'true' snapshot = self.get_object('CopySnapshot', params, Snapshot, verb='POST') return snapshot.id @@ -2170,7 +2462,8 @@ class EC2Connection(AWSQueryConnection): snap_found_for_this_time_period = False def get_snapshot_attribute(self, snapshot_id, - attribute='createVolumePermission'): + attribute='createVolumePermission', + dry_run=False): """ Get information about an attribute of a snapshot. Only one attribute can be specified per call. @@ -2183,18 +2476,24 @@ class EC2Connection(AWSQueryConnection): * createVolumePermission + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list of :class:`boto.ec2.snapshotattribute.SnapshotAttribute` :return: The requested Snapshot attribute """ params = {'Attribute': attribute} if snapshot_id: params['SnapshotId'] = snapshot_id + if dry_run: + params['DryRun'] = 'true' return self.get_object('DescribeSnapshotAttribute', params, SnapshotAttribute, verb='POST') def modify_snapshot_attribute(self, snapshot_id, attribute='createVolumePermission', - operation='add', user_ids=None, groups=None): + operation='add', user_ids=None, groups=None, + dry_run=False): """ Changes an attribute of an image. @@ -2216,6 +2515,9 @@ class EC2Connection(AWSQueryConnection): :param groups: The groups to add/remove attributes. The only valid value at this time is 'all'. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'SnapshotId': snapshot_id, 'Attribute': attribute, @@ -2224,10 +2526,13 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, user_ids, 'UserId') if groups: self.build_list_params(params, groups, 'UserGroup') + if dry_run: + params['DryRun'] = 'true' return self.get_status('ModifySnapshotAttribute', params, verb='POST') def reset_snapshot_attribute(self, snapshot_id, - attribute='createVolumePermission'): + attribute='createVolumePermission', + dry_run=False): """ Resets an attribute of a snapshot to its default value. @@ -2237,16 +2542,21 @@ class EC2Connection(AWSQueryConnection): :type attribute: string :param attribute: The attribute to reset + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: Whether the operation succeeded or not """ params = {'SnapshotId': snapshot_id, 'Attribute': attribute} + if dry_run: + params['DryRun'] = 'true' return self.get_status('ResetSnapshotAttribute', params, verb='POST') # Keypair methods - def get_all_key_pairs(self, keynames=None, filters=None): + def get_all_key_pairs(self, keynames=None, filters=None, dry_run=False): """ Get all key pairs associated with your account. @@ -2262,6 +2572,9 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.keypair.KeyPair` """ @@ -2270,28 +2583,36 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, keynames, 'KeyName') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeKeyPairs', params, [('item', KeyPair)], verb='POST') - def get_key_pair(self, keyname): + def get_key_pair(self, keyname, dry_run=False): """ Convenience method to retrieve a specific keypair (KeyPair). :type keyname: string :param keyname: The name of the keypair to retrieve + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.keypair.KeyPair` :return: The KeyPair specified or None if it is not found """ try: - return self.get_all_key_pairs(keynames=[keyname])[0] + return self.get_all_key_pairs( + keynames=[keyname], + dry_run=dry_run + )[0] except self.ResponseError, e: if e.code == 'InvalidKeyPair.NotFound': return None else: raise - def create_key_pair(self, key_name): + def create_key_pair(self, key_name, dry_run=False): """ Create a new key pair for your account. This will create the key pair within the region you @@ -2300,25 +2621,36 @@ class EC2Connection(AWSQueryConnection): :type key_name: string :param key_name: The name of the new keypair + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.keypair.KeyPair` :return: The newly created :class:`boto.ec2.keypair.KeyPair`. The material attribute of the new KeyPair object will contain the the unencrypted PEM encoded RSA private key. """ params = {'KeyName': key_name} + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateKeyPair', params, KeyPair, verb='POST') - def delete_key_pair(self, key_name): + def delete_key_pair(self, key_name, dry_run=False): """ Delete a key pair from your account. :type key_name: string :param key_name: The name of the keypair to delete + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'KeyName': key_name} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteKeyPair', params, verb='POST') - def import_key_pair(self, key_name, public_key_material): + def import_key_pair(self, key_name, public_key_material, dry_run=False): """ mports the public key from an RSA key pair that you created with a third-party tool. @@ -2345,6 +2677,9 @@ class EC2Connection(AWSQueryConnection): the public key material before sending it to AWS. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.keypair.KeyPair` :return: A :class:`boto.ec2.keypair.KeyPair` object representing the newly imported key pair. This object will contain only @@ -2353,12 +2688,14 @@ class EC2Connection(AWSQueryConnection): public_key_material = base64.b64encode(public_key_material) params = {'KeyName': key_name, 'PublicKeyMaterial': public_key_material} + if dry_run: + params['DryRun'] = 'true' return self.get_object('ImportKeyPair', params, KeyPair, verb='POST') # SecurityGroup methods def get_all_security_groups(self, groupnames=None, group_ids=None, - filters=None): + filters=None, dry_run=False): """ Get all security groups associated with your account in a region. @@ -2381,6 +2718,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.securitygroup.SecurityGroup` """ @@ -2391,11 +2731,13 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, group_ids, 'GroupId') if filters is not None: self.build_filter_params(params, filters) - + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeSecurityGroups', params, [('item', SecurityGroup)], verb='POST') - def create_security_group(self, name, description, vpc_id=None): + def create_security_group(self, name, description, vpc_id=None, + dry_run=False): """ Create a new security group for your account. This will create the security group within the region you @@ -2411,6 +2753,9 @@ class EC2Connection(AWSQueryConnection): :param vpc_id: The ID of the VPC to create the security group in, if any. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.securitygroup.SecurityGroup` :return: The newly created :class:`boto.ec2.securitygroup.SecurityGroup`. """ @@ -2420,6 +2765,9 @@ class EC2Connection(AWSQueryConnection): if vpc_id is not None: params['VpcId'] = vpc_id + if dry_run: + params['DryRun'] = 'true' + group = self.get_object('CreateSecurityGroup', params, SecurityGroup, verb='POST') group.name = name @@ -2428,7 +2776,7 @@ class EC2Connection(AWSQueryConnection): group.vpc_id = vpc_id return group - def delete_security_group(self, name=None, group_id=None): + def delete_security_group(self, name=None, group_id=None, dry_run=False): """ Delete a security group from your account. @@ -2439,6 +2787,9 @@ class EC2Connection(AWSQueryConnection): :param group_id: The ID of the security group to delete within a VPC. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful. """ @@ -2449,6 +2800,9 @@ class EC2Connection(AWSQueryConnection): elif group_id is not None: params['GroupId'] = group_id + if dry_run: + params['DryRun'] = 'true' + return self.get_status('DeleteSecurityGroup', params, verb='POST') def authorize_security_group_deprecated(self, group_name, @@ -2456,7 +2810,7 @@ class EC2Connection(AWSQueryConnection): src_security_group_owner_id=None, ip_protocol=None, from_port=None, to_port=None, - cidr_ip=None): + cidr_ip=None, dry_run=False): """ NOTE: This method uses the old-style request parameters that did not allow a port to be specified when @@ -2487,6 +2841,9 @@ class EC2Connection(AWSQueryConnection): :param to_port: The CIDR block you are providing access to. See http://goo.gl/Yj5QC + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful. """ @@ -2503,6 +2860,8 @@ class EC2Connection(AWSQueryConnection): params['ToPort'] = to_port if cidr_ip: params['CidrIp'] = cidr_ip + if dry_run: + params['DryRun'] = 'true' return self.get_status('AuthorizeSecurityGroupIngress', params) def authorize_security_group(self, group_name=None, @@ -2511,7 +2870,8 @@ class EC2Connection(AWSQueryConnection): ip_protocol=None, from_port=None, to_port=None, cidr_ip=None, group_id=None, - src_security_group_group_id=None): + src_security_group_group_id=None, + dry_run=False): """ Add a new rule to an existing security group. You need to pass in either src_security_group_name and @@ -2554,6 +2914,9 @@ class EC2Connection(AWSQueryConnection): group you are granting access to. Can be used instead of src_security_group_name + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful. """ @@ -2590,6 +2953,8 @@ class EC2Connection(AWSQueryConnection): for i, single_cidr_ip in enumerate(cidr_ip): params['IpPermissions.1.IpRanges.%d.CidrIp' % (i+1)] = \ single_cidr_ip + if dry_run: + params['DryRun'] = 'true' return self.get_status('AuthorizeSecurityGroupIngress', params, verb='POST') @@ -2600,13 +2965,18 @@ class EC2Connection(AWSQueryConnection): from_port=None, to_port=None, src_group_id=None, - cidr_ip=None): + cidr_ip=None, + dry_run=False): """ The action adds one or more egress rules to a VPC security group. Specifically, this action permits instances in a security group to send traffic to one or more destination CIDR IP address ranges, or to one or more destination security groups in the same VPC. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = { 'GroupId': group_id, @@ -2621,6 +2991,8 @@ class EC2Connection(AWSQueryConnection): params['IpPermissions.1.Groups.1.GroupId'] = src_group_id if cidr_ip is not None: params['IpPermissions.1.IpRanges.1.CidrIp'] = cidr_ip + if dry_run: + params['DryRun'] = 'true' return self.get_status('AuthorizeSecurityGroupEgress', params, verb='POST') @@ -2630,7 +3002,7 @@ class EC2Connection(AWSQueryConnection): src_security_group_owner_id=None, ip_protocol=None, from_port=None, to_port=None, - cidr_ip=None): + cidr_ip=None, dry_run=False): """ NOTE: This method uses the old-style request parameters that did not allow a port to be specified when @@ -2667,6 +3039,9 @@ class EC2Connection(AWSQueryConnection): :param to_port: The CIDR block you are revoking access to. http://goo.gl/Yj5QC + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful. """ @@ -2683,6 +3058,8 @@ class EC2Connection(AWSQueryConnection): params['ToPort'] = to_port if cidr_ip: params['CidrIp'] = cidr_ip + if dry_run: + params['DryRun'] = 'true' return self.get_status('RevokeSecurityGroupIngress', params) def revoke_security_group(self, group_name=None, @@ -2690,7 +3067,7 @@ class EC2Connection(AWSQueryConnection): src_security_group_owner_id=None, ip_protocol=None, from_port=None, to_port=None, cidr_ip=None, group_id=None, - src_security_group_group_id=None): + src_security_group_group_id=None, dry_run=False): """ Remove an existing rule from an existing security group. You need to pass in either src_security_group_name and @@ -2733,6 +3110,9 @@ class EC2Connection(AWSQueryConnection): for which you are revoking access. Can be used instead of src_security_group_name + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful. """ @@ -2763,6 +3143,8 @@ class EC2Connection(AWSQueryConnection): params['IpPermissions.1.ToPort'] = to_port if cidr_ip: params['IpPermissions.1.IpRanges.1.CidrIp'] = cidr_ip + if dry_run: + params['DryRun'] = 'true' return self.get_status('RevokeSecurityGroupIngress', params, verb='POST') @@ -2772,7 +3154,7 @@ class EC2Connection(AWSQueryConnection): from_port=None, to_port=None, src_group_id=None, - cidr_ip=None): + cidr_ip=None, dry_run=False): """ Remove an existing egress rule from an existing VPC security group. You need to pass in an ip_protocol, from_port and @@ -2801,6 +3183,9 @@ class EC2Connection(AWSQueryConnection): :param cidr_ip: The CIDR block you are revoking access to. See http://goo.gl/Yj5QC + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful. """ @@ -2818,6 +3203,8 @@ class EC2Connection(AWSQueryConnection): params['IpPermissions.1.Groups.1.GroupId'] = src_group_id if cidr_ip: params['IpPermissions.1.IpRanges.1.CidrIp'] = cidr_ip + if dry_run: + params['DryRun'] = 'true' return self.get_status('RevokeSecurityGroupEgress', params, verb='POST') @@ -2825,7 +3212,7 @@ class EC2Connection(AWSQueryConnection): # Regions # - def get_all_regions(self, region_names=None, filters=None): + def get_all_regions(self, region_names=None, filters=None, dry_run=False): """ Get all available regions for the EC2 service. @@ -2842,6 +3229,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.regioninfo.RegionInfo` """ @@ -2850,6 +3240,8 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, region_names, 'RegionName') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' regions = self.get_list('DescribeRegions', params, [('item', RegionInfo)], verb='POST') for region in regions: @@ -2873,7 +3265,8 @@ class EC2Connection(AWSQueryConnection): max_duration=None, max_instance_count=None, next_token=None, - max_results=None): + max_results=None, + dry_run=False): """ Describes Reserved Instance offerings that are available for purchase. @@ -2935,6 +3328,9 @@ class EC2Connection(AWSQueryConnection): :type max_results: int :param max_results: Maximum number of offerings to return per call. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.reservedinstance.ReservedInstancesOffering`. @@ -2971,13 +3367,15 @@ class EC2Connection(AWSQueryConnection): params['NextToken'] = next_token if max_results is not None: params['MaxResults'] = str(max_results) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeReservedInstancesOfferings', params, [('item', ReservedInstancesOffering)], verb='POST') def get_all_reserved_instances(self, reserved_instances_id=None, - filters=None): + filters=None, dry_run=False): """ Describes one or more of the Reserved Instances that you purchased. @@ -2994,6 +3392,9 @@ class EC2Connection(AWSQueryConnection): names/values is dependent on the request being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.reservedinstance.ReservedInstance` """ @@ -3003,12 +3404,15 @@ class EC2Connection(AWSQueryConnection): 'ReservedInstancesId') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeReservedInstances', params, [('item', ReservedInstance)], verb='POST') def purchase_reserved_instance_offering(self, reserved_instances_offering_id, - instance_count=1, limit_price=None): + instance_count=1, limit_price=None, + dry_run=False): """ Purchase a Reserved Instance for use with your account. ** CAUTION ** @@ -3028,6 +3432,9 @@ class EC2Connection(AWSQueryConnection): Must be a tuple of (amount, currency_code), for example: (100.0, 'USD'). + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.reservedinstance.ReservedInstance` :return: The newly created Reserved Instance """ @@ -3037,11 +3444,14 @@ class EC2Connection(AWSQueryConnection): if limit_price is not None: params['LimitPrice.Amount'] = str(limit_price[0]) params['LimitPrice.CurrencyCode'] = str(limit_price[1]) + if dry_run: + params['DryRun'] = 'true' return self.get_object('PurchaseReservedInstancesOffering', params, ReservedInstance, verb='POST') - def create_reserved_instances_listing(self, reserved_instances_id, instance_count, - price_schedules, client_token): + def create_reserved_instances_listing(self, reserved_instances_id, + instance_count, price_schedules, + client_token, dry_run=False): """Creates a new listing for Reserved Instances. Creates a new listing for Amazon EC2 Reserved Instances that will be @@ -3087,6 +3497,9 @@ class EC2Connection(AWSQueryConnection): :param client_token: Unique, case-sensitive identifier you provide to ensure idempotency of the request. Maximum 64 ASCII characters. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.reservedinstance.ReservedInstanceListing` @@ -3101,17 +3514,23 @@ class EC2Connection(AWSQueryConnection): price, term = schedule params['PriceSchedules.%s.Price' % i] = str(price) params['PriceSchedules.%s.Term' % i] = str(term) + if dry_run: + params['DryRun'] = 'true' return self.get_list('CreateReservedInstancesListing', params, [('item', ReservedInstanceListing)], verb='POST') - def cancel_reserved_instances_listing( - self, reserved_instances_listing_ids=None): + def cancel_reserved_instances_listing(self, + reserved_instances_listing_ids=None, + dry_run=False): """Cancels the specified Reserved Instance listing. :type reserved_instances_listing_ids: List of strings :param reserved_instances_listing_ids: The ID of the Reserved Instance listing to be cancelled. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.reservedinstance.ReservedInstanceListing` @@ -3121,6 +3540,8 @@ class EC2Connection(AWSQueryConnection): if reserved_instances_listing_ids is not None: self.build_list_params(params, reserved_instances_listing_ids, 'ReservedInstancesListingId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('CancelReservedInstancesListing', params, [('item', ReservedInstanceListing)], verb='POST') @@ -3128,22 +3549,27 @@ class EC2Connection(AWSQueryConnection): # Monitoring # - def monitor_instances(self, instance_ids): + def monitor_instances(self, instance_ids, dry_run=False): """ Enable CloudWatch monitoring for the supplied instances. :type instance_id: list of strings :param instance_id: The instance ids + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instanceinfo.InstanceInfo` """ params = {} self.build_list_params(params, instance_ids, 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('MonitorInstances', params, [('item', InstanceInfo)], verb='POST') - def monitor_instance(self, instance_id): + def monitor_instance(self, instance_id, dry_run=False): """ Deprecated Version, maintained for backward compatibility. Enable CloudWatch monitoring for the supplied instance. @@ -3151,27 +3577,35 @@ class EC2Connection(AWSQueryConnection): :type instance_id: string :param instance_id: The instance id + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instanceinfo.InstanceInfo` """ - return self.monitor_instances([instance_id]) + return self.monitor_instances([instance_id], dry_run=dry_run) - def unmonitor_instances(self, instance_ids): + def unmonitor_instances(self, instance_ids, dry_run=False): """ Disable CloudWatch monitoring for the supplied instance. :type instance_id: list of string :param instance_id: The instance id + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instanceinfo.InstanceInfo` """ params = {} self.build_list_params(params, instance_ids, 'InstanceId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('UnmonitorInstances', params, [('item', InstanceInfo)], verb='POST') - def unmonitor_instance(self, instance_id): + def unmonitor_instance(self, instance_id, dry_run=False): """ Deprecated Version, maintained for backward compatibility. Disable CloudWatch monitoring for the supplied instance. @@ -3179,10 +3613,13 @@ class EC2Connection(AWSQueryConnection): :type instance_id: string :param instance_id: The instance id + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.instanceinfo.InstanceInfo` """ - return self.unmonitor_instances([instance_id]) + return self.unmonitor_instances([instance_id], dry_run=dry_run) # # Bundle Windows Instances @@ -3191,7 +3628,7 @@ class EC2Connection(AWSQueryConnection): def bundle_instance(self, instance_id, s3_bucket, s3_prefix, - s3_upload_policy): + s3_upload_policy, dry_run=False): """ Bundle Windows instance. @@ -3208,6 +3645,10 @@ class EC2Connection(AWSQueryConnection): :param s3_upload_policy: Base64 encoded policy that specifies condition and permissions for Amazon EC2 to upload the user's image into Amazon S3. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'InstanceId': instance_id, @@ -3219,10 +3660,13 @@ class EC2Connection(AWSQueryConnection): params['Storage.S3.AWSAccessKeyId'] = self.aws_access_key_id signature = s3auth.sign_string(s3_upload_policy) params['Storage.S3.UploadPolicySignature'] = signature + if dry_run: + params['DryRun'] = 'true' return self.get_object('BundleInstance', params, BundleInstanceTask, verb='POST') - def get_all_bundle_tasks(self, bundle_ids=None, filters=None): + def get_all_bundle_tasks(self, bundle_ids=None, filters=None, + dry_run=False): """ Retrieve current bundling tasks. If no bundle id is specified, all tasks are retrieved. @@ -3241,38 +3685,52 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. - """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {} if bundle_ids: self.build_list_params(params, bundle_ids, 'BundleId') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeBundleTasks', params, [('item', BundleInstanceTask)], verb='POST') - def cancel_bundle_task(self, bundle_id): + def cancel_bundle_task(self, bundle_id, dry_run=False): """ Cancel a previously submitted bundle task :type bundle_id: string :param bundle_id: The identifier of the bundle task to cancel. - """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = {'BundleId': bundle_id} + if dry_run: + params['DryRun'] = 'true' return self.get_object('CancelBundleTask', params, BundleInstanceTask, verb='POST') - def get_password_data(self, instance_id): + def get_password_data(self, instance_id, dry_run=False): """ Get encrypted administrator password for a Windows instance. :type instance_id: string :param instance_id: The identifier of the instance to retrieve the password for. - """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = {'InstanceId': instance_id} + if dry_run: + params['DryRun'] = 'true' rs = self.get_object('GetPasswordData', params, ResultSet, verb='POST') return rs.passwordData @@ -3280,7 +3738,8 @@ class EC2Connection(AWSQueryConnection): # Cluster Placement Groups # - def get_all_placement_groups(self, groupnames=None, filters=None): + def get_all_placement_groups(self, groupnames=None, filters=None, + dry_run=False): """ Get all placement groups associated with your account in a region. @@ -3299,6 +3758,9 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.placementgroup.PlacementGroup` """ @@ -3307,10 +3769,12 @@ class EC2Connection(AWSQueryConnection): self.build_list_params(params, groupnames, 'GroupName') if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribePlacementGroups', params, [('item', PlacementGroup)], verb='POST') - def create_placement_group(self, name, strategy='cluster'): + def create_placement_group(self, name, strategy='cluster', dry_run=False): """ Create a new placement group for your account. This will create the placement group within the region you @@ -3323,21 +3787,32 @@ class EC2Connection(AWSQueryConnection): :param strategy: The placement strategy of the new placement group. Currently, the only acceptable value is "cluster". + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'GroupName':name, 'Strategy':strategy} + if dry_run: + params['DryRun'] = 'true' group = self.get_status('CreatePlacementGroup', params, verb='POST') return group - def delete_placement_group(self, name): + def delete_placement_group(self, name, dry_run=False): """ Delete a placement group from your account. :type key_name: string :param key_name: The name of the keypair to delete + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'GroupName':name} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeletePlacementGroup', params, verb='POST') # Tag methods @@ -3352,7 +3827,7 @@ class EC2Connection(AWSQueryConnection): params['Tag.%d.Value'%i] = value i += 1 - def get_all_tags(self, filters=None): + def get_all_tags(self, filters=None, dry_run=False): """ Retrieve all the metadata tags associated with your account. @@ -3366,16 +3841,21 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.tag.Tag` objects """ params = {} if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeTags', params, [('item', Tag)], verb='POST') - def create_tags(self, resource_ids, tags): + def create_tags(self, resource_ids, tags, dry_run=False): """ Create new metadata tags for the specified resource ids. @@ -3388,13 +3868,18 @@ class EC2Connection(AWSQueryConnection): value for that tag should be the empty string (e.g. ''). + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {} self.build_list_params(params, resource_ids, 'ResourceId') self.build_tag_param_list(params, tags) + if dry_run: + params['DryRun'] = 'true' return self.get_status('CreateTags', params, verb='POST') - def delete_tags(self, resource_ids, tags): + def delete_tags(self, resource_ids, tags, dry_run=False): """ Delete metadata tags for the specified resource ids. @@ -3410,17 +3895,22 @@ class EC2Connection(AWSQueryConnection): for the tag value, all tags with that name will be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ if isinstance(tags, list): tags = {}.fromkeys(tags, None) params = {} self.build_list_params(params, resource_ids, 'ResourceId') self.build_tag_param_list(params, tags) + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteTags', params, verb='POST') # Network Interface methods - def get_all_network_interfaces(self, filters=None): + def get_all_network_interfaces(self, filters=None, dry_run=False): """ Retrieve all of the Elastic Network Interfaces (ENI's) associated with your account. @@ -3435,17 +3925,22 @@ class EC2Connection(AWSQueryConnection): being performed. Check the EC2 API guide for details. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.ec2.networkinterface.NetworkInterface` """ params = {} if filters: self.build_filter_params(params, filters) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeNetworkInterfaces', params, [('item', NetworkInterface)], verb='POST') def create_network_interface(self, subnet_id, private_ip_address=None, - description=None, groups=None): + description=None, groups=None, dry_run=False): """ Creates a network interface in the specified subnet. @@ -3466,6 +3961,9 @@ class EC2Connection(AWSQueryConnection): This can be either a list of group ID's or a list of :class:`boto.ec2.securitygroup.SecurityGroup` objects. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: :class:`boto.ec2.networkinterface.NetworkInterface` :return: The newly created network interface. """ @@ -3482,11 +3980,13 @@ class EC2Connection(AWSQueryConnection): else: ids.append(group) self.build_list_params(params, ids, 'SecurityGroupId') + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateNetworkInterface', params, NetworkInterface, verb='POST') def attach_network_interface(self, network_interface_id, - instance_id, device_index): + instance_id, device_index, dry_run=False): """ Attaches a network interface to an instance. @@ -3500,13 +4000,20 @@ class EC2Connection(AWSQueryConnection): :type device_index: int :param device_index: The index of the device for the network interface attachment on the instance. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'NetworkInterfaceId': network_interface_id, 'InstanceId': instance_id, 'DeviceIndex': device_index} + if dry_run: + params['DryRun'] = 'true' return self.get_status('AttachNetworkInterface', params, verb='POST') - def detach_network_interface(self, attachment_id, force=False): + def detach_network_interface(self, attachment_id, force=False, + dry_run=False): """ Detaches a network interface from an instance. @@ -3516,21 +4023,31 @@ class EC2Connection(AWSQueryConnection): :type force: bool :param force: Set to true to force a detachment. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'AttachmentId': attachment_id} if force: params['Force'] = 'true' + if dry_run: + params['DryRun'] = 'true' return self.get_status('DetachNetworkInterface', params, verb='POST') - def delete_network_interface(self, network_interface_id): + def delete_network_interface(self, network_interface_id, dry_run=False): """ Delete the specified network interface. :type network_interface_id: str :param network_interface_id: The ID of the network interface to delete. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'NetworkInterfaceId': network_interface_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteNetworkInterface', params, verb='POST') def get_all_vmtypes(self): @@ -3544,7 +4061,12 @@ class EC2Connection(AWSQueryConnection): return self.get_list('DescribeVmTypes', params, [('euca:item', VmType)], verb='POST') def copy_image(self, source_region, source_image_id, name, - description=None, client_token=None): + description=None, client_token=None, dry_run=False): + """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = { 'SourceRegion': source_region, 'SourceImageId': source_image_id, @@ -3554,29 +4076,48 @@ class EC2Connection(AWSQueryConnection): params['Description'] = description if client_token is not None: params['ClientToken'] = client_token - image = self.get_object('CopyImage', params, CopyImage, + if dry_run: + params['DryRun'] = 'true' + return self.get_object('CopyImage', params, CopyImage, verb='POST') - return image - def describe_account_attributes(self, attribute_names=None): + def describe_account_attributes(self, attribute_names=None, dry_run=False): + """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = {} if attribute_names is not None: self.build_list_params(params, attribute_names, 'AttributeName') + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeAccountAttributes', params, [('item', AccountAttribute)], verb='POST') - def describe_vpc_attribute(self, vpc_id, attribute=None): + def describe_vpc_attribute(self, vpc_id, attribute=None, dry_run=False): + """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = { 'VpcId': vpc_id } if attribute is not None: params['Attribute'] = attribute - attr = self.get_object('DescribeVpcAttribute', params, + if dry_run: + params['DryRun'] = 'true' + return self.get_object('DescribeVpcAttribute', params, VPCAttribute, verb='POST') - return attr def modify_vpc_attribute(self, vpc_id, enable_dns_support=None, - enable_dns_hostnames=None): + enable_dns_hostnames=None, dry_run=False): + """ + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + + """ params = { 'VpcId': vpc_id } @@ -3586,5 +4127,6 @@ class EC2Connection(AWSQueryConnection): if enable_dns_hostnames is not None: params['EnableDnsHostnames.Value'] = ( 'true' if enable_dns_hostnames else 'false') - result = self.get_status('ModifyVpcAttribute', params, verb='POST') - return result + if dry_run: + params['DryRun'] = 'true' + return self.get_status('ModifyVpcAttribute', params, verb='POST') diff --git a/boto/ec2/ec2object.py b/boto/ec2/ec2object.py index 7756bee7..265678c6 100644 --- a/boto/ec2/ec2object.py +++ b/boto/ec2/ec2object.py @@ -15,7 +15,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -40,7 +40,7 @@ class EC2Object(object): def endElement(self, name, value, connection): setattr(self, name, value) - + class TaggedEC2Object(EC2Object): """ Any EC2 resource that can be tagged should be represented @@ -62,7 +62,7 @@ class TaggedEC2Object(EC2Object): else: return None - def add_tag(self, key, value=''): + def add_tag(self, key, value='', dry_run=False): """ Add a tag to this object. Tag's are stored by AWS and can be used to organize and filter resources. Adding a tag involves a round-trip @@ -76,12 +76,16 @@ class TaggedEC2Object(EC2Object): If you want only the tag name and no value, the value should be the empty string. """ - status = self.connection.create_tags([self.id], {key : value}) + status = self.connection.create_tags( + [self.id], + {key : value}, + dry_run=dry_run + ) if self.tags is None: self.tags = TagSet() self.tags[key] = value - def remove_tag(self, key, value=None): + def remove_tag(self, key, value=None, dry_run=False): """ Remove a tag from this object. Removing a tag involves a round-trip to the EC2 service. @@ -102,6 +106,10 @@ class TaggedEC2Object(EC2Object): tags = {key : value} else: tags = [key] - status = self.connection.delete_tags([self.id], tags) + status = self.connection.delete_tags( + [self.id], + tags, + dry_run=dry_run + ) if key in self.tags: del self.tags[key] diff --git a/boto/ec2/image.py b/boto/ec2/image.py index 376fc869..6b6d9ce9 100644 --- a/boto/ec2/image.py +++ b/boto/ec2/image.py @@ -130,7 +130,7 @@ class Image(TaggedEC2Object): def _update(self, updated): self.__dict__.update(updated.__dict__) - def update(self, validate=False): + def update(self, validate=False, dry_run=False): """ Update the image's state information by making a call to fetch the current image attributes from the service. @@ -142,7 +142,7 @@ class Image(TaggedEC2Object): raise a ValueError exception if no data is returned from EC2. """ - rs = self.connection.get_all_images([self.id]) + rs = self.connection.get_all_images([self.id], dry_run=dry_run) if len(rs) > 0: img = rs[0] if img.id == self.id: @@ -162,7 +162,7 @@ class Image(TaggedEC2Object): private_ip_address=None, placement_group=None, security_group_ids=None, additional_info=None, instance_profile_name=None, - instance_profile_arn=None, tenancy=None): + instance_profile_arn=None, tenancy=None, dry_run=False): """ Runs this instance. @@ -295,40 +295,62 @@ class Image(TaggedEC2Object): additional_info=additional_info, instance_profile_name=instance_profile_name, instance_profile_arn=instance_profile_arn, - tenancy=tenancy) - - def deregister(self, delete_snapshot=False): - return self.connection.deregister_image(self.id, delete_snapshot) - - def get_launch_permissions(self): - img_attrs = self.connection.get_image_attribute(self.id, - 'launchPermission') + tenancy=tenancy, dry_run=dry_run) + + def deregister(self, delete_snapshot=False, dry_run=False): + return self.connection.deregister_image( + self.id, + delete_snapshot, + dry_run=dry_run + ) + + def get_launch_permissions(self, dry_run=False): + img_attrs = self.connection.get_image_attribute( + self.id, + 'launchPermission', + dry_run=dry_run + ) return img_attrs.attrs - def set_launch_permissions(self, user_ids=None, group_names=None): + def set_launch_permissions(self, user_ids=None, group_names=None, + dry_run=False): return self.connection.modify_image_attribute(self.id, 'launchPermission', 'add', user_ids, - group_names) + group_names, + dry_run=dry_run) - def remove_launch_permissions(self, user_ids=None, group_names=None): + def remove_launch_permissions(self, user_ids=None, group_names=None, + dry_run=False): return self.connection.modify_image_attribute(self.id, 'launchPermission', 'remove', user_ids, - group_names) - - def reset_launch_attributes(self): - return self.connection.reset_image_attribute(self.id, - 'launchPermission') - - def get_kernel(self): - img_attrs =self.connection.get_image_attribute(self.id, 'kernel') + group_names, + dry_run=dry_run) + + def reset_launch_attributes(self, dry_run=False): + return self.connection.reset_image_attribute( + self.id, + 'launchPermission', + dry_run=dry_run + ) + + def get_kernel(self, dry_run=False): + img_attrs =self.connection.get_image_attribute( + self.id, + 'kernel', + dry_run=dry_run + ) return img_attrs.kernel - def get_ramdisk(self): - img_attrs = self.connection.get_image_attribute(self.id, 'ramdisk') + def get_ramdisk(self, dry_run=False): + img_attrs = self.connection.get_image_attribute( + self.id, + 'ramdisk', + dry_run=dry_run + ) return img_attrs.ramdisk class ImageAttribute: diff --git a/boto/ec2/instance.py b/boto/ec2/instance.py index e0137705..254fe230 100644 --- a/boto/ec2/instance.py +++ b/boto/ec2/instance.py @@ -149,9 +149,9 @@ class Reservation(EC2Object): else: setattr(self, name, value) - def stop_all(self): + def stop_all(self, dry_run=False): for instance in self.instances: - instance.stop() + instance.stop(dry_run=dry_run) class Instance(TaggedEC2Object): @@ -406,7 +406,7 @@ class Instance(TaggedEC2Object): def _update(self, updated): self.__dict__.update(updated.__dict__) - def update(self, validate=False): + def update(self, validate=False, dry_run=False): """ Update the instance's state information by making a call to fetch the current instance attributes from the service. @@ -418,7 +418,7 @@ class Instance(TaggedEC2Object): raise a ValueError exception if no data is returned from EC2. """ - rs = self.connection.get_all_reservations([self.id]) + rs = self.connection.get_all_reservations([self.id], dry_run=dry_run) if len(rs) > 0: r = rs[0] for i in r.instances: @@ -428,15 +428,15 @@ class Instance(TaggedEC2Object): raise ValueError('%s is not a valid Instance ID' % self.id) return self.state - def terminate(self): + def terminate(self, dry_run=False): """ Terminate the instance """ - rs = self.connection.terminate_instances([self.id]) + rs = self.connection.terminate_instances([self.id], dry_run=dry_run) if len(rs) > 0: self._update(rs[0]) - def stop(self, force=False): + def stop(self, force=False, dry_run=False): """ Stop the instance @@ -446,34 +446,38 @@ class Instance(TaggedEC2Object): :rtype: list :return: A list of the instances stopped """ - rs = self.connection.stop_instances([self.id], force) + rs = self.connection.stop_instances([self.id], force, dry_run=dry_run) if len(rs) > 0: self._update(rs[0]) - def start(self): + def start(self, dry_run=False): """ Start the instance. """ - rs = self.connection.start_instances([self.id]) + rs = self.connection.start_instances([self.id], dry_run=dry_run) if len(rs) > 0: self._update(rs[0]) - def reboot(self): - return self.connection.reboot_instances([self.id]) + def reboot(self, dry_run=False): + return self.connection.reboot_instances([self.id], dry_run=dry_run) - def get_console_output(self): + def get_console_output(self, dry_run=False): """ Retrieves the console output for the instance. :rtype: :class:`boto.ec2.instance.ConsoleOutput` :return: The console output as a ConsoleOutput object """ - return self.connection.get_console_output(self.id) + return self.connection.get_console_output(self.id, dry_run=dry_run) - def confirm_product(self, product_code): - return self.connection.confirm_product_instance(self.id, product_code) + def confirm_product(self, product_code, dry_run=False): + return self.connection.confirm_product_instance( + self.id, + product_code, + dry_run=dry_run + ) - def use_ip(self, ip_address): + def use_ip(self, ip_address, dry_run=False): """ Associates an Elastic IP to the instance. @@ -488,15 +492,19 @@ class Instance(TaggedEC2Object): if isinstance(ip_address, Address): ip_address = ip_address.public_ip - return self.connection.associate_address(self.id, ip_address) + return self.connection.associate_address( + self.id, + ip_address, + dry_run=dry_run + ) - def monitor(self): - return self.connection.monitor_instance(self.id) + def monitor(self, dry_run=False): + return self.connection.monitor_instance(self.id, dry_run=dry_run) - def unmonitor(self): - return self.connection.unmonitor_instance(self.id) + def unmonitor(self, dry_run=False): + return self.connection.unmonitor_instance(self.id, dry_run=dry_run) - def get_attribute(self, attribute): + def get_attribute(self, attribute, dry_run=False): """ Gets an attribute from this instance. @@ -521,9 +529,13 @@ class Instance(TaggedEC2Object): :return: An InstanceAttribute object representing the value of the attribute requested """ - return self.connection.get_instance_attribute(self.id, attribute) + return self.connection.get_instance_attribute( + self.id, + attribute, + dry_run=dry_run + ) - def modify_attribute(self, attribute, value): + def modify_attribute(self, attribute, value, dry_run=False): """ Changes an attribute of this instance @@ -546,10 +558,14 @@ class Instance(TaggedEC2Object): :rtype: bool :return: Whether the operation succeeded or not """ - return self.connection.modify_instance_attribute(self.id, attribute, - value) - - def reset_attribute(self, attribute): + return self.connection.modify_instance_attribute( + self.id, + attribute, + value, + dry_run=dry_run + ) + + def reset_attribute(self, attribute, dry_run=False): """ Resets an attribute of this instance to its default value. @@ -560,12 +576,14 @@ class Instance(TaggedEC2Object): :rtype: bool :return: Whether the operation succeeded or not """ - return self.connection.reset_instance_attribute(self.id, attribute) - - def create_image( - self, name, - description=None, no_reboot=False - ): + return self.connection.reset_instance_attribute( + self.id, + attribute, + dry_run=dry_run + ) + + def create_image(self, name, description=None, no_reboot=False, + dry_run=False): """ Will create an AMI from the instance in the running or stopped state. @@ -587,7 +605,13 @@ class Instance(TaggedEC2Object): :rtype: string :return: The new image id """ - return self.connection.create_image(self.id, name, description, no_reboot) + return self.connection.create_image( + self.id, + name, + description, + no_reboot, + dry_run=dry_run + ) class ConsoleOutput: diff --git a/boto/ec2/keypair.py b/boto/ec2/keypair.py index 65c95908..c15a0984 100644 --- a/boto/ec2/keypair.py +++ b/boto/ec2/keypair.py @@ -14,7 +14,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -28,7 +28,7 @@ from boto.ec2.ec2object import EC2Object from boto.exception import BotoClientError class KeyPair(EC2Object): - + def __init__(self, connection=None): EC2Object.__init__(self, connection) self.name = None @@ -48,20 +48,20 @@ class KeyPair(EC2Object): else: setattr(self, name, value) - def delete(self): + def delete(self, dry_run=False): """ Delete the KeyPair. - + :rtype: bool :return: True if successful, otherwise False. """ - return self.connection.delete_key_pair(self.name) + return self.connection.delete_key_pair(self.name, dry_run=dry_run) def save(self, directory_path): """ Save the material (the unencrypted PEM encoded RSA private key) of a newly created KeyPair to a local file. - + :type directory_path: string :param directory_path: The fully qualified path to the directory in which the keypair will be saved. The @@ -71,7 +71,7 @@ class KeyPair(EC2Object): name already exists in the directory, an exception will be raised and the old file will not be overwritten. - + :rtype: bool :return: True if successful. """ @@ -88,7 +88,7 @@ class KeyPair(EC2Object): else: raise BotoClientError('KeyPair contains no material') - def copy_to_region(self, region): + def copy_to_region(self, region, dry_run=False): """ Create a new key pair of the same new in another region. Note that the new key pair will use a different ssh @@ -106,7 +106,7 @@ class KeyPair(EC2Object): raise BotoClientError('Unable to copy to the same Region') conn_params = self.connection.get_params() rconn = region.connect(**conn_params) - kp = rconn.create_key_pair(self.name) + kp = rconn.create_key_pair(self.name, dry_run=dry_run) return kp diff --git a/boto/ec2/networkinterface.py b/boto/ec2/networkinterface.py index 6ae7fe60..98368050 100644 --- a/boto/ec2/networkinterface.py +++ b/boto/ec2/networkinterface.py @@ -166,8 +166,11 @@ class NetworkInterface(TaggedEC2Object): else: setattr(self, name, value) - def delete(self): - return self.connection.delete_network_interface(self.id) + def delete(self, dry_run=False): + return self.connection.delete_network_interface( + self.id, + dry_run=dry_run + ) class PrivateIPAddress(object): diff --git a/boto/ec2/placementgroup.py b/boto/ec2/placementgroup.py index e1bbea62..79bd4c46 100644 --- a/boto/ec2/placementgroup.py +++ b/boto/ec2/placementgroup.py @@ -14,7 +14,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -25,7 +25,7 @@ from boto.ec2.ec2object import EC2Object from boto.exception import BotoClientError class PlacementGroup(EC2Object): - + def __init__(self, connection=None, name=None, strategy=None, state=None): EC2Object.__init__(self, connection) self.name = name @@ -45,7 +45,10 @@ class PlacementGroup(EC2Object): else: setattr(self, name, value) - def delete(self): - return self.connection.delete_placement_group(self.name) + def delete(self, dry_run=False): + return self.connection.delete_placement_group( + self.name, + dry_run=dry_run + ) diff --git a/boto/ec2/reservedinstance.py b/boto/ec2/reservedinstance.py index d92f1686..725ff2d2 100644 --- a/boto/ec2/reservedinstance.py +++ b/boto/ec2/reservedinstance.py @@ -89,8 +89,12 @@ class ReservedInstancesOffering(EC2Object): print '\tUsage Price=%s' % self.usage_price print '\tDescription=%s' % self.description - def purchase(self, instance_count=1): - return self.connection.purchase_reserved_instance_offering(self.id, instance_count) + def purchase(self, instance_count=1, dry_run=False): + return self.connection.purchase_reserved_instance_offering( + self.id, + instance_count, + dry_run=dry_run + ) class RecurringCharge(object): diff --git a/boto/ec2/securitygroup.py b/boto/ec2/securitygroup.py index 731c2390..3d93faa2 100644 --- a/boto/ec2/securitygroup.py +++ b/boto/ec2/securitygroup.py @@ -82,14 +82,21 @@ class SecurityGroup(TaggedEC2Object): else: setattr(self, name, value) - def delete(self): + def delete(self, dry_run=False): if self.vpc_id: - return self.connection.delete_security_group(group_id=self.id) + return self.connection.delete_security_group( + group_id=self.id, + dry_run=dry_run + ) else: - return self.connection.delete_security_group(self.name) + return self.connection.delete_security_group( + self.name, + dry_run=dry_run + ) def add_rule(self, ip_protocol, from_port, to_port, - src_group_name, src_group_owner_id, cidr_ip, src_group_group_id): + src_group_name, src_group_owner_id, cidr_ip, + src_group_group_id, dry_run=False): """ Add a rule to the SecurityGroup object. Note that this method only changes the local version of the object. No information @@ -100,10 +107,17 @@ class SecurityGroup(TaggedEC2Object): rule.from_port = from_port rule.to_port = to_port self.rules.append(rule) - rule.add_grant(src_group_name, src_group_owner_id, cidr_ip, src_group_group_id) + rule.add_grant( + src_group_name, + src_group_owner_id, + cidr_ip, + src_group_group_id, + dry_run=dry_run + ) def remove_rule(self, ip_protocol, from_port, to_port, - src_group_name, src_group_owner_id, cidr_ip, src_group_group_id): + src_group_name, src_group_owner_id, cidr_ip, + src_group_group_id, dry_run=False): """ Remove a rule to the SecurityGroup object. Note that this method only changes the local version of the object. No information @@ -122,12 +136,12 @@ class SecurityGroup(TaggedEC2Object): if grant.cidr_ip == cidr_ip: target_grant = grant if target_grant: - rule.grants.remove(target_grant) + rule.grants.remove(target_grant, dry_run=dry_run) if len(rule.grants) == 0: - self.rules.remove(target_rule) + self.rules.remove(target_rule, dry_run=dry_run) def authorize(self, ip_protocol=None, from_port=None, to_port=None, - cidr_ip=None, src_group=None): + cidr_ip=None, src_group=None, dry_run=False): """ Add a new rule to this security group. You need to pass in either src_group_name @@ -182,17 +196,19 @@ class SecurityGroup(TaggedEC2Object): to_port, cidr_ip, group_id, - src_group_group_id) + src_group_group_id, + dry_run=dry_run) if status: if not isinstance(cidr_ip, list): cidr_ip = [cidr_ip] for single_cidr_ip in cidr_ip: self.add_rule(ip_protocol, from_port, to_port, src_group_name, - src_group_owner_id, single_cidr_ip, src_group_group_id) + src_group_owner_id, single_cidr_ip, + src_group_group_id, dry_run=dry_run) return status def revoke(self, ip_protocol=None, from_port=None, to_port=None, - cidr_ip=None, src_group=None): + cidr_ip=None, src_group=None, dry_run=False): group_name = None if not self.vpc_id: group_name = self.name @@ -220,13 +236,15 @@ class SecurityGroup(TaggedEC2Object): to_port, cidr_ip, group_id, - src_group_group_id) + src_group_group_id, + dry_run=dry_run) if status: self.remove_rule(ip_protocol, from_port, to_port, src_group_name, - src_group_owner_id, cidr_ip, src_group_group_id) + src_group_owner_id, cidr_ip, src_group_group_id, + dry_run=dry_run) return status - def copy_to_region(self, region, name=None): + def copy_to_region(self, region, name=None, dry_run=False): """ Create a copy of this security group in another region. Note that the new security group will be a separate entity @@ -247,7 +265,11 @@ class SecurityGroup(TaggedEC2Object): raise BotoClientError('Unable to copy to the same Region') conn_params = self.connection.get_params() rconn = region.connect(**conn_params) - sg = rconn.create_security_group(name or self.name, self.description) + sg = rconn.create_security_group( + name or self.name, + self.description, + dry_run=dry_run + ) source_groups = [] for rule in self.rules: for grant in rule.grants: @@ -255,13 +277,14 @@ class SecurityGroup(TaggedEC2Object): if grant_nom: if grant_nom not in source_groups: source_groups.append(grant_nom) - sg.authorize(None, None, None, None, grant) + sg.authorize(None, None, None, None, grant, + dry_run=dry_run) else: sg.authorize(rule.ip_protocol, rule.from_port, rule.to_port, - grant.cidr_ip) + grant.cidr_ip, dry_run=dry_run) return sg - def instances(self): + def instances(self, dry_run=False): """ Find all of the current instances that are running within this security group. @@ -272,11 +295,13 @@ class SecurityGroup(TaggedEC2Object): rs = [] if self.vpc_id: rs.extend(self.connection.get_all_reservations( - filters={'instance.group-id': self.id} + filters={'instance.group-id': self.id}, + dry_run=dry_run )) else: rs.extend(self.connection.get_all_reservations( - filters={'group-id': self.id} + filters={'group-id': self.id}, + dry_run=dry_run )) instances = [i for r in rs for i in r.instances] return instances diff --git a/boto/ec2/snapshot.py b/boto/ec2/snapshot.py index d2c4b2b9..24bffe6b 100644 --- a/boto/ec2/snapshot.py +++ b/boto/ec2/snapshot.py @@ -15,7 +15,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -27,9 +27,9 @@ from boto.ec2.ec2object import TaggedEC2Object from boto.ec2.zone import Zone class Snapshot(TaggedEC2Object): - + AttrName = 'createVolumePermission' - + def __init__(self, connection=None): TaggedEC2Object.__init__(self, connection) self.id = None @@ -72,7 +72,7 @@ class Snapshot(TaggedEC2Object): self.progress = updated.progress self.status = updated.status - def update(self, validate=False): + def update(self, validate=False, dry_run=False): """ Update the data associated with this snapshot by querying EC2. @@ -83,39 +83,49 @@ class Snapshot(TaggedEC2Object): raise a ValueError exception if no data is returned from EC2. """ - rs = self.connection.get_all_snapshots([self.id]) + rs = self.connection.get_all_snapshots([self.id], dry_run=dry_run) if len(rs) > 0: self._update(rs[0]) elif validate: raise ValueError('%s is not a valid Snapshot ID' % self.id) return self.progress - - def delete(self): - return self.connection.delete_snapshot(self.id) - def get_permissions(self): - attrs = self.connection.get_snapshot_attribute(self.id, self.AttrName) + def delete(self, dry_run=False): + return self.connection.delete_snapshot(self.id, dry_run=dry_run) + + def get_permissions(self, dry_run=False): + attrs = self.connection.get_snapshot_attribute( + self.id, + self.AttrName, + dry_run=dry_run + ) return attrs.attrs - def share(self, user_ids=None, groups=None): + def share(self, user_ids=None, groups=None, dry_run=False): return self.connection.modify_snapshot_attribute(self.id, self.AttrName, 'add', user_ids, - groups) + groups, + dry_run=dry_run) - def unshare(self, user_ids=None, groups=None): + def unshare(self, user_ids=None, groups=None, dry_run=False): return self.connection.modify_snapshot_attribute(self.id, self.AttrName, 'remove', user_ids, - groups) - - def reset_permissions(self): - return self.connection.reset_snapshot_attribute(self.id, - self.AttrName) - - def create_volume(self, zone, size=None, volume_type=None, iops=None): + groups, + dry_run=dry_run) + + def reset_permissions(self, dry_run=False): + return self.connection.reset_snapshot_attribute( + self.id, + self.AttrName, + dry_run=dry_run + ) + + def create_volume(self, zone, size=None, volume_type=None, iops=None, + dry_run=False): """ Create a new EBS Volume from this Snapshot @@ -136,7 +146,14 @@ class Snapshot(TaggedEC2Object): """ if isinstance(zone, Zone): zone = zone.name - return self.connection.create_volume(size, zone, self.id, volume_type, iops) + return self.connection.create_volume( + size, + zone, + self.id, + volume_type, + iops, + dry_run=dry_run + ) class SnapshotAttribute: @@ -167,4 +184,4 @@ class SnapshotAttribute: setattr(self, name, value) - + diff --git a/boto/ec2/spotdatafeedsubscription.py b/boto/ec2/spotdatafeedsubscription.py index 9b820a3e..1b30a99f 100644 --- a/boto/ec2/spotdatafeedsubscription.py +++ b/boto/ec2/spotdatafeedsubscription.py @@ -26,7 +26,7 @@ from boto.ec2.ec2object import EC2Object from boto.ec2.spotinstancerequest import SpotInstanceStateFault class SpotDatafeedSubscription(EC2Object): - + def __init__(self, connection=None, owner_id=None, bucket=None, prefix=None, state=None,fault=None): EC2Object.__init__(self, connection) @@ -45,7 +45,7 @@ class SpotDatafeedSubscription(EC2Object): return self.fault else: return None - + def endElement(self, name, value, connection): if name == 'ownerId': self.owner_id = value @@ -58,6 +58,8 @@ class SpotDatafeedSubscription(EC2Object): else: setattr(self, name, value) - def delete(self): - return self.connection.delete_spot_datafeed_subscription() + def delete(self, dry_run=False): + return self.connection.delete_spot_datafeed_subscription( + dry_run=dry_run + ) diff --git a/boto/ec2/spotinstancerequest.py b/boto/ec2/spotinstancerequest.py index 54fba1d6..c5b8bc95 100644 --- a/boto/ec2/spotinstancerequest.py +++ b/boto/ec2/spotinstancerequest.py @@ -184,5 +184,8 @@ class SpotInstanceRequest(TaggedEC2Object): else: setattr(self, name, value) - def cancel(self): - self.connection.cancel_spot_instance_requests([self.id]) + def cancel(self, dry_run=False): + self.connection.cancel_spot_instance_requests( + [self.id], + dry_run=dry_run + ) diff --git a/boto/ec2/volume.py b/boto/ec2/volume.py index bc5befc7..2127b260 100644 --- a/boto/ec2/volume.py +++ b/boto/ec2/volume.py @@ -98,7 +98,7 @@ class Volume(TaggedEC2Object): def _update(self, updated): self.__dict__.update(updated.__dict__) - def update(self, validate=False): + def update(self, validate=False, dry_run=False): """ Update the data associated with this volume by querying EC2. @@ -110,7 +110,10 @@ class Volume(TaggedEC2Object): returned from EC2. """ # Check the resultset since Eucalyptus ignores the volumeId param - unfiltered_rs = self.connection.get_all_volumes([self.id]) + unfiltered_rs = self.connection.get_all_volumes( + [self.id], + dry_run=dry_run + ) rs = [x for x in unfiltered_rs if x.id == self.id] if len(rs) > 0: self._update(rs[0]) @@ -118,16 +121,16 @@ class Volume(TaggedEC2Object): raise ValueError('%s is not a valid Volume ID' % self.id) return self.status - def delete(self): + def delete(self, dry_run=False): """ Delete this EBS volume. :rtype: bool :return: True if successful """ - return self.connection.delete_volume(self.id) + return self.connection.delete_volume(self.id, dry_run=dry_run) - def attach(self, instance_id, device): + def attach(self, instance_id, device, dry_run=False): """ Attach this EBS volume to an EC2 instance. @@ -142,9 +145,14 @@ class Volume(TaggedEC2Object): :rtype: bool :return: True if successful """ - return self.connection.attach_volume(self.id, instance_id, device) - - def detach(self, force=False): + return self.connection.attach_volume( + self.id, + instance_id, + device, + dry_run=dry_run + ) + + def detach(self, force=False, dry_run=False): """ Detach this EBS volume from an EC2 instance. @@ -167,10 +175,15 @@ class Volume(TaggedEC2Object): device = None if self.attach_data: device = self.attach_data.device - return self.connection.detach_volume(self.id, instance_id, - device, force) - - def create_snapshot(self, description=None): + return self.connection.detach_volume( + self.id, + instance_id, + device, + force, + dry_run=dry_run + ) + + def create_snapshot(self, description=None, dry_run=False): """ Create a snapshot of this EBS Volume. @@ -181,7 +194,11 @@ class Volume(TaggedEC2Object): :rtype: :class:`boto.ec2.snapshot.Snapshot` :return: The created Snapshot object """ - return self.connection.create_snapshot(self.id, description) + return self.connection.create_snapshot( + self.id, + description, + dry_run=dry_run + ) def volume_state(self): """ @@ -198,7 +215,7 @@ class Volume(TaggedEC2Object): state = self.attach_data.status return state - def snapshots(self, owner=None, restorable_by=None): + def snapshots(self, owner=None, restorable_by=None, dry_run=False): """ Get all snapshots related to this volume. Note that this requires that all available snapshots for the account be retrieved from EC2 @@ -221,8 +238,11 @@ class Volume(TaggedEC2Object): :return: The requested Snapshot objects """ - rs = self.connection.get_all_snapshots(owner=owner, - restorable_by=restorable_by) + rs = self.connection.get_all_snapshots( + owner=owner, + restorable_by=restorable_by, + dry_run=dry_run + ) mine = [] for snap in rs: if snap.volume_id == self.id: diff --git a/boto/vpc/__init__.py b/boto/vpc/__init__.py index 0e5eb3fb..24a93a74 100644 --- a/boto/vpc/__init__.py +++ b/boto/vpc/__init__.py @@ -83,7 +83,7 @@ class VPCConnection(EC2Connection): # VPC methods - def get_all_vpcs(self, vpc_ids=None, filters=None): + def get_all_vpcs(self, vpc_ids=None, filters=None, dry_run=False): """ Retrieve information about your VPCs. You can filter results to return information only about those VPCs that match your search @@ -102,6 +102,9 @@ class VPCConnection(EC2Connection): * *cidrBlock* - a list CIDR blocks of the VPC * *dhcpOptionsId* - a list of IDs of a set of DHCP options + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpc.vpc.VPC` """ @@ -110,37 +113,49 @@ class VPCConnection(EC2Connection): self.build_list_params(params, vpc_ids, 'VpcId') if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeVpcs', params, [('item', VPC)]) - def create_vpc(self, cidr_block): + def create_vpc(self, cidr_block, dry_run=False): """ Create a new Virtual Private Cloud. :type cidr_block: str :param cidr_block: A valid CIDR block + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created VPC :return: A :class:`boto.vpc.vpc.VPC` object """ params = {'CidrBlock' : cidr_block} + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateVpc', params, VPC) - def delete_vpc(self, vpc_id): + def delete_vpc(self, vpc_id, dry_run=False): """ Delete a Virtual Private Cloud. :type vpc_id: str :param vpc_id: The ID of the vpc to be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'VpcId': vpc_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteVpc', params) def modify_vpc_attribute(self, vpc_id, enable_dns_support=None, - enable_dns_hostnames=None): + enable_dns_hostnames=None, dry_run=False): """ Modifies the specified attribute of the specified VPC. You can only modify one attribute at a time. @@ -157,6 +172,10 @@ class VPCConnection(EC2Connection): provided for the instances launched in this VPC. You can only set this attribute to ``true`` if EnableDnsSupport is also ``true``. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {'VpcId': vpc_id} if enable_dns_support is not None: @@ -169,11 +188,14 @@ class VPCConnection(EC2Connection): params['EnableDnsHostnames.Value'] = 'true' else: params['EnableDnsHostnames.Value'] = 'false' + if dry_run: + params['DryRun'] = 'true' return self.get_status('ModifyVpcAttribute', params) # Route Tables - def get_all_route_tables(self, route_table_ids=None, filters=None): + def get_all_route_tables(self, route_table_ids=None, filters=None, + dry_run=False): """ Retrieve information about your routing tables. You can filter results to return information only about those route tables that match your @@ -188,6 +210,9 @@ class VPCConnection(EC2Connection): :param filters: A list of tuples containing filters. Each tuple consists of a filter key and a filter value. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpc.routetable.RouteTable` """ @@ -196,10 +221,12 @@ class VPCConnection(EC2Connection): self.build_list_params(params, route_table_ids, "RouteTableId") if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeRouteTables', params, [('item', RouteTable)]) - def associate_route_table(self, route_table_id, subnet_id): + def associate_route_table(self, route_table_id, subnet_id, dry_run=False): """ Associates a route table with a specific subnet. @@ -209,6 +236,9 @@ class VPCConnection(EC2Connection): :type subnet_id: str :param subnet_id: The ID of the subnet to associate with. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: str :return: The ID of the association created """ @@ -216,11 +246,12 @@ class VPCConnection(EC2Connection): 'RouteTableId': route_table_id, 'SubnetId': subnet_id } - + if dry_run: + params['DryRun'] = 'true' result = self.get_object('AssociateRouteTable', params, ResultSet) return result.associationId - def disassociate_route_table(self, association_id): + def disassociate_route_table(self, association_id, dry_run=False): """ Removes an association from a route table. This will cause all subnets that would've used this association to now use the main routing @@ -229,40 +260,55 @@ class VPCConnection(EC2Connection): :type association_id: str :param association_id: The ID of the association to disassociate. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = { 'AssociationId': association_id } + if dry_run: + params['DryRun'] = 'true' return self.get_status('DisassociateRouteTable', params) - def create_route_table(self, vpc_id): + def create_route_table(self, vpc_id, dry_run=False): """ Creates a new route table. :type vpc_id: str :param vpc_id: The VPC ID to associate this route table with. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created route table :return: A :class:`boto.vpc.routetable.RouteTable` object """ params = { 'VpcId': vpc_id } + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateRouteTable', params, RouteTable) - def delete_route_table(self, route_table_id): + def delete_route_table(self, route_table_id, dry_run=False): """ Delete a route table. :type route_table_id: str :param route_table_id: The ID of the route table to delete. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = { 'RouteTableId': route_table_id } + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteRouteTable', params) def create_route(self, route_table_id, destination_cidr_block, - gateway_id=None, instance_id=None): + gateway_id=None, instance_id=None, dry_run=False): """ Creates a new route in the route table within a VPC. The route's target can be either a gateway attached to the VPC or a NAT instance in the @@ -281,6 +327,9 @@ class VPCConnection(EC2Connection): :type instance_id: str :param instance_id: The ID of a NAT instance in your VPC. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -293,11 +342,14 @@ class VPCConnection(EC2Connection): params['GatewayId'] = gateway_id elif instance_id is not None: params['InstanceId'] = instance_id + if dry_run: + params['DryRun'] = 'true' return self.get_status('CreateRoute', params) def replace_route(self, route_table_id, destination_cidr_block, - gateway_id=None, instance_id=None, interface_id=None): + gateway_id=None, instance_id=None, interface_id=None, + dry_run=False): """ Replaces an existing route within a route table in a VPC. @@ -317,6 +369,9 @@ class VPCConnection(EC2Connection): :type interface_id: str :param interface_id: Allows routing to network interface attachments. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -331,10 +386,13 @@ class VPCConnection(EC2Connection): params['InstanceId'] = instance_id elif interface_id is not None: params['NetworkInterfaceId'] = interface_id + if dry_run: + params['DryRun'] = 'true' return self.get_status('ReplaceRoute', params) - def delete_route(self, route_table_id, destination_cidr_block): + def delete_route(self, route_table_id, destination_cidr_block, + dry_run=False): """ Deletes a route from a route table within a VPC. @@ -345,6 +403,9 @@ class VPCConnection(EC2Connection): :param destination_cidr_block: The CIDR address block used for destination match. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -352,13 +413,14 @@ class VPCConnection(EC2Connection): 'RouteTableId': route_table_id, 'DestinationCidrBlock': destination_cidr_block } - + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteRoute', params) # Internet Gateways def get_all_internet_gateways(self, internet_gateway_ids=None, - filters=None): + filters=None, dry_run=False): """ Get a list of internet gateways. You can filter results to return information about only those gateways that you're interested in. @@ -369,6 +431,10 @@ class VPCConnection(EC2Connection): :type filters: list of tuples :param filters: A list of tuples containing filters. Each tuple consists of a filter key and a filter value. + + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + """ params = {} @@ -377,32 +443,46 @@ class VPCConnection(EC2Connection): 'InternetGatewayId') if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeInternetGateways', params, [('item', InternetGateway)]) - def create_internet_gateway(self): + def create_internet_gateway(self, dry_run=False): """ Creates an internet gateway for VPC. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: Newly created internet gateway. :return: `boto.vpc.internetgateway.InternetGateway` """ - return self.get_object('CreateInternetGateway', {}, InternetGateway) + params = {} + if dry_run: + params['DryRun'] = 'true' + return self.get_object('CreateInternetGateway', params, InternetGateway) - def delete_internet_gateway(self, internet_gateway_id): + def delete_internet_gateway(self, internet_gateway_id, dry_run=False): """ Deletes an internet gateway from the VPC. :type internet_gateway_id: str :param internet_gateway_id: The ID of the internet gateway to delete. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: Bool :return: True if successful """ params = { 'InternetGatewayId': internet_gateway_id } + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteInternetGateway', params) - def attach_internet_gateway(self, internet_gateway_id, vpc_id): + def attach_internet_gateway(self, internet_gateway_id, vpc_id, + dry_run=False): """ Attach an internet gateway to a specific VPC. @@ -412,6 +492,9 @@ class VPCConnection(EC2Connection): :type vpc_id: str :param vpc_id: The ID of the VPC to attach to. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: Bool :return: True if successful """ @@ -419,10 +502,12 @@ class VPCConnection(EC2Connection): 'InternetGatewayId': internet_gateway_id, 'VpcId': vpc_id } - + if dry_run: + params['DryRun'] = 'true' return self.get_status('AttachInternetGateway', params) - def detach_internet_gateway(self, internet_gateway_id, vpc_id): + def detach_internet_gateway(self, internet_gateway_id, vpc_id, + dry_run=False): """ Detach an internet gateway from a specific VPC. @@ -432,6 +517,9 @@ class VPCConnection(EC2Connection): :type vpc_id: str :param vpc_id: The ID of the VPC to attach to. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: Bool :return: True if successful """ @@ -439,13 +527,14 @@ class VPCConnection(EC2Connection): 'InternetGatewayId': internet_gateway_id, 'VpcId': vpc_id } - + if dry_run: + params['DryRun'] = 'true' return self.get_status('DetachInternetGateway', params) # Customer Gateways def get_all_customer_gateways(self, customer_gateway_ids=None, - filters=None): + filters=None, dry_run=False): """ Retrieve information about your CustomerGateways. You can filter results to return information only about those CustomerGateways that @@ -467,6 +556,9 @@ class VPCConnection(EC2Connection): - *ipAddress* the IP address of customer gateway's internet-routable external inteface + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpc.customergateway.CustomerGateway` """ @@ -477,10 +569,13 @@ class VPCConnection(EC2Connection): if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' + return self.get_list('DescribeCustomerGateways', params, [('item', CustomerGateway)]) - def create_customer_gateway(self, type, ip_address, bgp_asn): + def create_customer_gateway(self, type, ip_address, bgp_asn, dry_run=False): """ Create a new Customer Gateway @@ -495,30 +590,41 @@ class VPCConnection(EC2Connection): :param bgp_asn: Customer gateway's Border Gateway Protocol (BGP) Autonomous System Number (ASN) + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created CustomerGateway :return: A :class:`boto.vpc.customergateway.CustomerGateway` object """ params = {'Type' : type, 'IpAddress' : ip_address, 'BgpAsn' : bgp_asn} + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateCustomerGateway', params, CustomerGateway) - def delete_customer_gateway(self, customer_gateway_id): + def delete_customer_gateway(self, customer_gateway_id, dry_run=False): """ Delete a Customer Gateway. :type customer_gateway_id: str :param customer_gateway_id: The ID of the customer_gateway to be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'CustomerGatewayId': customer_gateway_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteCustomerGateway', params) # VPN Gateways - def get_all_vpn_gateways(self, vpn_gateway_ids=None, filters=None): + def get_all_vpn_gateways(self, vpn_gateway_ids=None, filters=None, + dry_run=False): """ Retrieve information about your VpnGateways. You can filter results to return information only about those VpnGateways that match your search @@ -539,6 +645,9 @@ class VPCConnection(EC2Connection): - *availabilityZone*, a list of Availability zones the VPN gateway is in. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpc.customergateway.VpnGateway` """ @@ -547,10 +656,12 @@ class VPCConnection(EC2Connection): self.build_list_params(params, vpn_gateway_ids, 'VpnGatewayId') if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeVpnGateways', params, [('item', VpnGateway)]) - def create_vpn_gateway(self, type, availability_zone=None): + def create_vpn_gateway(self, type, availability_zone=None, dry_run=False): """ Create a new Vpn Gateway @@ -560,28 +671,38 @@ class VPCConnection(EC2Connection): :type availability_zone: str :param availability_zone: The Availability Zone where you want the VPN gateway. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created VpnGateway :return: A :class:`boto.vpc.vpngateway.VpnGateway` object """ params = {'Type' : type} if availability_zone: params['AvailabilityZone'] = availability_zone + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateVpnGateway', params, VpnGateway) - def delete_vpn_gateway(self, vpn_gateway_id): + def delete_vpn_gateway(self, vpn_gateway_id, dry_run=False): """ Delete a Vpn Gateway. :type vpn_gateway_id: str :param vpn_gateway_id: The ID of the vpn_gateway to be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'VpnGatewayId': vpn_gateway_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteVpnGateway', params) - def attach_vpn_gateway(self, vpn_gateway_id, vpc_id): + def attach_vpn_gateway(self, vpn_gateway_id, vpc_id, dry_run=False): """ Attaches a VPN gateway to a VPC. @@ -591,16 +712,21 @@ class VPCConnection(EC2Connection): :type vpc_id: str :param vpc_id: The ID of the VPC you want to attach the gateway to. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: An attachment :return: a :class:`boto.vpc.vpngateway.Attachment` """ params = {'VpnGatewayId': vpn_gateway_id, 'VpcId' : vpc_id} + if dry_run: + params['DryRun'] = 'true' return self.get_object('AttachVpnGateway', params, Attachment) # Subnets - def get_all_subnets(self, subnet_ids=None, filters=None): + def get_all_subnets(self, subnet_ids=None, filters=None, dry_run=False): """ Retrieve information about your Subnets. You can filter results to return information only about those Subnets that match your search @@ -623,6 +749,9 @@ class VPCConnection(EC2Connection): the subnet is in. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpc.subnet.Subnet` """ @@ -631,9 +760,12 @@ class VPCConnection(EC2Connection): self.build_list_params(params, subnet_ids, 'SubnetId') if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeSubnets', params, [('item', Subnet)]) - def create_subnet(self, vpc_id, cidr_block, availability_zone=None): + def create_subnet(self, vpc_id, cidr_block, availability_zone=None, + dry_run=False): """ Create a new Subnet @@ -646,6 +778,9 @@ class VPCConnection(EC2Connection): :type availability_zone: str :param availability_zone: The AZ you want the subnet in + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created Subnet :return: A :class:`boto.vpc.customergateway.Subnet` object """ @@ -653,43 +788,55 @@ class VPCConnection(EC2Connection): 'CidrBlock' : cidr_block} if availability_zone: params['AvailabilityZone'] = availability_zone + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateSubnet', params, Subnet) - def delete_subnet(self, subnet_id): + def delete_subnet(self, subnet_id, dry_run=False): """ Delete a subnet. :type subnet_id: str :param subnet_id: The ID of the subnet to be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'SubnetId': subnet_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteSubnet', params) # DHCP Options - def get_all_dhcp_options(self, dhcp_options_ids=None): + def get_all_dhcp_options(self, dhcp_options_ids=None, dry_run=False): """ Retrieve information about your DhcpOptions. :type dhcp_options_ids: list :param dhcp_options_ids: A list of strings with the desired DhcpOption ID's + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpc.dhcpoptions.DhcpOptions` """ params = {} if dhcp_options_ids: self.build_list_params(params, dhcp_options_ids, 'DhcpOptionsId') + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeDhcpOptions', params, [('item', DhcpOptions)]) def create_dhcp_options(self, domain_name=None, domain_name_servers=None, ntp_servers=None, netbios_name_servers=None, - netbios_node_type=None): + netbios_node_type=None, dry_run=False): """ Create a new DhcpOption @@ -718,6 +865,9 @@ class VPCConnection(EC2Connection): only use 2 at this time (broadcast and multicast are currently not supported). + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created DhcpOption :return: A :class:`boto.vpc.customergateway.DhcpOption` object """ @@ -753,23 +903,30 @@ class VPCConnection(EC2Connection): if netbios_node_type: key_counter = insert_option(params, 'netbios-node-type', netbios_node_type) + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateDhcpOptions', params, DhcpOptions) - def delete_dhcp_options(self, dhcp_options_id): + def delete_dhcp_options(self, dhcp_options_id, dry_run=False): """ Delete a DHCP Options :type dhcp_options_id: str :param dhcp_options_id: The ID of the DHCP Options to be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'DhcpOptionsId': dhcp_options_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteDhcpOptions', params) - def associate_dhcp_options(self, dhcp_options_id, vpc_id): + def associate_dhcp_options(self, dhcp_options_id, vpc_id, dry_run=False): """ Associate a set of Dhcp Options with a VPC. @@ -779,16 +936,22 @@ class VPCConnection(EC2Connection): :type vpc_id: str :param vpc_id: The ID of the VPC. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'DhcpOptionsId': dhcp_options_id, 'VpcId' : vpc_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('AssociateDhcpOptions', params) # VPN Connection - def get_all_vpn_connections(self, vpn_connection_ids=None, filters=None): + def get_all_vpn_connections(self, vpn_connection_ids=None, filters=None, + dry_run=False): """ Retrieve information about your VPN_CONNECTIONs. You can filter results to return information only about those VPN_CONNECTIONs that match your search @@ -811,6 +974,9 @@ class VPCConnection(EC2Connection): - *vpnGatewayId*, a list of IDs of the VPN gateway associated with the VPN connection + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: list :return: A list of :class:`boto.vpn_connection.vpnconnection.VpnConnection` """ @@ -820,10 +986,13 @@ class VPCConnection(EC2Connection): 'Vpn_ConnectionId') if filters: self.build_filter_params(params, dict(filters)) + if dry_run: + params['DryRun'] = 'true' return self.get_list('DescribeVpnConnections', params, [('item', VpnConnection)]) - def create_vpn_connection(self, type, customer_gateway_id, vpn_gateway_id): + def create_vpn_connection(self, type, customer_gateway_id, vpn_gateway_id, + dry_run=False): """ Create a new VPN Connection. @@ -837,28 +1006,39 @@ class VPCConnection(EC2Connection): :type vpn_gateway_id: str :param vpn_gateway_id: The ID of the VPN gateway. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: The newly created VpnConnection :return: A :class:`boto.vpc.vpnconnection.VpnConnection` object """ params = {'Type' : type, 'CustomerGatewayId' : customer_gateway_id, 'VpnGatewayId' : vpn_gateway_id} + if dry_run: + params['DryRun'] = 'true' return self.get_object('CreateVpnConnection', params, VpnConnection) - def delete_vpn_connection(self, vpn_connection_id): + def delete_vpn_connection(self, vpn_connection_id, dry_run=False): """ Delete a VPN Connection. :type vpn_connection_id: str :param vpn_connection_id: The ID of the vpn_connection to be deleted. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ params = {'VpnConnectionId': vpn_connection_id} + if dry_run: + params['DryRun'] = 'true' return self.get_status('DeleteVpnConnection', params) - def disable_vgw_route_propagation(self, route_table_id, gateway_id): + def disable_vgw_route_propagation(self, route_table_id, gateway_id, + dry_run=False): """ Disables a virtual private gateway (VGW) from propagating routes to the routing tables of an Amazon VPC. @@ -869,6 +1049,9 @@ class VPCConnection(EC2Connection): :type gateway_id: str :param gateway_id: The ID of the virtual private gateway. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -876,9 +1059,12 @@ class VPCConnection(EC2Connection): 'RouteTableId': route_table_id, 'GatewayId': gateway_id, } - self.get_status('DisableVgwRoutePropagation', params) + if dry_run: + params['DryRun'] = 'true' + return self.get_status('DisableVgwRoutePropagation', params) - def enable_vgw_route_propagation(self, route_table_id, gateway_id): + def enable_vgw_route_propagation(self, route_table_id, gateway_id, + dry_run=False): """ Enables a virtual private gateway (VGW) to propagate routes to the routing tables of an Amazon VPC. @@ -889,6 +1075,9 @@ class VPCConnection(EC2Connection): :type gateway_id: str :param gateway_id: The ID of the virtual private gateway. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -896,10 +1085,12 @@ class VPCConnection(EC2Connection): 'RouteTableId': route_table_id, 'GatewayId': gateway_id, } - self.get_status('EnableVgwRoutePropagation', params) + if dry_run: + params['DryRun'] = 'true' + return self.get_status('EnableVgwRoutePropagation', params) def create_vpn_connection_route(self, destination_cidr_block, - vpn_connection_id): + vpn_connection_id, dry_run=False): """ Creates a new static route associated with a VPN connection between an existing virtual private gateway and a VPN customer gateway. The static @@ -913,6 +1104,9 @@ class VPCConnection(EC2Connection): :type vpn_connection_id: str :param vpn_connection_id: The ID of the VPN connection. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -920,10 +1114,12 @@ class VPCConnection(EC2Connection): 'DestinationCidrBlock': destination_cidr_block, 'VpnConnectionId': vpn_connection_id, } - self.get_status('CreateVpnConnectionRoute', params) + if dry_run: + params['DryRun'] = 'true' + return self.get_status('CreateVpnConnectionRoute', params) def delete_vpn_connection_route(self, destination_cidr_block, - vpn_connection_id): + vpn_connection_id, dry_run=False): """ Deletes a static route associated with a VPN connection between an existing virtual private gateway and a VPN customer gateway. The static @@ -937,6 +1133,9 @@ class VPCConnection(EC2Connection): :type vpn_connection_id: str :param vpn_connection_id: The ID of the VPN connection. + :type dry_run: bool + :param dry_run: Set to True if the operation should not actually run. + :rtype: bool :return: True if successful """ @@ -944,4 +1143,6 @@ class VPCConnection(EC2Connection): 'DestinationCidrBlock': destination_cidr_block, 'VpnConnectionId': vpn_connection_id, } - self.get_status('DeleteVpnConnectionRoute', params) + if dry_run: + params['DryRun'] = 'true' + return self.get_status('DeleteVpnConnectionRoute', params) diff --git a/boto/vpc/vpc.py b/boto/vpc/vpc.py index 8fdaa62f..2eb480d1 100644 --- a/boto/vpc/vpc.py +++ b/boto/vpc/vpc.py @@ -72,8 +72,11 @@ class VPC(TaggedEC2Object): def _update(self, updated): self.__dict__.update(updated.__dict__) - def update(self, validate=False): - vpc_list = self.connection.get_all_vpcs([self.id]) + def update(self, validate=False, dry_run=False): + vpc_list = self.connection.get_all_vpcs( + [self.id], + dry_run=dry_run + ) if len(vpc_list): updated_vpc = vpc_list[0] self._update(updated_vpc) diff --git a/boto/vpc/vpnconnection.py b/boto/vpc/vpnconnection.py index aa49c36a..c36492f5 100644 --- a/boto/vpc/vpnconnection.py +++ b/boto/vpc/vpnconnection.py @@ -197,5 +197,8 @@ class VpnConnection(TaggedEC2Object): else: setattr(self, name, value) - def delete(self): - return self.connection.delete_vpn_connection(self.id) + def delete(self, dry_run=False): + return self.connection.delete_vpn_connection( + self.id, + dry_run=dry_run + ) diff --git a/boto/vpc/vpngateway.py b/boto/vpc/vpngateway.py index 83b912ef..fe476d93 100644 --- a/boto/vpc/vpngateway.py +++ b/boto/vpc/vpngateway.py @@ -14,7 +14,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -33,7 +33,7 @@ class Attachment(object): def startElement(self, name, attrs, connection): pass - + def endElement(self, name, value, connection): if name == 'vpcId': self.vpc_id = value @@ -41,7 +41,7 @@ class Attachment(object): self.state = value else: setattr(self, name, value) - + class VpnGateway(TaggedEC2Object): def __init__(self, connection=None): @@ -63,7 +63,7 @@ class VpnGateway(TaggedEC2Object): att = Attachment() self.attachments.append(att) return att - + def endElement(self, name, value, connection): if name == 'vpnGatewayId': self.id = value @@ -78,6 +78,10 @@ class VpnGateway(TaggedEC2Object): else: setattr(self, name, value) - def attach(self, vpc_id): - return self.connection.attach_vpn_gateway(self.id, vpc_id) + def attach(self, vpc_id, dry_run=False): + return self.connection.attach_vpn_gateway( + self.id, + vpc_id, + dry_run=dry_run + ) diff --git a/tests/integration/ec2/test_connection.py b/tests/integration/ec2/test_connection.py index ef1080b0..fc7137ab 100644 --- a/tests/integration/ec2/test_connection.py +++ b/tests/integration/ec2/test_connection.py @@ -32,9 +32,10 @@ import socket from nose.plugins.attrib import attr from boto.ec2.connection import EC2Connection +from boto.exception import EC2ResponseError -class EC2ConnectionTest (unittest.TestCase): +class EC2ConnectionTest(unittest.TestCase): ec2 = True @attr('notdefault') @@ -190,3 +191,51 @@ class EC2ConnectionTest (unittest.TestCase): assert l[0].product_codes[0] == demo_paid_ami_product_code print '--- tests completed ---' + + def test_dry_run(self): + c = EC2Connection() + dry_run_msg = 'Request would have succeeded, but DryRun flag is set.' + + try: + rs = c.get_all_images(dry_run=True) + self.fail("Should have gotten an exception") + except EC2ResponseError, e: + self.assertTrue(dry_run_msg in str(e)) + + try: + rs = c.run_instances( + image_id='ami-a0cd60c9', + instance_type='m1.small', + dry_run=True + ) + self.fail("Should have gotten an exception") + except EC2ResponseError, e: + self.assertTrue(dry_run_msg in str(e)) + + # Need an actual instance for the rest of this... + rs = c.run_instances( + image_id='ami-a0cd60c9', + instance_type='m1.small' + ) + time.sleep(120) + + try: + rs = c.stop_instances( + instance_ids=[rs.instances[0].id], + dry_run=True + ) + self.fail("Should have gotten an exception") + except EC2ResponseError, e: + self.assertTrue(dry_run_msg in str(e)) + + try: + rs = c.terminate_instances( + instance_ids=[rs.instances[0].id], + dry_run=True + ) + self.fail("Should have gotten an exception") + except EC2ResponseError, e: + self.assertTrue(dry_run_msg in str(e)) + + # And kill it. + rs.instances[0].terminate() -- cgit v1.2.1