Repository: boto/boto

Issue:
EC2 UserData encoding fix
I was trying to pass some user data while creating a new instance with the EC2Connection's `run_instances`, but kept receiving a `400 Bad Request` error.

After a bit of digging, it turned out to be because the passed user data is Base64 encoded (as it needs to be), but the Base64 string needs to be UTF-8 to be passed as part of the request parameters. `b64encode` outputs a byte string, so this fix just required adding a `decode('UTF-8')` to the end of things.



Discussion:
Thanks! The patch itself looks fine. However, to get it merged, it'll need tests added to make sure we don't regress in the future. If you (or someone else) has time to add them, that'd be great. Otherwise, it'll need to wait until a core dev has time to add them.


Resolving patch:
diff --git a/boto/ec2/autoscale/__init__.py b/boto/ec2/autoscale/__init__.py
--- a/boto/ec2/autoscale/__init__.py
+++ b/boto/ec2/autoscale/__init__.py
@@ -222,7 +222,7 @@ def create_launch_configuration(self, launch_config):
         if launch_config.key_name:
             params['KeyName'] = launch_config.key_name
         if launch_config.user_data:
-            params['UserData'] = base64.b64encode(launch_config.user_data)
+            params['UserData'] = base64.b64encode(launch_config.user_data).decode('utf-8')
         if launch_config.kernel_id:
             params['KernelId'] = launch_config.kernel_id
         if launch_config.ramdisk_id:
diff --git a/boto/ec2/connection.py b/boto/ec2/connection.py
--- a/boto/ec2/connection.py
+++ b/boto/ec2/connection.py
@@ -900,7 +900,7 @@ def run_instances(self, image_id, min_count=1, max_count=1,
                     l.append(group)
             self.build_list_params(params, l, 'SecurityGroup')
         if user_data:
-            params['UserData'] = base64.b64encode(user_data)
+            params['UserData'] = base64.b64encode(user_data).decode('utf-8')
         if addressing_type:
             params['AddressingType'] = addressing_type
         if instance_type:


Test patch:
diff --git a/tests/unit/ec2/autoscale/test_group.py b/tests/unit/ec2/autoscale/test_group.py
--- a/tests/unit/ec2/autoscale/test_group.py
+++ b/tests/unit/ec2/autoscale/test_group.py
@@ -21,6 +21,7 @@
 # IN THE SOFTWARE.
 #
 
+import base64
 from datetime import datetime
 
 from tests.unit import unittest
@@ -356,6 +357,7 @@ def test_launch_config(self):
                 name='launch_config',
                 image_id='123456',
                 instance_type = 'm1.large',
+                user_data = '#!/bin/bash',
                 security_groups = ['group1', 'group2'],
                 spot_price='price',
                 block_device_mappings = [bdm],
@@ -378,6 +380,7 @@ def test_launch_config(self):
             'EbsOptimized': 'false',
             'LaunchConfigurationName': 'launch_config',
             'ImageId': '123456',
+            'UserData': base64.b64encode('#!/bin/bash').decode('utf-8'),
             'InstanceMonitoring.Enabled': 'false',
             'InstanceType': 'm1.large',
             'SecurityGroups.member.1': 'group1',
diff --git a/tests/unit/ec2/test_instance.py b/tests/unit/ec2/test_instance.py
--- a/tests/unit/ec2/test_instance.py
+++ b/tests/unit/ec2/test_instance.py
@@ -1,11 +1,13 @@
 #!/usr/bin/env python
 
+import base64
 from tests.unit import unittest
 from tests.unit import AWSMockServiceTestCase
 
 import mock
 
 from boto.ec2.connection import EC2Connection
+from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
 
 DESCRIBE_INSTANCE_VPC = r"""<?xml version="1.0" encoding="UTF-8"?>
 <DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2012-10-01/">
@@ -207,6 +209,41 @@ def testIAMInstanceProfileParsedCorrectly(self):
              'id': 'iamid'})
 
 
+class TestRunInstances(AWSMockServiceTestCase):
+    connection_class = EC2Connection
+
+    def default_body(self):
+        # This is a dummy response
+        return """
+        <DescribeLaunchConfigurationsResponse>
+        </DescribeLaunchConfigurationsResponse>
+        """
+
+    def test_run_instances_user_data(self):
+        self.set_http_response(status_code=200)
+
+        response = self.service_connection.run_instances(
+            image_id='123456',
+            instance_type='m1.large',
+            security_groups=['group1', 'group2'],
+            user_data = '#!/bin/bash'
+        )
+
+        self.assert_request_parameters({
+            'Action': 'RunInstances',
+            'ImageId': '123456',
+            'InstanceType': 'm1.large',
+            'UserData': base64.b64encode('#!/bin/bash').decode('utf-8'),
+            'MaxCount': 1,
+            'MinCount': 1,
+            'SecurityGroup.1': 'group1',
+            'SecurityGroup.2': 'group2',
+        }, ignore_params_values=[
+            'Version', 'AWSAccessKeyId', 'SignatureMethod', 'SignatureVersion',
+            'Timestamp'
+        ])
+
+
 class TestDescribeInstances(AWSMockServiceTestCase):
     connection_class = EC2Connection