Repository: boto/boto

Issue:
Need a way to capture the association id from boto.ec2.associate_address()
Maybe I'm reading the source/docs wrong but I don't see a way to capture the association id once an elastic IP has been associated with an instance.  I am running my instances in a VPC.  Could boto.ec2.associate_address be modified to return the association id instead of just the status?



Discussion:
This looks like a duplicate of #1893. It's just waiting for a new method to be written.


Resolving patch:
diff --git a/boto/ec2/connection.py b/boto/ec2/connection.py
--- a/boto/ec2/connection.py
+++ b/boto/ec2/connection.py
@@ -1832,6 +1832,36 @@ def assign_private_ip_addresses(self, network_interface_id=None,
 
         return self.get_status('AssignPrivateIpAddresses', params, verb='POST')
 
+    def _associate_address(self, status, instance_id=None, public_ip=None,
+                          allocation_id=None, network_interface_id=None,
+                          private_ip_address=None, allow_reassociation=False,
+                          dry_run=False):
+        params = {}
+        if instance_id is not None:
+                params['InstanceId'] = instance_id
+        elif network_interface_id is not None:
+                params['NetworkInterfaceId'] = network_interface_id
+
+        if public_ip is not None:
+            params['PublicIp'] = public_ip
+        elif allocation_id is not None:
+            params['AllocationId'] = allocation_id
+
+        if private_ip_address is not None:
+            params['PrivateIpAddress'] = private_ip_address
+
+        if allow_reassociation:
+            params['AllowReassociation'] = 'true'
+
+        if dry_run:
+            params['DryRun'] = 'true'
+
+        if status:
+            return self.get_status('AssociateAddress', params, verb='POST')
+        else:
+            return self.get_object('AssociateAddress', params, Address,
+                                   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,
@@ -1874,27 +1904,59 @@ def associate_address(self, instance_id=None, public_ip=None,
         :rtype: bool
         :return: True if successful
         """
-        params = {}
-        if instance_id is not None:
-                params['InstanceId'] = instance_id
-        elif network_interface_id is not None:
-                params['NetworkInterfaceId'] = network_interface_id
+        return self._associate_address(True, instance_id=instance_id,
+            public_ip=public_ip, allocation_id=allocation_id,
+            network_interface_id=network_interface_id,
+            private_ip_address=private_ip_address,
+            allow_reassociation=allow_reassociation, dry_run=dry_run)
 
-        if public_ip is not None:
-            params['PublicIp'] = public_ip
-        elif allocation_id is not None:
-            params['AllocationId'] = allocation_id
+    def associate_address_object(self, instance_id=None, public_ip=None,
+                          allocation_id=None, network_interface_id=None,
+                          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
+        on if you're associating a VPC address or a plain EC2 address.
 
-        if private_ip_address is not None:
-            params['PrivateIpAddress'] = private_ip_address
+        When using an Allocation ID, make sure to pass ``None`` for ``public_ip``
+        as EC2 expects a single parameter and if ``public_ip`` is passed boto
+        will preference that instead of ``allocation_id``.
 
-        if allow_reassociation:
-            params['AllowReassociation'] = 'true'
+        :type instance_id: string
+        :param instance_id: The ID of the instance
 
-        if dry_run:
-            params['DryRun'] = 'true'
+        :type public_ip: string
+        :param public_ip: The public IP address for EC2 based allocations.
+
+        :type allocation_id: string
+        :param allocation_id: The allocation ID for a VPC-based elastic IP.
+
+        :type network_interface_id: string
+        :param network_interface_id: The network interface ID to which
+            elastic IP is to be assigned to
 
-        return self.get_status('AssociateAddress', params, verb='POST')
+        :type private_ip_address: string
+        :param private_ip_address: The primary or secondary private IP address
+            to associate with the Elastic IP address.
+
+        :type allow_reassociation: bool
+        :param allow_reassociation: Specify this option to allow an Elastic IP
+            address that is already associated with another network interface
+            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: class:`boto.ec2.address.Address`
+        :return: The associated address instance
+        """
+        return self._associate_address(False, instance_id=instance_id,
+            public_ip=public_ip, allocation_id=allocation_id,
+            network_interface_id=network_interface_id,
+            private_ip_address=private_ip_address,
+            allow_reassociation=allow_reassociation, dry_run=dry_run)
 
     def disassociate_address(self, public_ip=None, association_id=None,
                              dry_run=False):


Test patch:
diff --git a/tests/unit/ec2/test_connection.py b/tests/unit/ec2/test_connection.py
--- a/tests/unit/ec2/test_connection.py
+++ b/tests/unit/ec2/test_connection.py
@@ -1355,5 +1355,50 @@ def test_switched(self):
         )
 
 
+class TestAssociateAddress(TestEC2ConnectionBase):
+    def default_body(self):
+        return """
+            <AssociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
+               <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
+               <return>true</return>
+               <associationId>eipassoc-fc5ca095</associationId>
+            </AssociateAddressResponse>
+        """
+
+    def test_associate_address(self):
+        self.set_http_response(status_code=200)
+        result = self.ec2.associate_address(instance_id='i-1234',
+                                            public_ip='192.0.2.1')
+        self.assertEqual(True, result)
+
+    def test_associate_address_object(self):
+        self.set_http_response(status_code=200)
+        result = self.ec2.associate_address_object(instance_id='i-1234',
+                                                   public_ip='192.0.2.1')
+        self.assertEqual('eipassoc-fc5ca095', result.association_id)
+
+
+class TestAssociateAddressFail(TestEC2ConnectionBase):
+    def default_body(self):
+        return """
+            <Response>
+                <Errors>
+                     <Error>
+                       <Code>InvalidInstanceID.NotFound</Code>
+                       <Message>The instance ID 'i-4cbc822a' does not exist</Message>
+                     </Error>
+                </Errors>
+                <RequestID>ea966190-f9aa-478e-9ede-cb5432daacc0</RequestID>
+                <StatusCode>Failure</StatusCode>
+            </Response>
+        """
+
+    def test_associate_address(self):
+        self.set_http_response(status_code=200)
+        result = self.ec2.associate_address(instance_id='i-1234',
+                                            public_ip='192.0.2.1')
+        self.assertEqual(False, result)
+
+
 if __name__ == '__main__':
     unittest.main()