Repository: boto/botocore
Issue: param VpcSecurityGroupIds missing in RestoreDBClusterFromSnapshot

Hi, 

I think it is a compatible issue which makes my script failed in the production, really critical.
Could you please help check it?

Since @rayluo deleted this param on Oct 2nd, in the commit: https://github.com/boto/botocore/commit/ad67c4fee319221381fa9dcfe67335c392dd23fd.
However, according to the latest API document, there is no such change, VpcSecurityGroupIds still exists: http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_RestoreDBClusterFromSnapshot.html. 

Thanks
Henry


Changed files:
botocore/compat.py, tests/unit/test_credentials.py, botocore/credentials.py

diff --git a/botocore/compat.py b/botocore/compat.py
index 308878ba0..2c0dcf9c6 100644
--- a/botocore/compat.py
+++ b/botocore/compat.py
@@ -60,6 +60,9 @@ if six.PY3:
         # changes when using getargspec with functools.partials.
         return inspect.getfullargspec(func)[2]
 
+    def unicode(s, encoding=None, errors=None):
+        # NOOP in Python 3, because every string is already unicode
+        return s
 
 else:
     from urllib import quote
@@ -108,6 +111,8 @@ else:
     def accepts_kwargs(func):
         return inspect.getargspec(func)[2]
 
+    unicode = unicode
+
 try:
     from collections import OrderedDict
 except ImportError:
diff --git a/botocore/credentials.py b/botocore/credentials.py
index 55f0c2157..bd329aac9 100644
--- a/botocore/credentials.py
+++ b/botocore/credentials.py
@@ -23,6 +23,7 @@ from dateutil.parser import parse
 from dateutil.tz import tzlocal
 
 import botocore.config
+import botocore.compat
 from botocore.compat import total_seconds
 from botocore.exceptions import UnknownCredentialError
 from botocore.exceptions import PartialCredentialsError
@@ -123,6 +124,18 @@ class Credentials(object):
             method = 'explicit'
         self.method = method
 
+        self._normalize()
+
+    def _normalize(self):
+        # Keys would sometimes (accidentally) contain non-ascii characters.
+        # It would cause a confusing UnicodeDecodeError in Python 2.
+        # We explicitly convert them into unicode to avoid such error.
+        #
+        # Eventually the service will decide whether to accept the credential.
+        # This also complies with the behavior in Python 3.
+        self.access_key = botocore.compat.unicode(self.access_key, 'utf-8')
+        self.secret_key = botocore.compat.unicode(self.secret_key, 'utf-8')
+
 
 class RefreshableCredentials(Credentials):
     """
@@ -151,6 +164,11 @@ class RefreshableCredentials(Credentials):
         self._expiry_time = expiry_time
         self._time_fetcher = time_fetcher
         self.method = method
+        self._normalize()
+
+    def _normalize(self):
+        self._access_key = botocore.compat.unicode(self._access_key, 'utf-8')
+        self._secret_key = botocore.compat.unicode(self._secret_key, 'utf-8')
 
     @classmethod
     def create_from_metadata(cls, metadata, refresh_using, method):
@@ -227,6 +245,7 @@ class RefreshableCredentials(Credentials):
         self.token = data['token']
         self._expiry_time = parse(data['expiry_time'])
         logger.debug("Retrieved credentials will expire at: %s", self._expiry_time)
+        self._normalize()
 
 
 class CredentialProvider(object):
diff --git a/tests/unit/test_credentials.py b/tests/unit/test_credentials.py
index 745c81d16..8cddc6aae 100644
--- a/tests/unit/test_credentials.py
+++ b/tests/unit/test_credentials.py
@@ -53,7 +53,14 @@ def path(filename):
     return os.path.join(os.path.dirname(__file__), 'cfg', filename)
 
 
-class TestRefreshableCredentials(BaseEnvVar):
+class TestCredentials(BaseEnvVar):
+    def test_detect_nonascii_character(self):
+        c = credentials.Credentials('foo\\xe2\\x80\\x99', 'bar\\xe2\\x80\\x99')
+        self.assertTrue(isinstance(c.access_key, type(u'u')))
+        self.assertTrue(isinstance(c.secret_key, type(u'u')))
+
+
+class TestRefreshableCredentials(TestCredentials):
     def setUp(self):
         super(TestRefreshableCredentials, self).setUp()
         self.refresher = mock.Mock()