From e5d72820f0fefba90f3fa98f460e64132fe4e511 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Mon, 13 Apr 2015 08:11:42 -0700 Subject: [PATCH 1/8] Validate --zip-file contains valid zip contents --- awscli/customizations/awslambda.py | 13 ++++++ tests/unit/customizations/test_awslambda.py | 45 ++++++++++++--------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/awscli/customizations/awslambda.py b/awscli/customizations/awslambda.py index 042f0c027198..d21efffc1b9f 100644 --- a/awscli/customizations/awslambda.py +++ b/awscli/customizations/awslambda.py @@ -10,6 +10,10 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import zipfile + +from botocore.vendored import six + from awscli.arguments import CustomArgument from awscli.customizations import utils @@ -29,5 +33,14 @@ def _flatten_code_argument(argument_table, **kwargs): class ZipFileArgument(CustomArgument): def add_to_params(self, parameters, value): + fileobj = six.BytesIO(value) + try: + with zipfile.ZipFile(fileobj) as f: + f.infolist() + except zipfile.BadZipfile: + raise ValueError( + "--zip-file does not contain zip file content.\n" + "Example usage: --zip-file fileb://path/to/file.zip" + ) zip_file_param = {'ZipFile': value} parameters['Code'] = zip_file_param diff --git a/tests/unit/customizations/test_awslambda.py b/tests/unit/customizations/test_awslambda.py index c4b7439da5d0..bb26e4b03b7a 100644 --- a/tests/unit/customizations/test_awslambda.py +++ b/tests/unit/customizations/test_awslambda.py @@ -10,6 +10,9 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import os +import zipfile + from awscli.testutils import unittest from awscli.testutils import BaseAWSCommandParamsTest from awscli.testutils import FileCreator @@ -21,48 +24,54 @@ class TestCreateFunction(BaseAWSCommandParamsTest): def setUp(self): super(TestCreateFunction, self).setUp() - - # Make a temporary file self.files = FileCreator() - self.contents_of_file = 'myzipcontents' self.temp_file = self.files.create_file( - 'foo', self.contents_of_file) + 'foo', 'mycontents') + self.zip_file = os.path.join(self.files.rootdir, 'foo.zip') + with zipfile.ZipFile(self.zip_file, 'w') as f: + f.write(self.temp_file) + with open(self.zip_file, 'rb') as f: + self.zip_file_contents = f.read() def tearDown(self): super(TestCreateFunction, self).tearDown() self.files.remove_all() - def test_create_function(self): + def test_create_function_with_file(self): cmdline = self.prefix cmdline += ' --function-name myfunction --runtime myruntime' - cmdline += ' --role myrole --handler myhandler --zip-file myzip' + cmdline += ' --role myrole --handler myhandler' + cmdline += ' --zip-file fileb://%s' % self.zip_file result = { 'FunctionName': 'myfunction', 'Runtime': 'myruntime', 'Role': 'myrole', 'Handler': 'myhandler', - 'Code': {'ZipFile': 'myzip'} + 'Code': {'ZipFile': self.zip_file_contents} } self.assert_params_for_cmd(cmdline, result) - def test_create_function_with_file(self): + def test_create_function_code_argument_cause_error(self): + cmdline = self.prefix + cmdline += ' --function-name myfunction --runtime myruntime' + cmdline += ' --role myrole --handler myhandler --zip-file myzip' + cmdline += ' --code mycode' + stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) + self.assertIn('Unknown options: --code', stderr) + + def test_create_function_with_invalid_file(self): cmdline = self.prefix cmdline += ' --function-name myfunction --runtime myruntime' cmdline += ' --role myrole --handler myhandler' - cmdline += ' --zip-file file://%s' % self.temp_file + cmdline += ' --zip-file filename_instead_of_contents.zip' result = { 'FunctionName': 'myfunction', 'Runtime': 'myruntime', 'Role': 'myrole', 'Handler': 'myhandler', - 'Code': {'ZipFile': self.contents_of_file} + 'Code': {'ZipFile': self.zip_file_contents} } - self.assert_params_for_cmd(cmdline, result) - - def test_create_function_code_argument_cause_error(self): - cmdline = self.prefix - cmdline += ' --function-name myfunction --runtime myruntime' - cmdline += ' --role myrole --handler myhandler --zip-file myzip' - cmdline += ' --code mycode' stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) - self.assertIn('Unknown options: --code', stderr) + self.assertIn('does not contain zip file content', stderr) + # Should also give a pointer to fileb:// for them. + self.assertIn('fileb://', stderr) From e9b2ec01e0e174a54cace69be0b1d285abe6862e Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Mon, 13 Apr 2015 08:57:50 -0700 Subject: [PATCH 2/8] Mention fileb:// in error messages for file:// loading --- awscli/paramfile.py | 10 ++++++++-- tests/unit/customizations/test_awslambda.py | 20 ++++++++++++-------- tests/unit/test_paramfile.py | 9 ++++++++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/awscli/paramfile.py b/awscli/paramfile.py index ea8d3909ece4..a822104cca88 100644 --- a/awscli/paramfile.py +++ b/awscli/paramfile.py @@ -98,8 +98,14 @@ def get_file(prefix, path, mode): if not os.path.isfile(file_path): raise ResourceLoadingError("file does not exist: %s" % file_path) try: - with compat_open(file_path, mode) as f: - return f.read() + try: + with compat_open(file_path, mode) as f: + return f.read() + except UnicodeDecodeError: + raise ResourceLoadingError( + 'Unable to load paramfile (%s), text contents could ' + 'not be decoded. If this is a binary file, please use the ' + 'fileb:// prefix instead of the file:// prefix.' % file_path) except (OSError, IOError) as e: raise ResourceLoadingError('Unable to load paramfile %s: %s' % ( path, e)) diff --git a/tests/unit/customizations/test_awslambda.py b/tests/unit/customizations/test_awslambda.py index bb26e4b03b7a..cab85aea9e84 100644 --- a/tests/unit/customizations/test_awslambda.py +++ b/tests/unit/customizations/test_awslambda.py @@ -59,19 +59,23 @@ def test_create_function_code_argument_cause_error(self): stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) self.assertIn('Unknown options: --code', stderr) - def test_create_function_with_invalid_file(self): + def test_create_function_with_invalid_file_contents(self): cmdline = self.prefix cmdline += ' --function-name myfunction --runtime myruntime' cmdline += ' --role myrole --handler myhandler' cmdline += ' --zip-file filename_instead_of_contents.zip' - result = { - 'FunctionName': 'myfunction', - 'Runtime': 'myruntime', - 'Role': 'myrole', - 'Handler': 'myhandler', - 'Code': {'ZipFile': self.zip_file_contents} - } stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) self.assertIn('does not contain zip file content', stderr) # Should also give a pointer to fileb:// for them. self.assertIn('fileb://', stderr) + + def test_not_using_fileb_prefix(self): + cmdline = self.prefix + cmdline += ' --function-name myfunction --runtime myruntime' + cmdline += ' --role myrole --handler myhandler' + # Note file:// instead of fileb:// + cmdline += ' --zip-file file://%s' % self.zip_file + stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) + # Ensure we mention fileb:// to give the user an idea of + # where to go next. + self.assertIn('fileb://', stderr) diff --git a/tests/unit/test_paramfile.py b/tests/unit/test_paramfile.py index 5a474342cdd3..1fa8475a0bed 100644 --- a/tests/unit/test_paramfile.py +++ b/tests/unit/test_paramfile.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from awscli.compat import six -from awscli.paramfile import get_paramfile +from awscli.paramfile import get_paramfile, ResourceLoadingError from awscli.testutils import unittest, FileCreator @@ -38,3 +38,10 @@ def test_binary_file(self): data = get_paramfile(prefixed_filename) self.assertEqual(data, b'This is a test') self.assertIsInstance(data, six.binary_type) + + def test_cannot_load_text_file(self): + contents = b'\xbfX\xac\xbe' + filename = self.files.create_file('foo', contents, mode='wb') + prefixed_filename = 'file://' + filename + with self.assertRaises(ResourceLoadingError): + get_paramfile(prefixed_filename) From 805c0e3648042a6041141541b66f233ce7a2f29b Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Mon, 13 Apr 2015 09:20:44 -0700 Subject: [PATCH 3/8] Cleanup paramfile module, update test coverage --- awscli/paramfile.py | 55 ++++++++++++++++++------------------ tests/unit/test_clidriver.py | 6 ++-- tests/unit/test_paramfile.py | 46 +++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 30 deletions(-) diff --git a/awscli/paramfile.py b/awscli/paramfile.py index a822104cca88..1552e3984034 100644 --- a/awscli/paramfile.py +++ b/awscli/paramfile.py @@ -11,7 +11,6 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - import logging import os @@ -74,38 +73,43 @@ class ResourceLoadingError(Exception): def get_paramfile(path): - """ + """Load parameter based on a resource URI. + It is possible to pass parameters to operations by referring to files or URI's. If such a reference is detected, this function attempts to retrieve the data from the file or URI and returns it. If there are any errors or if the ``path`` does not appear to refer to a file or URI, a ``None`` is returned. + + :type path: str + :param path: The resource URI, e.g. file://foo.txt. This value + may also be a non resource URI, in which case ``None`` is returned. + + :return: The loaded value associated with the resource URI. + If the provided ``path`` is not a resource URI, then a + value of ``None`` is returned. + """ data = None if isinstance(path, six.string_types): - for prefix in PrefixMap: + for prefix, function_spec in PREFIX_MAP.items(): if path.startswith(prefix): - kwargs = KwargsMap.get(prefix, {}) - data = PrefixMap[prefix](prefix, path, **kwargs) + function, kwargs = function_spec + data = function(prefix, path, **kwargs) return data def get_file(prefix, path, mode): - file_path = path[len(prefix):] - file_path = os.path.expanduser(file_path) - file_path = os.path.expandvars(file_path) - if not os.path.isfile(file_path): - raise ResourceLoadingError("file does not exist: %s" % file_path) + file_path = os.path.expandvars(os.path.expanduser(path[len(prefix):])) try: - try: - with compat_open(file_path, mode) as f: - return f.read() - except UnicodeDecodeError: - raise ResourceLoadingError( - 'Unable to load paramfile (%s), text contents could ' - 'not be decoded. If this is a binary file, please use the ' - 'fileb:// prefix instead of the file:// prefix.' % file_path) + with compat_open(file_path, mode) as f: + return f.read() + except UnicodeDecodeError: + raise ResourceLoadingError( + 'Unable to load paramfile (%s), text contents could ' + 'not be decoded. If this is a binary file, please use the ' + 'fileb:// prefix instead of the file:// prefix.' % file_path) except (OSError, IOError) as e: raise ResourceLoadingError('Unable to load paramfile %s: %s' % ( path, e)) @@ -124,12 +128,9 @@ def get_uri(prefix, uri): raise ResourceLoadingError('Unable to retrieve %s: %s' % (uri, e)) -PrefixMap = {'file://': get_file, - 'fileb://': get_file, - 'http://': get_uri, - 'https://': get_uri} - -KwargsMap = {'file://': {'mode': 'r'}, - 'fileb://': {'mode': 'rb'}, - 'http://': {}, - 'https://': {}} +PREFIX_MAP = { + 'file://': (get_file, {'mode': 'r'}), + 'fileb://': (get_file, {'mode': 'rb'}), + 'http://': (get_uri, {}), + 'https://': (get_uri, {}), +} diff --git a/tests/unit/test_clidriver.py b/tests/unit/test_clidriver.py index 9de7129c2bcf..103a791bfa5b 100644 --- a/tests/unit/test_clidriver.py +++ b/tests/unit/test_clidriver.py @@ -543,9 +543,11 @@ def test_file_param_does_not_exist(self): rc = driver.main('ec2 describe-instances ' '--filters file://does/not/exist.json'.split()) self.assertEqual(rc, 255) + error_msg = self.stderr.getvalue() self.assertIn("Error parsing parameter '--filters': " - "file does not exist: does/not/exist.json", - self.stderr.getvalue()) + "Unable to load paramfile file://does/not/exist.json", + error_msg) + self.assertIn("No such file or directory", error_msg) def test_aws_configure_in_error_message_no_credentials(self): driver = create_clidriver() diff --git a/tests/unit/test_paramfile.py b/tests/unit/test_paramfile.py index 1fa8475a0bed..4347a525cbfc 100644 --- a/tests/unit/test_paramfile.py +++ b/tests/unit/test_paramfile.py @@ -10,10 +10,11 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import mock from awscli.compat import six +from awscli.testutils import unittest, FileCreator from awscli.paramfile import get_paramfile, ResourceLoadingError -from awscli.testutils import unittest, FileCreator class TestParamFile(unittest.TestCase): @@ -45,3 +46,46 @@ def test_cannot_load_text_file(self): prefixed_filename = 'file://' + filename with self.assertRaises(ResourceLoadingError): get_paramfile(prefixed_filename) + + def test_file_does_not_exist_raises_error(self): + with self.assertRaises(ResourceLoadingError): + get_paramfile('file://file/does/not/existsasdf.txt') + + def test_no_match_uris_returns_none(self): + self.assertIsNone(get_paramfile('foobar://somewhere.bar')) + + def test_non_string_type_returns_none(self): + self.assertIsNone(get_paramfile(100)) + + +class TestHTTPBasedResourceLoading(unittest.TestCase): + def setUp(self): + self.requests_patch = mock.patch('awscli.paramfile.requests') + self.requests_mock = self.requests_patch.start() + self.response = mock.Mock(status_code=200) + self.requests_mock.get.return_value = self.response + + def tearDown(self): + self.requests_patch.stop() + + def test_resource_from_http(self): + self.response.text = 'http contents' + loaded = get_paramfile('http://foo.bar.baz') + self.assertEqual(loaded, 'http contents') + self.requests_mock.get.assert_called_with('http://foo.bar.baz') + + def test_resource_from_https(self): + self.response.text = 'http contents' + loaded = get_paramfile('https://foo.bar.baz') + self.assertEqual(loaded, 'http contents') + self.requests_mock.get.assert_called_with('https://foo.bar.baz') + + def test_non_200_raises_error(self): + self.response.status_code = 500 + with self.assertRaisesRegexp(ResourceLoadingError, 'foo\.bar\.baz'): + get_paramfile('https://foo.bar.baz') + + def test_connection_error_raises_error(self): + self.requests_mock.get.side_effect = Exception("Connection error.") + with self.assertRaisesRegexp(ResourceLoadingError, 'foo\.bar\.baz'): + get_paramfile('https://foo.bar.baz') From ce82b065827c67cce2072734fb17a5dac064aa82 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Tue, 14 Apr 2015 09:11:29 -0700 Subject: [PATCH 4/8] Fix py26 bug where zipfile is not a context manager --- awscli/customizations/awslambda.py | 3 ++- tests/unit/customizations/test_awslambda.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/awscli/customizations/awslambda.py b/awscli/customizations/awslambda.py index d21efffc1b9f..bd46bb9e35d8 100644 --- a/awscli/customizations/awslambda.py +++ b/awscli/customizations/awslambda.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import zipfile +from contextlib import closing from botocore.vendored import six @@ -35,7 +36,7 @@ class ZipFileArgument(CustomArgument): def add_to_params(self, parameters, value): fileobj = six.BytesIO(value) try: - with zipfile.ZipFile(fileobj) as f: + with closing(zipfile.ZipFile(fileobj)) as f: f.infolist() except zipfile.BadZipfile: raise ValueError( diff --git a/tests/unit/customizations/test_awslambda.py b/tests/unit/customizations/test_awslambda.py index cab85aea9e84..15802ac13bc8 100644 --- a/tests/unit/customizations/test_awslambda.py +++ b/tests/unit/customizations/test_awslambda.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import os import zipfile +from contextlib import closing from awscli.testutils import unittest from awscli.testutils import BaseAWSCommandParamsTest @@ -28,7 +29,7 @@ def setUp(self): self.temp_file = self.files.create_file( 'foo', 'mycontents') self.zip_file = os.path.join(self.files.rootdir, 'foo.zip') - with zipfile.ZipFile(self.zip_file, 'w') as f: + with closing(zipfile.ZipFile(self.zip_file, 'w')) as f: f.write(self.temp_file) with open(self.zip_file, 'rb') as f: self.zip_file_contents = f.read() From b34065ac02c0973782ddd07521087777a414fee7 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Tue, 14 Apr 2015 09:17:41 -0700 Subject: [PATCH 5/8] Fix py3 bug with str vs. bytes --- awscli/customizations/awslambda.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/awscli/customizations/awslambda.py b/awscli/customizations/awslambda.py index bd46bb9e35d8..03b936627732 100644 --- a/awscli/customizations/awslambda.py +++ b/awscli/customizations/awslambda.py @@ -33,15 +33,22 @@ def _flatten_code_argument(argument_table, **kwargs): class ZipFileArgument(CustomArgument): + ERROR_MSG = ( + "--zip-file does not contain zip file content.\n" + "Example usage: --zip-file fileb://path/to/file.zip") + def add_to_params(self, parameters, value): + if not isinstance(value, bytes): + # If it's not bytes it's basically impossible for + # this to be valid zip content, but we'll at least + # still try to load the contents as a zip file + # to be absolutely sure. + value = value.encode('utf-8') fileobj = six.BytesIO(value) try: with closing(zipfile.ZipFile(fileobj)) as f: f.infolist() except zipfile.BadZipfile: - raise ValueError( - "--zip-file does not contain zip file content.\n" - "Example usage: --zip-file fileb://path/to/file.zip" - ) + raise ValueError(self.ERROR_MSG) zip_file_param = {'ZipFile': value} parameters['Code'] = zip_file_param From 30aa7087313240790828f0d15e3568d1a3b666bb Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Tue, 14 Apr 2015 11:15:41 -0700 Subject: [PATCH 6/8] Update error message to be more specific 99% of the time this issue is because of a customer specifying a file path without using the fileb:// prefix. --- awscli/customizations/awslambda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awscli/customizations/awslambda.py b/awscli/customizations/awslambda.py index 03b936627732..8e3525a10ecb 100644 --- a/awscli/customizations/awslambda.py +++ b/awscli/customizations/awslambda.py @@ -34,7 +34,7 @@ def _flatten_code_argument(argument_table, **kwargs): class ZipFileArgument(CustomArgument): ERROR_MSG = ( - "--zip-file does not contain zip file content.\n" + "--zip-file must be a file with the fileb:// prefix.\n" "Example usage: --zip-file fileb://path/to/file.zip") def add_to_params(self, parameters, value): From 5dbc95b1905b5322789d397fdffff93adfbf223d Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Tue, 14 Apr 2015 13:00:45 -0700 Subject: [PATCH 7/8] Add zip file validation to update-function-code call --- awscli/customizations/awslambda.py | 43 +++++++++++++-------- tests/unit/customizations/test_awslambda.py | 39 ++++++++++++++++--- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/awscli/customizations/awslambda.py b/awscli/customizations/awslambda.py index 8e3525a10ecb..c4c22957e192 100644 --- a/awscli/customizations/awslambda.py +++ b/awscli/customizations/awslambda.py @@ -18,10 +18,21 @@ from awscli.arguments import CustomArgument from awscli.customizations import utils +ERROR_MSG = ( + "--zip-file must be a file with the fileb:// prefix.\n" + "Example usage: --zip-file fileb://path/to/file.zip") + def register_lambda_create_function(cli): cli.register('building-argument-table.lambda.create-function', _flatten_code_argument) + cli.register('process-cli-arg.lambda.update-function-code', + validate_is_zip_file) + + +def validate_is_zip_file(cli_argument, value, **kwargs): + if cli_argument.name == 'zip-file': + _should_contain_zip_content(value) def _flatten_code_argument(argument_table, **kwargs): @@ -32,23 +43,23 @@ def _flatten_code_argument(argument_table, **kwargs): del argument_table['code'] -class ZipFileArgument(CustomArgument): - ERROR_MSG = ( - "--zip-file must be a file with the fileb:// prefix.\n" - "Example usage: --zip-file fileb://path/to/file.zip") +def _should_contain_zip_content(value): + if not isinstance(value, bytes): + # If it's not bytes it's basically impossible for + # this to be valid zip content, but we'll at least + # still try to load the contents as a zip file + # to be absolutely sure. + value = value.encode('utf-8') + fileobj = six.BytesIO(value) + try: + with closing(zipfile.ZipFile(fileobj)) as f: + f.infolist() + except zipfile.BadZipfile: + raise ValueError(ERROR_MSG) + +class ZipFileArgument(CustomArgument): def add_to_params(self, parameters, value): - if not isinstance(value, bytes): - # If it's not bytes it's basically impossible for - # this to be valid zip content, but we'll at least - # still try to load the contents as a zip file - # to be absolutely sure. - value = value.encode('utf-8') - fileobj = six.BytesIO(value) - try: - with closing(zipfile.ZipFile(fileobj)) as f: - f.infolist() - except zipfile.BadZipfile: - raise ValueError(self.ERROR_MSG) + _should_contain_zip_content(value) zip_file_param = {'ZipFile': value} parameters['Code'] = zip_file_param diff --git a/tests/unit/customizations/test_awslambda.py b/tests/unit/customizations/test_awslambda.py index 15802ac13bc8..adddd30599b8 100644 --- a/tests/unit/customizations/test_awslambda.py +++ b/tests/unit/customizations/test_awslambda.py @@ -19,12 +19,10 @@ from awscli.testutils import FileCreator -class TestCreateFunction(BaseAWSCommandParamsTest): - - prefix = 'lambda create-function' +class BaseLambdaTests(BaseAWSCommandParamsTest): def setUp(self): - super(TestCreateFunction, self).setUp() + super(BaseLambdaTests, self).setUp() self.files = FileCreator() self.temp_file = self.files.create_file( 'foo', 'mycontents') @@ -35,9 +33,14 @@ def setUp(self): self.zip_file_contents = f.read() def tearDown(self): - super(TestCreateFunction, self).tearDown() + super(BaseLambdaTests, self).tearDown() self.files.remove_all() + +class TestCreateFunction(BaseLambdaTests): + + prefix = 'lambda create-function' + def test_create_function_with_file(self): cmdline = self.prefix cmdline += ' --function-name myfunction --runtime myruntime' @@ -66,7 +69,7 @@ def test_create_function_with_invalid_file_contents(self): cmdline += ' --role myrole --handler myhandler' cmdline += ' --zip-file filename_instead_of_contents.zip' stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) - self.assertIn('does not contain zip file content', stderr) + self.assertIn('must be a file with the fileb:// prefix', stderr) # Should also give a pointer to fileb:// for them. self.assertIn('fileb://', stderr) @@ -80,3 +83,27 @@ def test_not_using_fileb_prefix(self): # Ensure we mention fileb:// to give the user an idea of # where to go next. self.assertIn('fileb://', stderr) + + +class TestUpdateFunctionCode(BaseLambdaTests): + + prefix = 'lambda update-function-code' + + def test_not_using_fileb_prefix(self): + cmdline = self.prefix + ' --function-name foo' + cmdline += ' --zip-file filename_instead_of_contents.zip' + stdout, stderr, rc = self.run_cmd(cmdline, expected_rc=255) + self.assertIn('must be a file with the fileb:// prefix', stderr) + # Should also give a pointer to fileb:// for them. + self.assertIn('fileb://', stderr) + + def test_using_fileb_prefix_succeeds(self): + cmdline = self.prefix + cmdline += ' --function-name myfunction' + cmdline += ' --zip-file fileb://%s' % self.zip_file + result = { + 'FunctionName': 'myfunction', + 'ZipFile': self.zip_file_contents, + } + self.assert_params_for_cmd(cmdline, result) + From 86795d02f0dd104ecb7adf2fc50a045e008a3238 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Fri, 17 Apr 2015 16:26:17 -0700 Subject: [PATCH 8/8] Skip validation is value is None Otherwise this breaks --generate-cli-skeleton. --- awscli/customizations/awslambda.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awscli/customizations/awslambda.py b/awscli/customizations/awslambda.py index c4c22957e192..566d3c532cbe 100644 --- a/awscli/customizations/awslambda.py +++ b/awscli/customizations/awslambda.py @@ -60,6 +60,8 @@ def _should_contain_zip_content(value): class ZipFileArgument(CustomArgument): def add_to_params(self, parameters, value): + if value is None: + return _should_contain_zip_content(value) zip_file_param = {'ZipFile': value} parameters['Code'] = zip_file_param