From 41af98c98b6c372c1c158f42611dccdf4dcf6c7e Mon Sep 17 00:00:00 2001 From: Seth Black Date: Tue, 8 Oct 2019 15:59:03 -0500 Subject: [PATCH] added UpdateFunctionCode and UpdateFunctionConfiguration and associated test cases --- moto/awslambda/models.py | 65 +++++++++++++++++ moto/awslambda/responses.py | 36 ++++++++++ moto/awslambda/urls.py | 4 +- tests/test_awslambda/test_lambda.py | 106 ++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 1 deletion(-) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index acc7a5257..f2400ec38 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -273,6 +273,71 @@ class LambdaFunction(BaseModel): "Configuration": self.get_configuration(), } + def update_configuration(self, config_updates): + for key, value in config_updates.items(): + if key == "Description": + self.description = value + elif key == "Handler": + self.handler = value + elif key == "MemorySize": + self.memory_size = value + elif key == "Role": + self.role = value + elif key == "Runtime": + self.run_time = value + elif key == "Timeout": + self.timeout = value + elif key == "VpcConfig": + self.vpc_config = value + + return self.get_configuration() + + def update_function_code(self, spec): + if 'DryRun' in spec and spec['DryRun']: + return self.get_configuration() + + if 'Publish' in spec and spec['Publish']: + self.set_version(self.version + 1) + + if 'ZipFile' in spec: + # using the "hackery" from __init__" because it seems to work + # TODOs and FIXMEs included, because they'll need to be fixed + # in both places now + try: + to_unzip_code = base64.b64decode( + bytes(spec['ZipFile'], 'utf-8')) + except Exception: + to_unzip_code = base64.b64decode(spec['ZipFile']) + + self.code_bytes = to_unzip_code + self.code_size = len(to_unzip_code) + self.code_sha_256 = hashlib.sha256(to_unzip_code).hexdigest() + + # TODO: we should be putting this in a lambda bucket + self.code['UUID'] = str(uuid.uuid4()) + self.code['S3Key'] = '{}-{}'.format(self.function_name, self.code['UUID']) + else: + key = None + try: + # FIXME: does not validate bucket region + key = s3_backend.get_key(spec['S3Bucket'], spec['S3Key']) + except MissingBucket: + if do_validate_s3(): + raise ValueError( + "InvalidParameterValueException", + "Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist") + except MissingKey: + if do_validate_s3(): + raise ValueError( + "InvalidParameterValueException", + "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist.") + if key: + self.code_bytes = key.value + self.code_size = key.size + self.code_sha_256 = hashlib.sha256(key.value).hexdigest() + + return self.get_configuration() + @staticmethod def convert(s): try: diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index 1e7feb0d0..e22fee152 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -122,6 +122,18 @@ class LambdaResponse(BaseResponse): if request.method == 'POST': return self._add_policy(request, full_url, headers) + def configuration(self, request, full_url, headers): + if request.method == 'PUT': + return self._put_configuration(request, full_url) + else: + raise ValueError("Cannot handle request") + + def code(self, request, full_url, headers): + if request.method == 'PUT': + return self._put_code(request, full_url, headers) + else: + raise ValueError("Cannot handle request") + def _add_policy(self, request, full_url, headers): path = request.path if hasattr(request, 'path') else path_url(request.url) function_name = path.split('/')[-2] @@ -308,3 +320,27 @@ class LambdaResponse(BaseResponse): return 204, {}, "{}" else: return 404, {}, "{}" + + def _put_configuration(self, request, full_url): + function_name = self._get_param('FunctionName', None) + qualifier = self._get_param('Qualifier', None) + + fn = self.lambda_backend.get_function(function_name, qualifier) + + if fn: + config = fn.update_configuration(json.loads(request.body)) + return 200, {}, json.dumps(config) + else: + return 404, {}, "{}" + + def _put_code(self, request, full_url, headers): + function_name = self._get_param('FunctionName', None) + qualifier = self._get_param('Qualifier', None) + + fn = self.lambda_backend.get_function(function_name, qualifier) + + if fn: + config = fn.update_function_code(json.loads(request.body)) + return 200, {}, json.dumps(config) + else: + return 404, {}, "{}" diff --git a/moto/awslambda/urls.py b/moto/awslambda/urls.py index fb2c6ee7e..a306feff5 100644 --- a/moto/awslambda/urls.py +++ b/moto/awslambda/urls.py @@ -16,5 +16,7 @@ url_paths = { r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invocations/?$': response.invoke, r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invoke-async/?$': response.invoke_async, r'{0}/(?P[^/]+)/tags/(?P.+)': response.tag, - r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/policy/?$': response.policy + r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/policy/?$': response.policy, + r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/configuration/?$': response.configuration, + r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/code/?$': response.code } diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 9467b0803..0a6df7cca 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -1245,3 +1245,109 @@ def test_delete_event_source_mapping(): assert response['State'] == 'Deleting' conn.get_event_source_mapping.when.called_with(UUID=response['UUID'])\ .should.throw(botocore.client.ClientError) + + +@mock_lambda +@mock_s3 +def test_update_configuration(): + s3_conn = boto3.client('s3', 'us-west-2') + s3_conn.create_bucket(Bucket='test-bucket') + + zip_content = get_test_zip_file2() + s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) + conn = boto3.client('lambda', 'us-west-2') + + fxn = conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.lambda_handler', + Code={ + 'S3Bucket': 'test-bucket', + 'S3Key': 'test.zip', + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + assert fxn['Description'] == 'test lambda function' + assert fxn['Handler'] == 'lambda_function.lambda_handler' + assert fxn['MemorySize'] == 128 + assert fxn['Runtime'] == 'python2.7' + assert fxn['Timeout'] == 3 + + updated_config = conn.update_function_configuration( + FunctionName='testFunction', + Description='updated test lambda function', + Handler='lambda_function.new_lambda_handler', + Runtime='python3.6', + Timeout=7 + ) + + assert updated_config['ResponseMetadata']['HTTPStatusCode'] == 200 + assert updated_config['Description'] == 'updated test lambda function' + assert updated_config['Handler'] == 'lambda_function.new_lambda_handler' + assert updated_config['MemorySize'] == 128 + assert updated_config['Runtime'] == 'python3.6' + assert updated_config['Timeout'] == 7 + + +@mock_lambda +@freeze_time('2015-01-01 00:00:00') +def test_update_function(): + conn = boto3.client('lambda', 'us-west-2') + + zip_content_one = get_test_zip_file1() + + fxn = conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.lambda_handler', + Code={ + 'ZipFile': zip_content_one, + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + zip_content_two = get_test_zip_file2() + + conn.update_function_code( + FunctionName='testFunction', + ZipFile=zip_content_two, + Publish=True + ) + + response = conn.get_function( + FunctionName='testFunction' + ) + response['Configuration'].pop('LastModified') + + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + assert len(response['Code']) == 2 + assert response['Code']['RepositoryType'] == 'S3' + assert response['Code']['Location'].startswith('s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com'.format(_lambda_region)) + response['Configuration'].should.equal( + { + "CodeSha256": hashlib.sha256(zip_content_two).hexdigest(), + "CodeSize": len(zip_content_two), + "Description": "test lambda function", + "FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunction:2'.format(_lambda_region), + "FunctionName": "testFunction", + "Handler": "lambda_function.lambda_handler", + "MemorySize": 128, + "Role": "test-iam-role", + "Runtime": "python2.7", + "Timeout": 3, + "Version": '$LATEST', + "VpcConfig": { + "SecurityGroupIds": [], + "SubnetIds": [], + } + }, + )